diff --git a/.gitignore b/.gitignore index 0ac9b0d71..75fdfe79b 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,8 @@ !/app/files/warninglists/* !/app/files/misp-galaxy !/app/files/misp-galaxy/* +!/app/files/misp-objects +!/app/files/misp-objects/* /app/files/scripts/python-stix/ /app/files/scripts/python-cybox/ /app/files/scripts/mixbox/ diff --git a/.gitmodules b/.gitmodules index c308eedbd..a26b0d31b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -21,6 +21,10 @@ [submodule "app/files/misp-galaxy"] path = app/files/misp-galaxy url = https://github.com/MISP/misp-galaxy +[submodule "app/files/misp-objects"] + path = app/files/misp-objects + url = https://github.com/MISP/misp-objects [submodule "misp-vagrant"] path = misp-vagrant url = https://github.com/MISP/misp-vagrant.git + diff --git a/INSTALL/MYSQL.sql b/INSTALL/MYSQL.sql index 76b292120..407104ae4 100644 --- a/INSTALL/MYSQL.sql +++ b/INSTALL/MYSQL.sql @@ -20,6 +20,8 @@ CREATE TABLE IF NOT EXISTS `admin_settings` ( CREATE TABLE IF NOT EXISTS `attributes` ( `id` int(11) NOT NULL AUTO_INCREMENT, `event_id` int(11) NOT NULL, + `object_id` int(11) NOT NULL DEFAULT 0, + `object_relation` varchar(255) COLLATE utf8_bin, `category` varchar(255) COLLATE utf8_bin NOT NULL, `type` varchar(100) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, `value1` text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, @@ -34,6 +36,8 @@ CREATE TABLE IF NOT EXISTS `attributes` ( `disable_correlation` tinyint(1) NOT NULL DEFAULT 0, PRIMARY KEY (`id`), INDEX `event_id` (`event_id`), + INDEX `object_id` (`object_id`), + INDEX `object_relation` (`object_relation`), INDEX `value1` (`value1`(255)), INDEX `value2` (`value2`(255)), INDEX `type` (`type`), @@ -418,6 +422,131 @@ CREATE TABLE IF NOT EXISTS `org_blacklists` ( -- -------------------------------------------------------- +-- +-- Table structure for table `objects` +-- + +CREATE TABLE IF NOT EXISTS objects ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci, + `meta-category` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci, + `description` text CHARACTER SET utf8 COLLATE utf8_unicode_ci, + `template_uuid` varchar(40) COLLATE utf8_bin DEFAULT NULL, + `template_version` int(11) NOT NULL, + `event_id` int(11) NOT NULL, + `uuid` varchar(40) COLLATE utf8_bin DEFAULT NULL, + `timestamp` int(11) NOT NULL DEFAULT 0, + `distribution` tinyint(4) NOT NULL DEFAULT 0, + `sharing_group_id` int(11), + `comment` text COLLATE utf8_bin NOT NULL, + `deleted` TINYINT NOT NULL DEFAULT 0, + PRIMARY KEY (id), + INDEX `name` (`name`), + INDEX `template_uuid` (`template_uuid`), + INDEX `template_version` (`template_version`), + INDEX `meta-category` (`meta-category`), + INDEX `event_id` (`event_id`), + INDEX `uuid` (`uuid`), + INDEX `timestamp` (`timestamp`), + INDEX `distribution` (`distribution`), + INDEX `sharing_group_id` (`sharing_group_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `object_object_references` +-- + +CREATE TABLE IF NOT EXISTS object_references ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `uuid` varchar(40) COLLATE utf8_bin DEFAULT NULL, + `timestamp` int(11) NOT NULL DEFAULT 0, + `object_id` int(11) NOT NULL, + `event_id` int(11) NOT NULL, + `source_uuid` varchar(40) COLLATE utf8_bin DEFAULT NULL, + `referenced_uuid` varchar(40) COLLATE utf8_bin DEFAULT NULL, + `referenced_id` int(11) NOT NULL, + `referenced_type` int(11) NOT NULL DEFAULT 0, + `relationship_type` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci, + `comment` text COLLATE utf8_bin NOT NULL, + `deleted` TINYINT NOT NULL DEFAULT 0, + PRIMARY KEY (id), + INDEX `source_uuid` (`source_uuid`), + INDEX `referenced_uuid` (`referenced_uuid`), + INDEX `timestamp` (`timestamp`), + INDEX `object_id` (`object_id`), + INDEX `referenced_id` (`referenced_id`), + INDEX `relationship_type` (`relationship_type`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `object_relationships` +-- + +CREATE TABLE IF NOT EXISTS object_relationships ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `version` int(11) NOT NULL, + `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci, + `description` text COLLATE utf8_bin NOT NULL, + `format` text COLLATE utf8_bin NOT NULL, + PRIMARY KEY (id), + INDEX `name` (`name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `object_templates` +-- + +CREATE TABLE IF NOT EXISTS object_templates ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `user_id` int(11) NOT NULL, + `org_id` int(11) NOT NULL, + `uuid` varchar(40) COLLATE utf8_bin DEFAULT NULL, + `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci, + `meta-category` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci, + `description` text COLLATE utf8_bin, + `version` int(11) NOT NULL, + `requirements` text COLLATE utf8_bin, + `fixed` tinyint(1) NOT NULL DEFAULT 0, + `active` tinyint(1) NOT NULL DEFAULT 0, + PRIMARY KEY (id), + INDEX `user_id` (`user_id`), + INDEX `org_id` (`org_id`), + INDEX `uuid` (`uuid`), + INDEX `name` (`name`), + INDEX `meta-category` (`meta-category`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + +-- +-- Table structure for table `object_template_elements` +-- + +CREATE TABLE IF NOT EXISTS object_template_elements ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `object_template_id` int(11) NOT NULL, + `object_relation` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin, + `type` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin, + `ui-priority` int(11) NOT NULL, + `categories` text COLLATE utf8_bin, + `sane_default` text COLLATE utf8_bin, + `values_list` text COLLATE utf8_bin, + `description` text COLLATE utf8_bin, + `disable_correlations` tinyint(1) NOT NULL DEFAULT 0, + `multiple` tinyint(1) NOT NULL DEFAULT 0, + PRIMARY KEY (id), + INDEX `object_relation` (`object_relation`), + INDEX `type` (`type`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- -------------------------------------------------------- + -- -- Table structure for table `organisations` -- @@ -513,6 +642,7 @@ CREATE TABLE IF NOT EXISTS `roles` ( `perm_sharing_group` tinyint(1) NOT NULL DEFAULT 0, `perm_tag_editor` tinyint(1) NOT NULL DEFAULT 0, `perm_sighting` tinyint(1) NOT NULL DEFAULT 0, + `perm_object_template` tinyint(1) NOT NULL DEFAULT 0, `default_role` tinyint(1) NOT NULL DEFAULT 0, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; @@ -1061,23 +1191,23 @@ INSERT INTO `feeds` (`id`, `provider`, `name`, `url`, `distribution`, `default`, -- 7. Read Only - read -- -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`, `default_role`) -VALUES (1, 'admin', NOW(), NOW(), 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 (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`, `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); +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); -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`, `default_role`) -VALUES (3, 'User', NOW(), NOW(), 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1); +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); -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`, `default_role`) -VALUES (4, 'Publisher', NOW(), NOW(), 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 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 (4, 'Publisher', NOW(), NOW(), 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 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`, `default_role`) -VALUES (5, 'Sync user', NOW(), NOW(), 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 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 (5, 'Sync user', NOW(), NOW(), 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 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`, `default_role`) -VALUES (6, 'Read Only', NOW(), NOW(), 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 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 (6, 'Read Only', NOW(), NOW(), 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); -- -------------------------------------------------------- diff --git a/PyMISP b/PyMISP index bfa5b67c1..6eb807381 160000 --- a/PyMISP +++ b/PyMISP @@ -1 +1 @@ -Subproject commit bfa5b67c1d17467ae18b29cb1e328a19698a3146 +Subproject commit 6eb807381dfa3d2a9dd2a42fdd219d6f3cdfd1ff diff --git a/VERSION.json b/VERSION.json index b1f16b13a..f734a1088 100644 --- a/VERSION.json +++ b/VERSION.json @@ -1 +1 @@ -{"major":2, "minor":4, "hotfix":79} +{"major":2, "minor":4, "hotfix":80} diff --git a/app/Controller/AttributesController.php b/app/Controller/AttributesController.php index dfdaf7545..27bd870e5 100644 --- a/app/Controller/AttributesController.php +++ b/app/Controller/AttributesController.php @@ -345,7 +345,6 @@ class AttributesController extends AppController { $success = 0; foreach ($this->request->data['Attribute']['values'] as $k => $value) { - // Check if there were problems with the file upload // only keep the last part of the filename, this should prevent directory attacks $filename = basename($value['name']); @@ -361,36 +360,39 @@ class AttributesController extends AppController { } if ($this->request->data['Attribute']['malware']) { - $result = $this->Event->Attribute->handleMaliciousBase64($this->request->data['Attribute']['event_id'], $filename, base64_encode($tmpfile->read()), array_keys($hashes)); - if (!$result['success']) { - $this->Session->setFlash(__('There was a problem to upload the file.', true), 'default', array(), 'error'); - $this->redirect(array('controller' => 'events', 'action' => 'view', $this->request->data['Attribute']['event_id'])); - } - foreach ($hashes as $hash => $typeName) { - if (!$result[$hash]) continue; - $attribute = array( - 'Attribute' => array( - 'value' => $filename . '|' . $result[$hash], - 'category' => $this->request->data['Attribute']['category'], - 'type' => $typeName, - 'event_id' => $this->request->data['Attribute']['event_id'], - 'comment' => $this->request->data['Attribute']['comment'], - 'to_ids' => 1, - 'distribution' => $this->request->data['Attribute']['distribution'], - 'sharing_group_id' => isset($this->request->data['Attribute']['sharing_group_id']) ? $this->request->data['Attribute']['sharing_group_id'] : 0, - ) + if ($this->request->data['Attribute']['advanced']) { + $result = $this->Attribute->advancedAddMalwareSample($tmpfile); + if ($result) $success++; + else $fails[] = $filename; + } else { + $result = $this->Attribute->simpleAddMalwareSample( + $eventId, + $this->request->data['Attribute']['category'], + $this->request->data['Attribute']['distribution'], + $this->request->data['Attribute']['distribution'] == 4 ? $this->request->data['Attribute']['sharing_group_id'] : 0, + $this->request->data['Attribute']['comment'], + $filename, + $tmpfile ); - if ($hash == 'md5') $attribute['Attribute']['data'] = $result['data']; - $this->Attribute->create(); - $r = $this->Attribute->save($attribute); - if ($r == false) { - if ($hash == 'md5') { - $fails[] = $filename; - } else { - $partialFails[] = '[' . $typeName . ']' . $filename; + if ($result) $success++; + else $fails[] = $filename; + } + if (!empty($result)) { + foreach ($result['Object'] as $object) { + $object['distribution'] = $this->request->data['Attribute']['distribution']; + $object['sharing_group_id'] = isset($this->request->data['Attribute']['distribution']) ? $this->request->data['Attribute']['distribution'] : 0; + if (!empty($object['Attribute'])) { + foreach ($object['Attribute'] as $k => $attribute) { + if ($attribute['value'] == $tmpfile->name) $object['Attribute'][$k]['value'] = $value['name']; + } + } + $this->loadModel('MispObject'); + $this->MispObject->captureObject(array('Object' => $object), $eventId, $this->Auth->user()); + } + if (!empty($result['ObjectReference'])) { + foreach ($result['ObjectReference'] as $reference) { + $this->MispObject->ObjectReference->smartSave($reference, $eventId); } - } else { - if ($hash == 'md5') $success++; } } } else { @@ -413,9 +415,8 @@ class AttributesController extends AppController { else $success++; } } - $message = 'The attachment(s) have been uploaded.'; - if (!empty($partialFails)) $message .= ' Some of the hashes however could not be generated.'; + if (!empty($partialFails)) $message .= ' Some of the attributes however could not be created.'; if (!empty($fails)) $message = 'Some of the attachments failed to upload. The failed files were: ' . implode(', ', $fails) . ' - This can be caused by the attachments already existing in the event.'; if (empty($success)) { if (empty($fails)) $message = 'The attachment(s) could not be saved. please contact your administrator.'; @@ -729,12 +730,29 @@ class AttributesController extends AppController { // enabling / disabling the distribution field in the edit view based on whether user's org == orgc in the event $this->Event->read(); - if ($this->Attribute->save($this->request->data)) { + if ($this->Attribute->data['Attribute']['object_id']) { + $result = $this->Attribute->save($this->request->data, array('Attribute.category', 'Attribute.value', 'Attribute.to_ids', 'Attribute.comment', 'Attribute.distribution', 'Attribute.sharing_group_id')); + $this->Attribute->Object->updateTimestamp($id); + } else { + $result = $this->Attribute->save($this->request->data); + } + if ($result) { $this->Session->setFlash(__('The attribute has been saved')); // remove the published flag from the event + $this->Event->unpublishEvent($eventId); $this->Event->set('timestamp', $date->getTimestamp()); $this->Event->set('published', 0); $this->Event->save($this->Event->data, array('fieldList' => array('published', 'timestamp', 'info'))); + if (!empty($this->Attribute->data['Attribute']['object_id'])) { + $object = $this->Attribute->Object->find('first', array( + 'recursive' => -1, + 'conditions' => array('Object.id' => $this->Attribute->data['Attribute']['object_id']) + )); + if (!empty($object)) { + $object['Object']['timestamp'] = $date->getTimestamp(); + $this->Attribute->Object->save($object); + } + } if ($this->_isRest() || $this->response->type() === 'application/json') { $saved_attribute = $this->Attribute->find('first', array( 'conditions' => array('id' => $this->Attribute->id), @@ -760,7 +778,11 @@ class AttributesController extends AppController { $this->request->data = $this->Attribute->read(null, $id); } $this->set('attribute', $this->request->data); - + if ($this->request->data['Attribute']['object_id']) { + $this->set('objectAttribute', true); + } else { + $this->set('objectAttribute', false); + } // enabling / disabling the distribution field in the edit view based on whether user's org == orgc in the event $this->loadModel('Event'); $this->Event->id = $eventId; @@ -777,9 +799,6 @@ class AttributesController extends AppController { $types = $this->_arrayToValuesIndexArray($types); $this->set('types', $types); // combobox for categories - $categories = array_keys($this->Attribute->categoryDefinitions); - $categories = $this->_arrayToValuesIndexArray($categories); - $this->set('categories', $categories); $this->set('currentDist', $this->Event->data['Event']['distribution']); $this->loadModel('SharingGroup'); @@ -802,7 +821,23 @@ class AttributesController extends AppController { $this->set('info', $info); $this->set('attrDescriptions', $this->Attribute->fieldDescriptions); $this->set('typeDefinitions', $this->Attribute->typeDefinitions); - $this->set('categoryDefinitions', $this->Attribute->categoryDefinitions); + $categoryDefinitions = $this->Attribute->categoryDefinitions; + $categories = array_keys($this->Attribute->categoryDefinitions); + $categories = $this->_arrayToValuesIndexArray($categories); + if ($this->request->data['Attribute']['object_id']) { + foreach ($categoryDefinitions as $k => $v) { + if (!in_array($this->request->data['Attribute']['type'], $v['types'])) { + unset($categoryDefinitions[$k]); + } + } + foreach ($categories as $k => $v) { + if (!isset($categoryDefinitions[$k])) { + unset($categories[$k]); + } + } + } + $this->set('categories', $categories); + $this->set('categoryDefinitions', $categoryDefinitions); } // ajax edit - post a single edited field and this method will attempt to save it and return a json with the validation errors if they occur. @@ -1041,6 +1076,17 @@ class AttributesController extends AppController { $result['Attribute']['deleted'] = 1; $result['Attribute']['timestamp'] = $date->getTimestamp(); $save = $this->Attribute->save($result); + $object_refs = $this->Attribute->Object->ObjectReference->find('all', array( + 'conditions' => array( + 'ObjectReference.referenced_type' => 0, + 'ObjectReference.referenced_id' => $id, + ), + 'recursive' => -1 + )); + foreach ($object_refs as $ref) { + $ref['ObjectReference']['deleted'] = 1; + $this->Attribute->Object->ObjectReference->save($ref); + } } // attachment will be deleted with the beforeDelete() function in the Model if ($save) { diff --git a/app/Controller/Component/ACLComponent.php b/app/Controller/Component/ACLComponent.php index 2731f743a..eac6ea1aa 100644 --- a/app/Controller/Component/ACLComponent.php +++ b/app/Controller/Component/ACLComponent.php @@ -105,9 +105,11 @@ class ACLComponent extends Component { 'freeTextImport' => array('perm_add'), 'hids' => array('*'), 'index' => array('*'), + 'massDelete' => array('perm_site_admin'), 'nids' => array('*'), 'proposalEventIndex' => array('*'), 'publish' => array('perm_publish'), + 'pushEventToZMQ' => array('perm_site_admin'), 'pushProposals' => array('perm_sync'), 'queryEnrichment' => array('perm_add'), 'removePivot' => array('*'), @@ -189,6 +191,35 @@ class ACLComponent extends Component { 'delete' => array(), 'index' => array('*'), ), + 'objects' => array( + 'add' => array('perm_add'), + 'addValueField' => array('perm_add'), + 'delete' => array('perm_add'), + 'edit' => array('perm_add'), + 'get_row' => array('perm_add'), + 'revise_object' => array('perm_add'), + 'view' => array('*'), + ), + 'objectReferences' => array( + 'add' => array('perm_add'), + 'delete' => array('perm_add'), + 'view' => array('*'), + ), + 'objectTemplates' => array( + 'activate' => array(), + 'add' => array('perm_object_template'), + 'edit' => array('perm_object_template'), + 'delete' => array('perm_object_template'), + 'getToggleField' => array(), + 'objectChoice' => array('*'), + 'view' => array('*'), + 'viewElements' => array('*'), + 'index' => array('*'), + 'update' => array('perm_site_admin') + ), + 'objectTemplateElements' => array( + 'viewElements' => array('*') + ), 'orgBlacklists' => array( 'add' => array(), 'delete' => array(), @@ -215,6 +246,7 @@ class ACLComponent extends Component { 'add' => array('*'), 'delete' => array('*'), 'edit' => array('*'), + 'pushMessageToZMQ' => array('perm_site_admin') ), 'regexp' => array( 'admin_add' => array('perm_regexp_access'), @@ -243,6 +275,7 @@ class ACLComponent extends Component { 'fetchServersForSG' => array('*'), 'filterEventIndex' => array(), 'getGit' => array(), + 'getInstanceUUID' => array('perm_sync'), 'getPyMISPVersion' => array('*'), 'getVersion' => array('*'), 'index' => array('OR' => array('perm_sync', 'perm_admin')), diff --git a/app/Controller/EventsController.php b/app/Controller/EventsController.php index 32c661586..cbe527866 100644 --- a/app/Controller/EventsController.php +++ b/app/Controller/EventsController.php @@ -715,6 +715,9 @@ class EventsController extends AppController { } public function viewEventAttributes($id, $all = false) { + if (isset($this->params['named']['focus'])) { + $this->set('focus', $this->params['named']['focus']); + } $conditions = array('eventid' => $id); if (isset($this->params['named']['deleted']) && $this->params['named']['deleted']) { $conditions['deleted'] = 1; @@ -722,7 +725,7 @@ class EventsController extends AppController { $results = $this->Event->fetchEvent($this->Auth->user(), $conditions); if (empty($results)) throw new NotFoundException('Invalid event'); $event = $results[0]; - $emptyEvent = (!isset($event['Attribute']) || empty($event['Attribute'])); + $emptyEvent = (empty($event['Object']) && empty($event['Attribute'])); $this->set('emptyEvent', $emptyEvent); $params = $this->Event->rearrangeEventForView($event, $this->passedArgs, $all); $this->params->params['paging'] = array($this->modelClass => $params); @@ -773,13 +776,23 @@ class EventsController extends AppController { $this->disableCache(); $this->layout = 'ajax'; $this->loadModel('Sighting'); + $uriArray = explode('/', $this->params->here); + foreach ($uriArray as $k => $v) { + if (strpos($v, ':')) { + $temp = explode(':', $v); + if ($temp[0] == 'focus') { + unset($uriArray[$k]); + } + } + $this->params->here = implode('/', $uriArray); + } $this->set('sightingTypes', $this->Sighting->type); $this->set('currentUri', $this->params->here); $this->render('/Elements/eventattribute'); } private function __viewUI($event, $continue, $fromEvent) { - $emptyEvent = (!isset($event['Attribute']) || empty($event['Attribute'])); + $emptyEvent = (empty($event['Object']) && empty($event['Attribute'])); $this->set('emptyEvent', $emptyEvent); $attributeCount = isset($event['Attribute']) ? count($event['Attribute']) : 0; $this->set('attribute_count', $attributeCount); @@ -893,6 +906,13 @@ class EventsController extends AppController { $this->set('typeGroups', array_keys($this->Event->Attribute->typeGroupings)); $this->loadModel('Sighting'); $this->set('sightingTypes', $this->Sighting->type); + $attributeUri = '/events/viewEventAttributes/' . $event['Event']['id']; + foreach ($this->params->named as $k => $v) { + if (!is_numeric($k)) { + $attributeUri .= '/' . $v; + } + } + $this->set('currentUri', $attributeUri); } public function view($id = null, $continue=false, $fromEvent=null) { diff --git a/app/Controller/ObjectReferencesController.php b/app/Controller/ObjectReferencesController.php new file mode 100644 index 000000000..458770f3e --- /dev/null +++ b/app/Controller/ObjectReferencesController.php @@ -0,0 +1,217 @@ + 20, + 'order' => array( + 'ObjectReference.id' => 'desc' + ), + ); + + public function add($objectId) { + if (Validation::uuid($objectId)) { + $temp = $this->ObjectReference->Object->find('first', array( + 'recursive' => -1, + 'fields' => array('Object.id'), + 'conditions' => array('Object.uuid' => $objectId) + )); + if (empty($temp)) throw new NotFoundException('Invalid Object'); + $objectId = $temp['Object']['id']; + } else if (!is_numeric($objectId)) { + throw new NotFoundException(__('Invalid object')); + } + $object = $this->ObjectReference->Object->find('first', array( + 'conditions' => array('Object.id' => $objectId), + 'recursive' => -1, + 'contain' => array( + 'Event' => array( + 'fields' => array('Event.id', 'Event.orgc_id') + ) + ) + )); + if (!$this->userRole['perm_add']) { + throw new MethodNotAllowedException('You don\'t have the required permissions to add object reference.'); + } + if (empty($object) || (!$this->_isSiteAdmin() && $object['Event']['orgc_id'] != $this->Auth->user('orgc_id'))) { + throw new MethodNotAllowedException('Invalid object.'); + } + $this->set('objectId', $objectId); + if ($this->request->is('post')) { + $data = array(); + if (!isset($this->request->data['ObjectReference'])) { + $this->request->data['ObjectReference'] = $this->request->data; + } + $referenced_type = 1; + $target_object = $this->ObjectReference->Object->find('first', array( + 'conditions' => array('Object.uuid' => $this->request->data['ObjectReference']['uuid']), + 'recursive' => -1, + 'fields' => array('Object.id', 'Object.uuid', 'Object.event_id') + )); + if (!empty($target_object)) { + $referenced_id = $target_object['Object']['id']; + $referenced_uuid = $target_object['Object']['uuid']; + if ($target_object['Object']['event_id'] != $object['Event']['id']) { + throw new NotFoundException('Invalid target. Target has to be within the same event.'); + } + } else { + $target_attribute = $this->ObjectReference->Object->Attribute->find('first', array( + 'conditions' => array('Attribute.uuid' => $this->request->data['ObjectReference']['uuid']), + 'recursive' => -1, + 'fields' => array('Attribute.id', 'Attribute.uuid', 'Attribute.event_id') + )); + if (empty($target_attribute)) { + throw new NotFoundException('Invalid target.'); + } + if ($target_attribute['Attribute']['event_id'] != $object['Event']['id']) { + throw new NotFoundException('Invalid target. Target has to be within the same event.'); + } + $referenced_id = $target_attribute['Attribute']['id']; + $referenced_uuid = $target_attribute['Attribute']['uuid']; + $referenced_type = 0; + } + $relationship_type = empty($this->request->data['ObjectReference']['relationship_type']) ? '' : $this->request->data['ObjectReference']['relationship_type']; + if (!empty($this->request->data['ObjectReference']['relationship_type_select']) && $this->request->data['ObjectReference']['relationship_type_select'] !== 'custom') { + $relationship_type = $this->request->data['ObjectReference']['relationship_type_select']; + } + $data = array( + 'referenced_type' => $referenced_type, + 'referenced_id' => $referenced_id, + 'referenced_uuid' => $referenced_uuid, + 'relationship_type' => $relationship_type, + 'comment' => !empty($this->request->data['ObjectReference']['comment']) ? $this->request->data['ObjectReference']['comment'] : '', + 'event_id' => $object['Event']['id'], + 'object_uuid' => $object['Object']['uuid'], + 'object_id' => $objectId, + 'referenced_type' => $referenced_type, + 'uuid' => CakeText::uuid() + ); + $this->ObjectReference->create(); + $result = $this->ObjectReference->save(array('ObjectReference' => $data)); + if ($result) { + $this->ObjectReference->updateTimestamps($this->id, $data); + if ($this->_isRest()) { + $object = $this->ObjectReference->find("first", array( + 'recursive' => -1, + 'conditions' => array('ObjectReference.id' => $this->ObjectReference->id) + )); + return $this->RestResponse->viewData($object, $this->response->type()); + } else if ($this->request->is('ajax')) { + return new CakeResponse(array('body'=> json_encode(array('saved' => true, 'success' => 'Object reference added.')),'status'=>200, 'type' => 'json')); + } + } else { + if ($this->_isRest()) { + return $this->RestResponse->saveFailResponse('ObjectReferences', 'add', false, $this->ObjectReference->validationErrors, $this->response->type()); + } else if ($this->request->is('ajax')) { + return new CakeResponse(array('body'=> json_encode(array('saved' => false, 'errors' => 'Object reference could not be added.')),'status'=>200, 'type' => 'json')); + } + } + } else { + if ($this->_isRest()) { + return $this->RestResponse->describe('ObjectReferences', 'add', false, $this->response->type()); + } else { + $event = $this->ObjectReference->Object->Event->find('first', array( + 'conditions' => array('Event.id' => $object['Event']['id']), + 'recursive' => -1, + 'fields' => array('Event.id'), + 'contain' => array( + 'Attribute' => array( + 'conditions' => array('Attribute.deleted' => 0, 'Attribute.object_id' => 0), + 'fields' => array('Attribute.id', 'Attribute.uuid', 'Attribute.type', 'Attribute.category', 'Attribute.value', 'Attribute.to_ids') + ), + 'Object' => array( + 'conditions' => array('Object.deleted' => 0), + 'conditions' => array('NOT' => array('Object.id' => $objectId)), + 'fields' => array('Object.id', 'Object.uuid', 'Object.name', 'Object.meta-category'), + 'Attribute' => array( + 'conditions' => array('Attribute.deleted' => 0), + 'fields' => array('Attribute.id', 'Attribute.uuid', 'Attribute.type', 'Attribute.category', 'Attribute.value', 'Attribute.to_ids') + ) + ) + ) + )); + $toRearrange = array('Attribute', 'Object'); + foreach ($toRearrange as $d) { + if (!empty($event[$d])) { + $temp = array(); + foreach ($event[$d] as $data) { + $temp[$data['uuid']] = $data; + } + $event[$d] = $temp; + } + } + $this->loadModel('ObjectRelationship'); + $relationshipsTemp = $this->ObjectRelationship->find('all', array( + 'recursive' => -1 + )); + $relationships = array(); + $relationshipMetadata = array(); + foreach ($relationshipsTemp as $k => $v) { + $relationshipMetadata[$v['ObjectRelationship']['name']] = $v; + $relationships[$v['ObjectRelationship']['name']] = $v['ObjectRelationship']['name']; + } + $relationships['custom'] = 'custom'; + $this->set('relationships', $relationships); + $this->set('event', $event); + $this->set('objectId', $objectId); + $this->layout = 'ajax'; + $this->render('ajax/add'); + } + } + + } + + public function delete($id, $hard = false) { + if (Validation::uuid($id)) { + $temp = $this->ObjectReference->find('first', array( + 'recursive' => -1, + 'fields' => array('ObjectReference.id'), + 'conditions' => array('ObjectReference.uuid' => $id) + )); + if (empty($temp)) throw new NotFoundException('Invalid object reference'); + $id = $temp['ObjectReference']['id']; + } else if (!is_numeric($id)) { + throw new NotFoundException(__('Invalid object reference')); + } + $objectReference = $this->ObjectReference->find('first', array( + 'conditions' => array('ObjectReference.id' => $id), + 'recursive' => -1, + 'contain' => array('Object' => array('Event')) + )); + if (empty($objectReference)) { + throw new MethodNotAllowedException('Invalid object reference.'); + } + if (!$this->_isSiteAdmin() && $this->Auth->user('org_id') != $objectReference['Object']['Event']['orgc_id']) { + throw new MethodNotAllowedException('Invalid object reference.'); + } + if ($this->request->is('post') || $this->request->is('put') || $this->request->is('delete')) { + $result = $this->ObjectReference->smartDelete($objectReference['ObjectReference']['id'], $hard); + if ($result === true) { + if ($this->_isRest()) { + return $this->RestResponse->saveSuccessResponse('ObjectReferences', 'delete', $id, $this->response->type()); + } else { + return new CakeResponse(array('body'=> json_encode(array('saved' => true, 'success' => 'Object reference deleted.')), 'status'=>200, 'type' => 'json')); + } + } else { + if ($this->_isRest()) { + return $this->RestResponse->saveFailResponse('ObjectReferences', 'delete', $id, $result, $this->response->type()); + } else { + return new CakeResponse(array('body'=> json_encode(array('saved' => false, 'errors' => 'Object reference was not deleted.')), 'status'=>200, 'type' => 'json')); + } + } + } else { + $this->set('hard', $hard); + $this->set('id', $id); + $this->set('event_id', $objectReference['Object']['Event']['id']); + $this->render('ajax/delete'); + } + } + + public function view($id) { + + } +} diff --git a/app/Controller/ObjectTemplateElementsController.php b/app/Controller/ObjectTemplateElementsController.php new file mode 100644 index 000000000..62b85685e --- /dev/null +++ b/app/Controller/ObjectTemplateElementsController.php @@ -0,0 +1,23 @@ + 60, + 'order' => array( + 'ObjectTemplateElement.id' => 'desc' + ), + 'recursive' => -1 + ); + + public function viewElements($id, $context = 'all') { + $this->paginate['conditions'] = array('ObjectTemplateElement.object_template_id' => $id); + $elements = $this->paginate(); + $this->set('list', $elements); + $this->layout = 'ajax'; + $this->render('ajax/view_elements'); + } +} diff --git a/app/Controller/ObjectTemplatesController.php b/app/Controller/ObjectTemplatesController.php new file mode 100644 index 000000000..9e99423c2 --- /dev/null +++ b/app/Controller/ObjectTemplatesController.php @@ -0,0 +1,202 @@ + 60, + 'order' => array( + 'Object.id' => 'desc' + ), + 'contain' => array( + 'Organisation' => array('fields' => array('Organisation.id', 'Organisation.name', 'Organisation.uuid')) + ), + 'recursive' => -1 + ); + + public function objectChoice($event_id) { + $this->ObjectTemplate->populateIfEmpty($this->Auth->user()); + $templates_raw = $this->ObjectTemplate->find('all', array( + 'recursive' => -1, + 'conditions' => array('ObjectTemplate.active' => 1), + 'fields' => array('id', 'meta-category', 'name', 'description', 'org_id'), + 'contain' => array('Organisation.name'), + 'sort' => array('ObjectTemplate.name asc') + )); + $templates = array('all' => array()); + foreach ($templates_raw as $k => $template) { + unset($template['ObjectTemplate']['meta-category']); + $template['ObjectTemplate']['org_name'] = $template['Organisation']['name']; + $templates[$templates_raw[$k]['ObjectTemplate']['meta-category']][] = $template['ObjectTemplate']; + $templates['all'][] = $template['ObjectTemplate']; + } + foreach ($templates as $category => $template_list) { + $templates[$category] = Hash::sort($templates[$category], '{n}.name'); + } + $template_categories = array_keys($templates); + $this->layout = false; + $this->set('template_categories', $template_categories); + $this->set('eventId', $event_id); + $this->set('templates', $templates); + $this->render('ajax/object_choice'); + } + + public function view($id) { + $params = array( + 'recursive' => -1, + 'contain' => array( + 'Organisation' => array('fields' => array('Organisation.id', 'Organisation.name', 'Organisation.uuid')) + ), + 'conditions' => array('ObjectTemplate.id' => $id) + ); + if ($this->_isSiteAdmin()) { + $params['contain']['User']= array('fields' => array('User.id', 'User.email')); + } + $objectTemplate = $this->ObjectTemplate->find('first', $params); + if (empty($objectTemplate)) { + throw new NotFoundException('Invalid object template'); + } + if ($this->_isRest()) { + return $this->RestResponse->viewData($objectTemplate, $this->response->type()); + } else { + $this->set('id', $id); + $this->set('template', $objectTemplate); + } + } + + public function delete($id) { + if (!$this->request->is('post') && !$this->request->is('put') && !$this->request->is('delete')) { + throw new MethodNotAllowedException(); + } + $this->ObjectTemplate->id = $id; + if (!$this->ObjectTemplate->exists()) { + throw new NotFoundException('Invalid ObjectTemplate'); + } + if ($this->ObjectTemplate->delete()) { + if ($this->_isRest()) { + return $this->RestResponse->saveSuccessResponse('ObjectTemplates', 'admin_delete', $id, $this->response->type()); + } else { + $this->Session->setFlash(__('ObjectTemplate deleted')); + } + } + if ($this->_isRest()) { + return $this->RestResponse->saveFailResponse('ObjectTemplates', 'admin_delete', $id, $this->ObjectTemplate->validationErrors, $this->response->type()); + } else { + $this->Session->setFlash('ObjectTemplate could not be deleted'); + } + $this->redirect($this->referer()); + } + + public function viewElements($id, $context = 'all') { + $elements = $this->ObjectTemplate->ObjectTemplateElement->find('all', array( + 'conditions' => array('ObjectTemplateElement.object_template_id' => $id) + )); + $this->set('list', $elements); + $this->layout = 'ajax'; + $this->render('ajax/view_elements'); + } + + public function index($all = false) { + if (!$all || !$this->_isSiteAdmin()) { + $this->paginate['conditions'][] = array('ObjectTemplate.active' => 1); + $this->set('all', false); + } else { + $this->set('all', true); + } + if ($this->_isRest()) { + $rules = $this->paginate; + unset($rules['limit']); + unset($rules['order']); + $objectTemplates = $this->ObjectTemplate->find('all', $rules); + return $this->RestResponse->viewData($objectTemplates, $this->response->type()); + } else { + $this->paginate['order'] = array('ObjectTemplate.name' => 'ASC'); + $objectTemplates = $this->paginate(); + $this->set('list', $objectTemplates); + } + } + + public function update() { + $result = $this->ObjectTemplate->update($this->Auth->user()); + $this->loadModel('ObjectRelationship'); + $result2 = $this->ObjectRelationship->update(); + $this->Log = ClassRegistry::init('Log'); + $fails = 0; + $successes = 0; + if (!empty($result)) { + if (isset($result['success'])) { + foreach ($result['success'] as $id => $success) { + if (isset($success['old'])) $change = $success['name'] . ': updated from v' . $success['old'] . ' to v' . $success['new']; + else $change = $success['name'] . ' v' . $success['new'] . ' installed'; + $this->Log->create(); + $this->Log->save(array( + 'org' => $this->Auth->user('Organisation')['name'], + 'model' => 'ObjectTemplate', + 'model_id' => $id, + 'email' => $this->Auth->user('email'), + 'action' => 'update', + 'user_id' => $this->Auth->user('id'), + 'title' => 'Object template updated', + 'change' => $change, + )); + $successes++; + } + } + if (isset($result['fails'])) { + foreach ($result['fails'] as $id => $fail) { + $this->Log->create(); + $this->Log->save(array( + 'org' => $this->Auth->user('Organisation')['name'], + 'model' => 'ObjectTemplate', + 'model_id' => $id, + 'email' => $this->Auth->user('email'), + 'action' => 'update', + 'user_id' => $this->Auth->user('id'), + 'title' => 'Object template failed to update', + 'change' => $fail['name'] . ' could not be installed/updated. Error: ' . $fail['fail'], + )); + $fails++; + } + } + } else { + $this->Log->create(); + $this->Log->save(array( + 'org' => $this->Auth->user('Organisation')['name'], + 'model' => 'ObjectTemplate', + 'model_id' => 0, + 'email' => $this->Auth->user('email'), + 'action' => 'update', + 'user_id' => $this->Auth->user('id'), + 'title' => 'Object template update (nothing to update)', + 'change' => 'Executed an update of the Object Template library, but there was nothing to update.', + )); + } + if ($successes == 0 && $fails == 0) $this->Session->setFlash('All object templates are up to date already.'); + else if ($successes == 0) $this->Session->setFlash('Could not update any of the object templates'); + else { + $message = 'Successfully updated ' . $successes . ' object templates.'; + if ($fails != 0) $message .= ' However, could not update ' . $fails . ' object templates.'; + $this->Session->setFlash($message); + } + $this->redirect(array('controller' => 'ObjectTemplates', 'action' => 'index')); + } + + public function activate() { + $id = $this->request->data['ObjectTemplate']['data']; + if (!is_numeric($id)) return new CakeResponse(array('body'=> json_encode(array('saved' => false, 'errors' => 'Template not found.')), 'status' => 200, 'type' => 'json')); + $result = $this->ObjectTemplate->setActive($id); + if ($result === false) { + return new CakeResponse(array('body'=> json_encode(array('saved' => false, 'errors' => 'Template\'s state could not be toggeled.')), 'status' => 200, 'type' => 'json')); + } + $message = (($result == 1) ? 'activated' : 'disabled'); + return new CakeResponse(array('body'=> json_encode(array('saved' => true, 'success' => 'Template ' . $message . '.')), 'status' => 200, 'type' => 'json')); + } + + public function getToggleField() { + if (!$this->request->is('ajax')) throw new MethodNotAllowedException('This action is available via AJAX only.'); + $this->layout = 'ajax'; + $this->render('ajax/getToggleField'); + } +} diff --git a/app/Controller/ObjectsController.php b/app/Controller/ObjectsController.php new file mode 100644 index 000000000..8e765719f --- /dev/null +++ b/app/Controller/ObjectsController.php @@ -0,0 +1,518 @@ + 20, + 'order' => array( + 'Object.id' => 'desc' + ), + ); + + public function beforeFilter() { + parent::beforeFilter(); + $this->Security->unlockedActions = array('revise_object', 'get_row'); + } + + public function revise_object($action, $event_id, $template_id, $object_id = false) { + if (!$this->request->is('post') && !$this->request->is('put')) { + throw new MethodNotAllowedException('This action can only be reached via POST requests'); + } + $this->request->data = $this->MispObject->attributeCleanup($this->request->data); + $eventFindParams = array( + 'recursive' => -1, + 'fields' => array('Event.id', 'Event.uuid', 'Event.orgc_id'), + 'conditions' => array('Event.id' => $event_id) + ); + $template = $this->MispObject->ObjectTemplate->find('first', array( + 'conditions' => array('ObjectTemplate.id' => $template_id), + 'recursive' => -1, + 'contain' => array( + 'ObjectTemplateElement' + ) + )); + $event = $this->MispObject->Event->find('first', $eventFindParams); + if (empty($event) || (!$this->_isSiteAdmin() && $event['Event']['orgc_id'] != $this->Auth->user('org_id'))) { + throw new NotFoundException('Invalid event.'); + } + if ($this->request->data['Object']['distribution'] == 4) { + $sg = $this->MispObject->SharingGroup->find('first', array( + 'conditions' => array('SharingGroup.id' => $this->request->data['Object']['sharing_group_id']), + 'recursive' => -1, + 'fields' => array('SharingGroup.id', 'SharingGroup.name'), + 'order' => false + )); + if (empty($sg)) throw new NotFoundException('Invalid sharing group.'); + $this->set('sg', $sg); + } + $this->set('distributionLevels', $this->MispObject->Attribute->distributionLevels); + $this->set('action', $action); + $this->set('template', $template); + $this->set('object_id', $object_id); + $this->set('event', $event); + $this->set('data', $this->request->data); + } + + + /** + * Create an object using a template + * POSTing will take the input and validate it against the template + * GETing will return the template + */ + public function add($eventId, $templateId = false) { + if (!$this->userRole['perm_modify']) { + throw new MethodNotAllowedException('You don\'t have permissions to create objects.'); + } + $eventFindParams = array( + 'recursive' => -1, + 'fields' => array('Event.id', 'Event.uuid', 'Event.orgc_id'), + 'conditions' => array('Event.id' => $eventId) + ); + + // Find the event that is to be updated + if (Validation::uuid($eventId)) { + $eventFindParams['conditions']['Event.uuid'] = $eventId; + } else if (is_numeric($eventId)) { + $eventFindParams['conditions']['Event.id'] = $eventId; + } else { + throw new NotFoundException('Invalid event.'); + } + $event = $this->MispObject->Event->find('first', $eventFindParams); + if (empty($event) || (!$this->_isSiteAdmin() && $event['Event']['orgc_id'] != $this->Auth->user('org_id'))) { + throw new NotFoundException('Invalid event.'); + } + $eventId = $event['Event']['id']; + $templates = $this->MispObject->ObjectTemplate->find('all', array( + 'conditions' => array('ObjectTemplate.id' => $templateId), + 'recursive' => -1, + 'contain' => array( + 'ObjectTemplateElement' + ) + )); + $template_version = false; + $template = false; + foreach ($templates as $temp) { + if (!empty($template_version)) { + if (intval($template['ObjectTemplate']['version']) > intval($template_version)) { + $template = $temp; + } + } else { + $template = $temp; + } + } + $error = false; + if (empty($template)) { + $error = 'No valid template found to edit the object.'; + } + + // If we have received a POST request + if ($this->request->is('post')) { + if (isset($this->request->data['request'])) { + $this->request->data = $this->request->data['request']; + } + if (isset($this->request->data['Object']['data'])) { + $this->request->data = json_decode($this->request->data['Object']['data'], true); + } + if (!isset($this->request->data['Attribute'])) { + $this->request->data = array('Attribute' => $this->request->data); + } + if (!isset($this->request->data['Object'])) { + $attributeTemp = $this->request->data['Attribute']; + unset($this->request->data['Attribute']); + $this->request->data = array('Object' => $this->request->data); + $this->request->data['Attribute'] = $attributeTemp; + unset($attributeTemp); + } + $object = $this->MispObject->attributeCleanup($this->request->data); + // we pre-validate the attributes before we create an object at this point + // This allows us to stop the process and return an error (API) or return + // to the add form + if (empty($object['Attribute'])) { + $error = 'Could not save the object as no attributes were set.'; + } else { + foreach ($object['Attribute'] as $k => $attribute) { + $object['Attribute'][$k]['event_id'] = $eventId; + $this->MispObject->Event->Attribute->set($attribute); + if (!$this->MispObject->Event->Attribute->validates()) { + $error = 'Could not save object as at least one attribute has failed validation (' . $attribute['object_relation'] . '). ' . json_encode($this->MispObject->Event->Attribute->validationErrors); + } + } + } + if (empty($error)) { + $error = $this->MispObject->ObjectTemplate->checkTemplateConformity($template, $object); + if ($error === true) { + $result = $this->MispObject->saveObject($object, $eventId, $template, $this->Auth->user(), $errorBehaviour = 'halt'); + if ($result) $this->MispObject->Event->unpublishEvent($eventId); + } else { + $result = false; + } + if ($this->_isRest()) { + if (is_numeric($result)) { + $object = $this->MispObject->find('first', array( + 'recursive' => -1, + 'conditions' => array('Object.id' => $result), + 'contain' => array('Attribute') + )); + return $this->RestResponse->viewData($object, $this->response->type()); + } else { + return $this->RestResponse->saveFailResponse('Objects', 'add', false, $error, $this->response->type()); + } + } else { + if (is_numeric($result)) { + $this->Session->setFlash('Object saved.'); + $this->redirect(array('controller' => 'events', 'action' => 'view', $eventId)); + } + } + } + } + + // In the case of a GET request or if the object could not be validated, show the form / the requirement + if ($this->_isRest()) { + if ($error) { + + } else { + return $this->RestResponse->viewData($orgs, $this->response->type()); + } + } else { + if (!empty($error)) { + $this->Session->setFlash($error); + } + $template = $this->MispObject->prepareTemplate($template, $this->request->data); + $enabledRows = array_keys($template['ObjectTemplateElement']); + $this->set('enabledRows', $enabledRows); + $distributionData = $this->MispObject->Event->Attribute->fetchDistributionData($this->Auth->user()); + $this->set('distributionData', $distributionData); + $this->set('event', $event); + $this->set('ajax', false); + $this->set('action', 'add'); + $this->set('template', $template); + } + } + + public function get_row($template_id, $object_relation, $k) { + $template = $this->MispObject->ObjectTemplate->find('first', array( + 'conditions' => array('ObjectTemplate.id' => $template_id), + 'recursive' => -1, + 'contain' => array( + 'ObjectTemplateElement' + ) + )); + $template = $this->MispObject->prepareTemplate($template); + $element = array(); + foreach ($template['ObjectTemplateElement'] as $templateElement) { + if ($templateElement['object_relation'] == $object_relation) { + $element = $templateElement; + } + } + $distributionData = $this->MispObject->Event->Attribute->fetchDistributionData($this->Auth->user()); + $this->layout = false; + $this->set('distributionData', $distributionData); + $this->set('k', $k); + $this->set('element', $element); + } + + public function edit($id) { + if (!$this->userRole['perm_modify']) { + throw new MethodNotAllowedException('You don\'t have permissions to edit objects.'); + } + $object = $this->MispObject->find('first', array( + 'conditions' => array('Object.id' => $id), + 'recursive' => -1, + 'contain' => array( + 'Attribute' => array( + 'conditions' => array( + 'Attribute.deleted' => 0 + ) + ) + ) + )); + if (empty($object)) { + throw new NotFoundException('Invalid object.'); + } + $eventFindParams = array( + 'recursive' => -1, + 'fields' => array('Event.id', 'Event.uuid', 'Event.orgc_id'), + 'conditions' => array('Event.id' => $object['Object']['event_id']) + ); + + $event = $this->MispObject->Event->find('first', $eventFindParams); + if (empty($event) || (!$this->_isSiteAdmin() && $event['Event']['orgc_id'] != $this->Auth->user('org_id'))) { + throw new NotFoundException('Invalid object.'); + } + $template = $this->MispObject->ObjectTemplate->find('first', array( + 'conditions' => array( + 'ObjectTemplate.uuid' => $object['Object']['template_uuid'], + 'ObjectTemplate.version' => $object['Object']['template_version'], + ), + 'recursive' => -1, + 'contain' => array( + 'ObjectTemplateElement' + ) + )); + if (empty($template)) { + $this->Session->setFlash('Object cannot be edited, no valid template found.'); + $this->redirect(array('controller' => 'events', 'action' => 'view', $object['Object']['event_id'])); + } + $template = $this->MispObject->prepareTemplate($template, $object); + $enabledRows = false; + + if ($this->request->is('post') || $this->request->is('put')) { + if (isset($this->request->data['request'])) { + $this->request->data = $this->request->data['request']; + } + if (isset($this->request->data['Object']['data'])) { + $this->request->data = json_decode($this->request->data['Object']['data'], true); + } + if (!isset($this->request->data['Attribute'])) { + $this->request->data = array('Attribute' => $this->request->data); + } + $objectToSave = $this->MispObject->attributeCleanup($this->request->data); + $objectToSave = $this->MispObject->deltaMerge($object, $objectToSave); + // we pre-validate the attributes before we create an object at this point + // This allows us to stop the process and return an error (API) or return + // to the add form + if (empty($error)) { + if ($this->_isRest()) { + if (is_numeric($objectToSave)) { + $objectToSave = $this->MispObject->find('first', array( + 'recursive' => -1, + 'conditions' => array('Object.id' => $result), + 'contain' => array('Attribute') + )); + $this->MispObject->Event->unpublishEvent($object['Object']['event_id']); + return $this->RestResponse->viewData($objectToSave, $this->response->type()); + } else { + return $this->RestResponse->saveFailResponse('Objects', 'add', false, $result, $this->response->type()); + } + } else { + $this->MispObject->Event->unpublishEvent($object['Object']['event_id']); + $this->Session->setFlash('Object saved.'); + $this->redirect(array('controller' => 'events', 'action' => 'view', $object['Object']['event_id'])); + } + } + } else { + $enabledRows = array(); + $this->request->data['Object'] = $object['Object']; + foreach ($template['ObjectTemplateElement'] as $k => $element) { + foreach ($object['Attribute'] as $k2 => $attribute) { + if ($attribute['object_relation'] == $element['object_relation']) { + $enabledRows[] = $k; + $this->request->data['Attribute'][$k] = $attribute; + if (!empty($element['values_list'])) { + $this->request->data['Attribute'][$k]['value_select'] = $attribute['value']; + } else { + if (!empty($element['sane_default'])) { + if (in_array($attribute['value'], $element['sane_default'])) { + $this->request->data['Attribute'][$k]['value_select'] = $attribute['value']; + } else { + $this->request->data['Attribute'][$k]['value_select'] = 'Enter value manually'; + } + } + } + } + } + } + + } + $this->set('enabledRows', $enabledRows); + $distributionData = $this->MispObject->Event->Attribute->fetchDistributionData($this->Auth->user()); + $this->set('distributionData', $distributionData); + $this->set('event', $event); + $this->set('ajax', false); + $this->set('template', $template); + $this->set('action', 'edit'); + $this->set('object', $object); + $this->render('add'); + } + + public function addValueField() { + + } + + public function delete($id, $hard = false) { + if (!$this->userRole['perm_modify']) { + throw new MethodNotAllowedException('You don\'t have permissions to delete objects.'); + } + if (Validation::uuid($id)) { + $lookupField = 'uuid'; + } else if (!is_numeric($id)) { + $lookupField = 'id'; + throw new NotFoundException('Invalid object.'); + } + $object = $this->MispObject->find('first', array( + 'recursive' => -1, + 'fields' => array('Object.id', 'Object.event_id', 'Event.id', 'Event.uuid', 'Event.orgc_id'), + 'conditions' => array('Object.id' => $id), + 'contain' => array( + 'Event' + ) + )); + if (empty($object)) { + throw new NotFoundException('Invalid event.'); + } + $eventId = $object['Event']['id']; + if (!$this->_isSiteAdmin() && ($object['Event']['orgc_id'] != $this->Auth->user('org_id') || !$this->userRole['perm_modify'])) { + throw new UnauthorizedException('You do not have permission to do that.'); + } + if ($this->request->is('post')) { + if ($this->__delete($id, $hard)) { + $message = 'Object deleted.'; + if ($this->request->is('ajax')) { + return new CakeResponse( + array( + 'body'=> json_encode( + array( + 'saved' => true, + 'success' => $message + ) + ), + 'status'=>200, + 'type' => 'json' + ) + ); + } else if ($this->_isRest()) { + return $this->RestResponse->saveSuccessResponse( + 'Objects', + 'delete', + $id, + $this->response->type() + ); + } else { + $this->Session->setFlash($message); + $this->redirect(array('controller' => 'events', 'action' => 'view', $object['Event']['id'])); + } + } else { + $message = 'Object could not be deleted.'; + if ($this->request->is('ajax')) { + return new CakeResponse( + array( + 'body'=> json_encode( + array( + 'saved' => false, + 'errors' => $message + ) + ), + 'status'=>200, + 'type' => 'json' + ) + ); + } else if ($this->_isRest()) { + return $this->RestResponse->saveFailResponse( + 'Objects', + 'delete', + false, + $this->MispObject->validationErrors, + $this->response->type() + ); + } else { + $this->Session->setFlash($message); + $this->redirect(array('controller' => 'events', 'action' => 'view', $object['Event']['id'])); + } + } + } else { + if ($this->request->is('ajax') && $this->request->is('get')) { + $this->set('hard', $hard); + $this->set('id', $id); + $this->set('event_id', $object['Event']['id']); + $this->render('ajax/delete'); + } + } + } + + private function __delete($id, $hard) { + $this->MispObject->id = $id; + if (!$this->MispObject->exists()) { + return false; + } + $object = $this->MispObject->find('first', array( + 'conditions' => array('Object.id' => $id), + 'fields' => array('Object.*'), + 'contain' => array( + 'Event' => array( + 'fields' => array('Event.*') + ), + 'Attribute' => array( + 'fields' => array('Attribute.*') + ) + ), + )); + if (empty($object)) throw new MethodNotAllowedException('Object not found or not authorised.'); + + // check for permissions + if (!$this->_isSiteAdmin()) { + if ($object['Event']['locked']) { + if ($this->Auth->user('org_id') != $object['Event']['org_id'] || !$this->userRole['perm_sync']) { + throw new MethodNotAllowedException('Object not found or not authorised.'); + } + } else { + if ($this->Auth->user('org_id') != $object['Event']['orgc_id']) { + throw new MethodNotAllowedException('Object not found or not authorised.'); + } + } + } + $date = new DateTime(); + if ($hard) { + // For a hard delete, simply run the delete, it will cascade + $this->MispObject->delete($id); + return true; + } else { + // For soft deletes, sanitise the object first if the setting is enabled + if (Configure::read('Security.sanitise_attribute_on_delete')) { + $object['Object']['name'] = 'N/A'; + $object['Object']['category'] = 'N/A'; + $object['Object']['description'] = 'N/A'; + $object['Object']['template_uuid'] = 'N/A'; + $object['Object']['template_version'] = 0; + $object['Object']['comment'] = ''; + } + $object['Object']['deleted'] = 1; + $object['Object']['timestamp'] = $date->getTimestamp(); + $this->MispObject->save($object); + foreach ($object['Attribute'] as $attribute) { + if (Configure::read('Security.sanitise_attribute_on_delete')) { + $attribute['category'] = 'Other'; + $attribute['type'] = 'comment'; + $attribute['value'] = 'deleted'; + $attribute['comment'] = ''; + $attribute['to_ids'] = 0; + } + $attribute['deleted'] = 1; + $attribute['timestamp'] = $date->getTimestamp(); + $this->MispObject->Attribute->save(array('Attribute' => $attribute)); + $this->MispObject->Event->ShadowAttribute->deleteAll( + array('ShadowAttribute.old_id' => $attribute['id']), + false + ); + } + $object['Event']['timestamp'] = $date->getTimestamp(); + $object['Event']['published'] = 0; + $this->MispObject->Event->save($object, array('fieldList' => array('published', 'timestamp', 'info'))); + $object_refs = $this->MispObject->ObjectReference->find('all', array( + 'conditions' => array( + 'ObjectReference.referenced_type' => 1, + 'ObjectReference.referenced_id' => $id, + ), + 'recursive' => -1 + )); + foreach ($object_refs as $ref) { + $ref['ObjectReference']['deleted'] = 1; + $this->MispObject->ObjectReference->save($ref); + } + return true; + } + } + + public function view($id) { + if ($this->_isRest()) { + $objects = $this->MispObject->fetchObjects($this->Auth->user(), array('conditions' => array('Object.id' => $id))); + if (!empty($objects)) { + return $this->RestResponse->viewData($objects, $this->response->type()); + } + } + } +} diff --git a/app/Controller/ServersController.php b/app/Controller/ServersController.php index af0e1ffff..c5eb2c75c 100644 --- a/app/Controller/ServersController.php +++ b/app/Controller/ServersController.php @@ -123,11 +123,13 @@ class ServersController extends AppController { $this->loadModel('Event'); $dataForView = array( 'Attribute' => array('attrDescriptions' => 'fieldDescriptions', 'distributionDescriptions' => 'distributionDescriptions', 'distributionLevels' => 'distributionLevels'), - 'Event' => array('eventDescriptions' => 'fieldDescriptions', 'analysisLevels' => 'analysisLevels') + 'Event' => array('eventDescriptions' => 'fieldDescriptions', 'analysisLevels' => 'analysisLevels'), + 'Object' => array() ); foreach ($dataForView as $m => $variables) { if ($m === 'Event') $currentModel = $this->Event; else if ($m === 'Attribute') $currentModel = $this->Event->Attribute; + else if ($m === 'Object') $currentModel = $this->Event->Object; foreach ($variables as $alias => $variable) { $this->set($alias, $currentModel->{$variable}); } @@ -762,6 +764,13 @@ class ServersController extends AppController { if ($tab == 'diagnostics' || $tab == 'download') { $php_ini = php_ini_loaded_file(); $this->set('php_ini', $php_ini); + $advanced_attachments = shell_exec('python ' . APP . 'files/scripts/generate_file_objects.py -c'); + try { + $advanced_attachments = json_decode($advanced_attachments, true); + } catch (Exception $e) { + $advanced_attachments = false; + } + $this->set('advanced_attachments', $advanced_attachments); // check if the current version of MISP is outdated or not $version = $this->__checkVersion(); $this->set('version', $version); diff --git a/app/Controller/ShadowAttributesController.php b/app/Controller/ShadowAttributesController.php index e8f4bd3e7..d18c573bc 100644 --- a/app/Controller/ShadowAttributesController.php +++ b/app/Controller/ShadowAttributesController.php @@ -662,6 +662,10 @@ class ShadowAttributesController extends AppController { 'static' => array('old_id' => 'Attribute.id', 'uuid' => 'Attribute.uuid', 'event_id' => 'Attribute.event_id', 'event_uuid' => 'Event.uuid', 'event_org_id' => 'Event.orgc_id'), 'optional' => array('category', 'type', 'value', 'to_ids', 'comment') ); + if ($existingAttribute['Attribute']['object_id']) { + unset($fields['optional']['type']); + $fields['static']['type'] = 'Attribute.type'; + } } foreach ($fields['static'] as $k => $v) { $v = explode('.', $v); @@ -724,18 +728,36 @@ class ShadowAttributesController extends AppController { unset($types[$key]); } } + if ($existingAttribute['Attribute']['object_id']) { + $this->set('objectAttribute', true); + } else { + $this->set('objectAttribute', false); + } $types = $this->_arrayToValuesIndexArray($types); $this->set('types', $types); // combobox for categories $categories = $this->_arrayToValuesIndexArray(array_keys($this->ShadowAttribute->Event->Attribute->categoryDefinitions)); $categories = $this->_arrayToValuesIndexArray($categories); - $this->set('categories', $categories); foreach ($this->ShadowAttribute->Event->Attribute->categoryDefinitions as $key => $value) { $info['category'][$key] = array('key' => $key, 'desc' => isset($value['formdesc'])? $value['formdesc'] : $value['desc']); } foreach ($this->ShadowAttribute->Event->Attribute->typeDefinitions as $key => $value) { $info['type'][$key] = array('key' => $key, 'desc' => isset($value['formdesc'])? $value['formdesc'] : $value['desc']); } + $categoryDefinitions = $this->ShadowAttribute->Event->Attribute->categoryDefinitions; + if ($existingAttribute['Attribute']['object_id']) { + foreach ($categoryDefinitions as $k => $v) { + if (!in_array($existingAttribute['Attribute']['type'], $v['types'])) { + unset($categoryDefinitions[$k]); + } + } + foreach ($categories as $k => $v) { + if (!isset($categoryDefinitions[$k])) { + unset($categories[$k]); + } + } + } + $this->set('categories', $categories); $this->set('info', $info); $this->set('attrDescriptions', $this->ShadowAttribute->fieldDescriptions); $this->set('typeDefinitions', $this->ShadowAttribute->typeDefinitions); diff --git a/app/Lib/Tools/CustomPaginationTool.php b/app/Lib/Tools/CustomPaginationTool.php index 45148d5f5..bf62f4018 100644 --- a/app/Lib/Tools/CustomPaginationTool.php +++ b/app/Lib/Tools/CustomPaginationTool.php @@ -1,7 +1,7 @@ $model, 'current' => 1, @@ -16,7 +16,7 @@ class CustomPaginationTool { 'options' => array( ), ); - $validOptions = array('sort', 'direction', 'page'); + $validOptions = array('sort', 'direction', 'page', 'focus'); if ($model == 'events') $validOptions[] = 'attributeFilter'; foreach ($validOptions as $v) { if (isset($options[$v])) { @@ -43,20 +43,35 @@ class CustomPaginationTool { $items = array_slice($items, $params['current'] - 1, $params['limit']); } - function applyRulesOnArray(&$items, $options, $model, $sort = 'id') { - $params = $this->createPaginationRules($items, $options, $model, $sort); + function applyRulesOnArray(&$items, $options, $model, $sort = 'id', $focusKey = 'uuid') { + $params = $this->createPaginationRules($items, $options, $model, $sort, $focusKey); if (isset($params['sort'])) { $sortArray = array(); foreach ($items as $k => $item) { - $sortArray[$k] = $item[$params['sort']]; + $sortArray[$k] = empty($item[$params['sort']]) ? '' : $item[$params['sort']]; } - asort($sortArray); + if (empty($params['options']['direction']) || $params['options']['direction'] == 'asc') { + asort($sortArray); + } else { + arsort($sortArray); + } + foreach ($sortArray as $k => $sortedElement) { $sortArray[$k] = $items[$k]; } $items = array(); $items = $sortArray; - //$items = Set::sort($items, '{n}.' . $params['sort'], $params['direction']); + } + $items = array_values($items); + if (!empty($params['options']['focus'])) { + foreach ($items as $k => $item) { + if ($item[$focusKey] == $params['options']['focus']) { + $params['page'] = 1 + intval(floor($k / $params['limit'])); + $params['current'] = 1 + ($params['page'] - 1) * 60; + continue; + } + } + unset($params['options']['focus']); } array_unshift($items, 'dummy'); unset($items[0]); diff --git a/app/Lib/Tools/JSONConverterTool.php b/app/Lib/Tools/JSONConverterTool.php index 83d0f36f2..582bb6c1f 100644 --- a/app/Lib/Tools/JSONConverterTool.php +++ b/app/Lib/Tools/JSONConverterTool.php @@ -10,7 +10,7 @@ class JSONConverterTool { } public function convert($event, $isSiteAdmin=false) { - $toRearrange = array('Org', 'Orgc', 'SharingGroup', 'Attribute', 'ShadowAttribute', 'RelatedAttribute', 'RelatedEvent', 'Galaxy'); + $toRearrange = array('Org', 'Orgc', 'SharingGroup', 'Attribute', 'ShadowAttribute', 'RelatedAttribute', 'RelatedEvent', 'Galaxy', 'Object'); foreach ($toRearrange as $object) { if (isset($event[$object])) { $event['Event'][$object] = $event[$object]; @@ -49,29 +49,12 @@ class JSONConverterTool { } if (isset($event['Event']['Attribute'])) { - // remove value1 and value2 from the output and remove invalid utf8 characters for the xml parser - foreach ($event['Event']['Attribute'] as $key => $value) { - if (isset($value['SharingGroup']) && empty($value['SharingGroup'])) { - unset($event['Event']['Attribute'][$key]['SharingGroup']); - } - unset($event['Event']['Attribute'][$key]['value1']); - unset($event['Event']['Attribute'][$key]['value2']); - unset($event['Event']['Attribute'][$key]['category_order']); - if (isset($event['RelatedAttribute'][$value['id']])) { - $event['Event']['Attribute'][$key]['RelatedAttribute'] = $event['Event']['RelatedAttribute'][$value['id']]; - foreach ($event['Event']['Attribute'][$key]['RelatedAttribute'] as &$ra) { - $ra = array('Attribute' => $ra); - } - } - if (isset($event['Event']['Attribute'][$key]['AttributeTag'])) { - foreach ($event['Event']['Attribute'][$key]['AttributeTag'] as $atk => $tag) { - unset($tag['Tag']['org_id']); - $event['Event']['Attribute'][$key]['Tag'][$atk] = $tag['Tag']; - } - unset($event['Event']['Attribute'][$key]['AttributeTag']); - } - } + $event['Event']['Attribute'] = $this->__cleanAttributes($event['Event']['Attribute']); } + if (isset($event['Event']['Object'])) { + $event['Event']['Object'] = $this->__cleanObjects($event['Event']['Object']); + } + unset($event['Event']['RelatedAttribute']); if (isset($event['Event']['RelatedEvent'])) { foreach ($event['Event']['RelatedEvent'] as $key => $value) { @@ -87,6 +70,44 @@ class JSONConverterTool { return json_encode($result, JSON_PRETTY_PRINT); } + private function __cleanAttributes($attributes) { + // remove value1 and value2 from the output and remove invalid utf8 characters for the xml parser + foreach ($attributes as $key => $value) { + if (isset($value['SharingGroup']) && empty($value['SharingGroup'])) { + unset($attributes[$key]['SharingGroup']); + } + unset($attributes[$key]['value1']); + unset($attributes[$key]['value2']); + unset($attributes[$key]['category_order']); + if (isset($event['RelatedAttribute'][$value['id']])) { + $attributes[$key]['RelatedAttribute'] = $event['Event']['RelatedAttribute'][$value['id']]; + foreach ($attributes[$key]['RelatedAttribute'] as &$ra) { + $ra = array('Attribute' => $ra); + } + } + if (isset($attributes[$key]['AttributeTag'])) { + foreach ($attributes[$key]['AttributeTag'] as $atk => $tag) { + unset($tag['Tag']['org_id']); + $attributes[$key]['Tag'][$atk] = $tag['Tag']; + } + unset($attributes[$key]['AttributeTag']); + } + } + return $attributes; + } + + private function __cleanObjects($objects) { + foreach ($objects as $k => $object) { + if (!empty($object['Attribute'])) { + $objects[$k]['Attribute'] = $this->__cleanAttributes($object['Attribute']); + } else { + unset($objects[$k]); + } + } + $objects = array_values($objects); + return $objects; + } + public function arrayPrinter($array, $root = true) { if (is_array($array)) { $resultArray = array(); diff --git a/app/Lib/Tools/XMLConverterTool.php b/app/Lib/Tools/XMLConverterTool.php index 499081218..a493dd8fd 100644 --- a/app/Lib/Tools/XMLConverterTool.php +++ b/app/Lib/Tools/XMLConverterTool.php @@ -35,6 +35,65 @@ class XMLConverterTool { return $text; } + private function __rearrange($data, $model, $container) { + if (isset($data[$model])) { + $data[$container][$model] = $data[$model]; + unset($data[$model]); + } + return $data; + } + + private function __rearrangeAttributes($attributes) { + foreach ($attributes as $key => $value) { + $this->__sanitizeField($attributes[$key]['value']); + $this->__sanitizeField($attributes[$key]['comment']); + unset($attributes[$key]['value1'], $attributes[$key]['value2'], $attributes[$key]['category_order']); + if (isset($event['Event']['RelatedAttribute']) && isset($event['Event']['RelatedAttribute'][$value['id']])) { + $attributes[$key]['RelatedAttribute'] = $event['Event']['RelatedAttribute'][$value['id']]; + foreach ($attributes[$key]['RelatedAttribute'] as &$ra) { + $ra = array('Attribute' => array(0 => $ra)); + } + } + if (!empty($attributes[$key]['Feed'])) { + foreach ($attributes[$key]['Feed'] as $fKey => $feed) { + $this->__sanitizeField($attributes[$key]['Feed'][$fKey]['name']); + $this->__sanitizeField($attributes[$key]['Feed'][$fKey]['url']); + $this->__sanitizeField($attributes[$key]['Feed'][$fKey]['provider']); + } + } + if (isset($attributes[$key]['ShadowAttribute'])) { + foreach ($attributes[$key]['ShadowAttribute'] as $skey => $svalue) { + $this->__sanitizeField($attributes[$key]['ShadowAttribute'][$skey]['value']); + $this->__sanitizeField($attributes[$key]['ShadowAttribute'][$skey]['comment']); + $attributes[$key]['ShadowAttribute'][$skey]['Org'] = array(0 => $attributes[$key]['ShadowAttribute'][$skey]['Org']); + if (isset($attributes[$key]['ShadowAttribute'][$skey]['EventOrg'])) $attributes[$key]['ShadowAttribute'][$skey]['EventOrg'] = array(0 => $attributes[$key]['ShadowAttribute'][$skey]['EventOrg']); + } + } + if (isset($attributes[$key]['SharingGroup']['SharingGroupOrg'])) { + foreach ($attributes[$key]['SharingGroup']['SharingGroupOrg'] as $k => $sgo) { + $attributes[$key]['SharingGroup']['SharingGroupOrg'][$k]['Organisation'] = array(0 => $attributes[$key]['SharingGroup']['SharingGroupOrg'][$k]['Organisation']); + } + } + if (isset($attributes[$key]['SharingGroup']['SharingGroupServer'])) { + foreach ($attributes[$key]['SharingGroup']['SharingGroupServer'] as $k => $sgs) { + $attributes[$key]['SharingGroup']['SharingGroupServer'][$k]['Server'] = array(0 => $attributes[$key]['SharingGroup']['SharingGroupServer'][$k]['Server']); + } + } + if (isset($attributes[$key]['SharingGroup'])) { + $attributes[$key]['SharingGroup'][0] = $attributes[$key]['SharingGroup']; + unset($attributes[$key]['SharingGroup']); + } + if (isset($attributes[$key]['AttributeTag'])) { + foreach ($attributes[$key]['AttributeTag'] as $atk => $tag) { + unset($tag['Tag']['org_id']); + $attributes[$key]['Tag'][$atk] = $tag['Tag']; + } + unset($attributes[$key]['AttributeTag']); + } + } + return $attributes; + } + public function convertArray($event, $isSiteAdmin=false) { $event['Event']['Org'][0] = $event['Org']; $event['Event']['Orgc'][0] = $event['Orgc']; @@ -51,12 +110,11 @@ class XMLConverterTool { if (isset($event['SharingGroup'])) { $event['Event']['SharingGroup'][0] = $event['SharingGroup']; } - if (isset($event['Attribute'])) $event['Event']['Attribute'] = $event['Attribute']; - if (isset($event['ShadowAttribute'])) { - $event['Event']['ShadowAttribute'] = $event['ShadowAttribute']; - unset($event['ShadowAttribute']); - } - if (isset($event['RelatedEvent'])) if (isset($event['RelatedEvent'])) $event['Event']['RelatedEvent'] = $event['RelatedEvent']; + $event = $this->__rearrange($event, 'Attribute', 'Event'); + $event = $this->__rearrange($event, 'Object', 'Event'); + $event = $this->__rearrange($event, 'ShadowAttribute', 'Event'); + $event = $this->__rearrange($event, 'RelatedEvent', 'Event'); + $event = $this->__rearrange($event, 'RelatedAttribute', 'Event'); // legacy unset($event['Event']['org']); @@ -68,12 +126,6 @@ class XMLConverterTool { $event['Event']['Tag'][$k] = $tag['Tag']; } } - $this->__sanitizeField($event['Event']['info']); - if (isset($event['RelatedAttribute'])) { - $event['Event']['RelatedAttribute'] = $event['RelatedAttribute']; - unset($event['RelatedAttribute']); - } - else $event['Event']['RelatedAttribute'] = array(); foreach ($event['Event']['RelatedAttribute'] as &$attribute_w_relation) { foreach ($attribute_w_relation as &$relation) { $this->__sanitizeField($relation['info']); @@ -91,55 +143,13 @@ class XMLConverterTool { if (isset($event['Event']['Attribute'])) { // remove value1 and value2 from the output and remove invalid utf8 characters for the xml parser - foreach ($event['Event']['Attribute'] as $key => $value) { - $this->__sanitizeField($event['Event']['Attribute'][$key]['value']); - $this->__sanitizeField($event['Event']['Attribute'][$key]['comment']); - unset($event['Event']['Attribute'][$key]['value1'], $event['Event']['Attribute'][$key]['value2'], $event['Event']['Attribute'][$key]['category_order']); - if (isset($event['Event']['RelatedAttribute']) && isset($event['Event']['RelatedAttribute'][$value['id']])) { - $event['Event']['Attribute'][$key]['RelatedAttribute'] = $event['Event']['RelatedAttribute'][$value['id']]; - foreach ($event['Event']['Attribute'][$key]['RelatedAttribute'] as &$ra) { - $ra = array('Attribute' => array(0 => $ra)); - } - } - if (!empty($event['Event']['Attribute'][$key]['Feed'])) { - foreach ($event['Event']['Attribute'][$key]['Feed'] as $fKey => $feed) { - $this->__sanitizeField($event['Event']['Attribute'][$key]['Feed'][$fKey]['name']); - $this->__sanitizeField($event['Event']['Attribute'][$key]['Feed'][$fKey]['url']); - $this->__sanitizeField($event['Event']['Attribute'][$key]['Feed'][$fKey]['provider']); - } - } - if (isset($event['Event']['Attribute'][$key]['ShadowAttribute'])) { - foreach ($event['Event']['Attribute'][$key]['ShadowAttribute'] as $skey => $svalue) { - $this->__sanitizeField($event['Event']['Attribute'][$key]['ShadowAttribute'][$skey]['value']); - $this->__sanitizeField($event['Event']['Attribute'][$key]['ShadowAttribute'][$skey]['comment']); - $event['Event']['Attribute'][$key]['ShadowAttribute'][$skey]['Org'] = array(0 => $event['Event']['Attribute'][$key]['ShadowAttribute'][$skey]['Org']); - if (isset($event['Event']['Attribute'][$key]['ShadowAttribute'][$skey]['EventOrg'])) $event['Event']['Attribute'][$key]['ShadowAttribute'][$skey]['EventOrg'] = array(0 => $event['Event']['Attribute'][$key]['ShadowAttribute'][$skey]['EventOrg']); - } - } - if (isset($event['Event']['Attribute'][$key]['SharingGroup']['SharingGroupOrg'])) { - foreach ($event['Event']['Attribute'][$key]['SharingGroup']['SharingGroupOrg'] as $k => $sgo) { - $event['Event']['Attribute'][$key]['SharingGroup']['SharingGroupOrg'][$k]['Organisation'] = array(0 => $event['Event']['Attribute'][$key]['SharingGroup']['SharingGroupOrg'][$k]['Organisation']); - } - } - if (isset($event['Event']['Attribute'][$key]['SharingGroup']['SharingGroupServer'])) { - foreach ($event['Event']['Attribute'][$key]['SharingGroup']['SharingGroupServer'] as $k => $sgs) { - $event['Event']['Attribute'][$key]['SharingGroup']['SharingGroupServer'][$k]['Server'] = array(0 => $event['Event']['Attribute'][$key]['SharingGroup']['SharingGroupServer'][$k]['Server']); - } - } - if (isset($event['Event']['Attribute'][$key]['SharingGroup'])) { - $event['Event']['Attribute'][$key]['SharingGroup'][0] = $event['Event']['Attribute'][$key]['SharingGroup']; - unset($event['Event']['Attribute'][$key]['SharingGroup']); - } - if (isset($event['Event']['Attribute'][$key]['AttributeTag'])) { - foreach ($event['Event']['Attribute'][$key]['AttributeTag'] as $atk => $tag) { - unset($tag['Tag']['org_id']); - $event['Event']['Attribute'][$key]['Tag'][$atk] = $tag['Tag']; - } - unset($event['Event']['Attribute'][$key]['AttributeTag']); - } + $event['Event']['Attribute'] = $this->__rearrangeAttributes($event['Event']['Attribute']); + } + if (!empty($event['Event']['Object'])) { + foreach ($event['Event']['Object'] as $k => $v) { + $event['Event']['Object'][$k]['Attribute'] = $this->__rearrangeAttributes($event['Event']['Object'][$k]['Attribute']); } } - unset($event['Event']['RelatedAttribute']); if (isset($event['Event']['ShadowAttribute'])) { // remove invalid utf8 characters for the xml parser foreach ($event['Event']['ShadowAttribute'] as $key => $value) { @@ -189,4 +199,8 @@ class XMLConverterTool { if ($mispVersion) $result .= '' . $mispVersion . ''; return $result . '' . PHP_EOL; } + + private function __prepareAttributes($attributes) { + return $attributes; + } } diff --git a/app/Lib/cakephp b/app/Lib/cakephp index cfcbd6ea3..c3a612aa9 160000 --- a/app/Lib/cakephp +++ b/app/Lib/cakephp @@ -1 +1 @@ -Subproject commit cfcbd6ea31cd6b6216fb14ecf47b166a19a6c703 +Subproject commit c3a612aa94d30a4c51653f40f55ce07177300307 diff --git a/app/Model/AppModel.php b/app/Model/AppModel.php index 7aa724aeb..032164a74 100644 --- a/app/Model/AppModel.php +++ b/app/Model/AppModel.php @@ -47,7 +47,7 @@ class AppModel extends Model { 58 => false, 59 => false, 60 => false, 61 => false, 62 => false, 63 => false, 64 => false, 65 => false, 66 => false, 67 => true, 68 => false, 69 => false, 71 => false, 72 => false, 73 => false, - 75 => false, 77 => false, 78 => false + 75 => false, 77 => false, 78 => false, 79 => false, 80 => false ) ) ); @@ -708,6 +708,115 @@ class AppModel extends Model { $this->__addIndex('galaxy_clusters', 'galaxy_id'); $this->__addIndex('galaxy_elements', 'galaxy_cluster_id'); break; + case '2.4.80': + $sqlArray[] = "CREATE TABLE IF NOT EXISTS objects ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci, + `meta-category` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci, + `description` text CHARACTER SET utf8 COLLATE utf8_unicode_ci, + `template_uuid` varchar(40) COLLATE utf8_bin DEFAULT NULL, + `template_version` int(11) NOT NULL, + `event_id` int(11) NOT NULL, + `uuid` varchar(40) COLLATE utf8_bin DEFAULT NULL, + `timestamp` int(11) NOT NULL DEFAULT 0, + `distribution` tinyint(4) NOT NULL DEFAULT 0, + `sharing_group_id` int(11), + `comment` text COLLATE utf8_bin NOT NULL, + `deleted` TINYINT(1) NOT NULL DEFAULT 0, + PRIMARY KEY (id), + INDEX `name` (`name`), + INDEX `template_uuid` (`template_uuid`), + INDEX `template_version` (`template_version`), + INDEX `meta-category` (`meta-category`), + INDEX `event_id` (`event_id`), + INDEX `uuid` (`uuid`), + INDEX `timestamp` (`timestamp`), + INDEX `distribution` (`distribution`), + INDEX `sharing_group_id` (`sharing_group_id`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8;"; + + $sqlArray[] = "CREATE TABLE IF NOT EXISTS object_references ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `uuid` varchar(40) COLLATE utf8_bin DEFAULT NULL, + `timestamp` int(11) NOT NULL DEFAULT 0, + `object_id` int(11) NOT NULL, + `event_id` int(11) NOT NULL, + `object_uuid` varchar(40) COLLATE utf8_bin DEFAULT NULL, + `referenced_uuid` varchar(40) COLLATE utf8_bin DEFAULT NULL, + `referenced_id` int(11) NOT NULL, + `referenced_type` int(11) NOT NULL DEFAULT 0, + `relationship_type` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci, + `comment` text COLLATE utf8_bin NOT NULL, + `deleted` TINYINT(1) NOT NULL DEFAULT 0, + PRIMARY KEY (id), + INDEX `object_uuid` (`object_uuid`), + INDEX `referenced_uuid` (`referenced_uuid`), + INDEX `timestamp` (`timestamp`), + INDEX `object_id` (`object_id`), + INDEX `referenced_id` (`referenced_id`), + INDEX `relationship_type` (`relationship_type`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8;"; + + $sqlArray[] = "CREATE TABLE IF NOT EXISTS object_relationships ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `version` int(11) NOT NULL, + `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci, + `description` text COLLATE utf8_bin NOT NULL, + `format` text COLLATE utf8_bin NOT NULL, + PRIMARY KEY (id), + INDEX `name` (`name`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8;"; + + + $sqlArray[] = "CREATE TABLE IF NOT EXISTS object_templates ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `user_id` int(11) NOT NULL, + `org_id` int(11) NOT NULL, + `uuid` varchar(40) COLLATE utf8_bin DEFAULT NULL, + `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci, + `meta-category` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci, + `description` text COLLATE utf8_bin, + `version` int(11) NOT NULL, + `requirements` text COLLATE utf8_bin, + `fixed` tinyint(1) NOT NULL DEFAULT 0, + `active` tinyint(1) NOT NULL DEFAULT 0, + PRIMARY KEY (id), + INDEX `user_id` (`user_id`), + INDEX `org_id` (`org_id`), + INDEX `uuid` (`uuid`), + INDEX `name` (`name`), + INDEX `meta-category` (`meta-category`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8;"; + + $sqlArray[] = "CREATE TABLE IF NOT EXISTS object_template_elements ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `object_template_id` int(11) NOT NULL, + `object_relation` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci, + `type` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci, + `ui-priority` int(11) NOT NULL, + `categories` text COLLATE utf8_bin, + `sane_default` text COLLATE utf8_bin, + `values_list` text COLLATE utf8_bin, + `description` text COLLATE utf8_bin, + `disable_correlations` tinyint(1) NOT NULL DEFAULT 0, + `multiple` tinyint(1) NOT NULL DEFAULT 0, + PRIMARY KEY (id), + INDEX `object_relation` (`object_relation`), + INDEX `type` (`type`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8;"; + + $sqlArray[] = 'ALTER TABLE `logs` CHANGE `model` `model` VARCHAR(80) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL;'; + $sqlArray[] = 'ALTER TABLE `logs` CHANGE `action` `action` VARCHAR(80) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL;'; + + $sqlArray[] = 'ALTER TABLE attributes ADD object_id int(11) NOT NULL DEFAULT 0;'; + $sqlArray[] = 'ALTER TABLE attributes ADD object_relation varchar(255) COLLATE utf8_bin;'; + + $sqlArray[] = "ALTER TABLE `roles` ADD `perm_object_template` tinyint(1) NOT NULL DEFAULT 0;"; + $sqlArray[] = 'UPDATE `roles` SET `perm_object_template` = 1 WHERE `perm_site_admin` = 1;'; + + $indexArray[] = array('attributes', 'object_id'); + $indexArray[] = array('attributes', 'object_relation'); + break; case 'fixNonEmptySharingGroupID': $sqlArray[] = 'UPDATE `events` SET `sharing_group_id` = 0 WHERE `distribution` != 4;'; $sqlArray[] = 'UPDATE `attributes` SET `sharing_group_id` = 0 WHERE `distribution` != 4;'; diff --git a/app/Model/Attribute.php b/app/Model/Attribute.php index 9281a9964..51255437b 100644 --- a/app/Model/Attribute.php +++ b/app/Model/Attribute.php @@ -488,6 +488,10 @@ class Attribute extends AppModel { 'SharingGroup' => array( 'className' => 'SharingGroup', 'foreignKey' => 'sharing_group_id' + ), + 'Object' => array( + 'className' => 'MispObject', + 'foreignKey' => 'object_id' ) ); @@ -538,8 +542,6 @@ class Attribute extends AppModel { } } - if ($this->data['Attribute']['distribution'] != 4) $this->data['Attribute']['sharing_group_id'] = 0; - // update correlation... (only needed here if there's an update) if ($this->id || !empty($this->data['Attribute']['id'])) { $this->__beforeSaveCorrelation($this->data['Attribute']); @@ -596,6 +598,15 @@ class Attribute extends AppModel { if (Configure::read('MISP.enable_advanced_correlations') && in_array($this->data['Attribute']['type'], array('ip-src', 'ip-dst', 'domain-ip')) && strpos($this->data['Attribute']['value'], '/')) { $this->setCIDRList(); } + if (!empty($this->data['Attribute']['id'])) { + $this->Object->ObjectReference->deleteAll( + array( + 'ObjectReference.referenced_type' => 0, + 'ObjectReference.referenced_id' => $this->data['Attribute']['id'], + ), + false + ); + } } public function beforeValidate($options = array()) { @@ -635,9 +646,24 @@ class Attribute extends AppModel { $this->data['Attribute']['value'] = $result; } - // TODO: add explanatory comment - if (!isset($this->data['Attribute']['distribution']) || $this->data['Attribute']['distribution'] != 4) $this->data['Attribute']['sharing_group_id'] = 0; - if (!isset($this->data['Attribute']['distribution'])) $this->data['Attribute']['distribution'] = 5; + // Set defaults for when some of the mandatory fields don't have defaults + // These fields all have sane defaults either based on another field, or due to server settings + if (!isset($this->data['Attribute']['distribution'])) { + $this->data['Attribute']['distribution'] = Configure::read('MISP.default_attribute_distribution'); + if ($this->data['Attribute']['distribution'] == 'event') { + $this->data['Attribute']['distribution'] = 5; + } + } + + if (!empty($this->data['Attribute']['type']) && empty($this->data['Attribute']['category'])) { + $this->data['Attribute']['category'] = $this->typeDefinitions[$this->data['Attribute']['type']]['default_category']; + } + + if (!isset($this->data['Attribute']['to_ids'])) { + $this->data['Attribute']['to_ids'] = $this->typeDefinitions[$this->data['Attribute']['type']]['to_ids']; + } + + if ($this->data['Attribute']['distribution'] != 4) $this->data['Attribute']['sharing_group_id'] = 0; // return true, otherwise the object cannot be saved return true; @@ -651,6 +677,10 @@ class Attribute extends AppModel { public function valueIsUnique ($fields) { if (isset($this->data['Attribute']['deleted']) && $this->data['Attribute']['deleted']) return true; + // We escape this rule for objects as we can have the same category/type/value combination in different objects + if (!empty($this->data['Attribute']['object_relation'])) { + return true; + } $value = $fields['value']; if (strpos($value, '|')) { $value = explode('|', $value); @@ -1640,7 +1670,8 @@ class Attribute extends AppModel { $options = array( 'conditions' => $conditions, 'group' => array('Attribute.type', 'Attribute.value1'), - 'enforceWarninglist' => $enforceWarninglist + 'enforceWarninglist' => $enforceWarninglist, + 'flatten' => true ); $items = $this->fetchAttributes($user, $options); if (empty($items)) continue; @@ -1701,7 +1732,8 @@ class Attribute extends AppModel { 'contain' => array('Event'=> array('fields' => array('Event.id', 'Event.threat_level_id'))), 'group' => array('Attribute.type', 'Attribute.value1'), // fields to GROUP BY 'enforceWarninglist' => $enforceWarninglist, - 'includeAllTags' => $includeAllTags + 'includeAllTags' => $includeAllTags, + 'flatten' => true ); $items = $this->fetchAttributes($user, $params); if (empty($items)) continue; @@ -2226,6 +2258,28 @@ class Attribute extends AppModel { return $this->find('list', $params); } + public function fetchAttributesSimple($user, $options = array()) { + $params = array( + 'conditions' => $this->buildConditions($user), + 'fields' => array(), + 'recursive' => -1 + ); + if (isset($options['conditions'])) { + $params['conditions']['AND'][] = $options['conditions']; + } + if (isset($options['fields'])) { + $params['fields'] = $options['fields']; + } + $results = $this->find('all', array( + 'conditions' => $params['conditions'], + 'recursive' => -1, + 'fields' => $params['fields'], + 'sort' => false + )); + return $results; + + } + // Method that fetches all attributes for the various exports // very flexible, it's basically a replacement for find, with the addition that it restricts access based on user // options: @@ -2242,7 +2296,7 @@ class Attribute extends AppModel { 'Event' => array( 'fields' => array('id', 'info', 'org_id', 'orgc_id', 'uuid'), ), - ), + ) ); $params['contain']['AttributeTag'] = array('Tag' => array('conditions' => array())); if (empty($options['includeAllTags'])) $params['contain']['AttributeTag']['Tag']['conditions']['exportable'] = 1; @@ -2268,12 +2322,13 @@ class Attribute extends AppModel { } if (isset($options['fields'])) $params['fields'] = $options['fields']; if (isset($options['conditions'])) $params['conditions']['AND'][] = $options['conditions']; + if (empty($options['flatten'])) $params['conditions']['AND'][] = array('Attribute.object_id' => 0); if (isset($options['order'])) $params['order'] = $options['order']; if (!isset($options['withAttachments'])) $options['withAttachments'] = false; else ($params['order'] = array()); if (!isset($options['enforceWarninglist'])) $options['enforceWarninglist'] = false; if (!$user['Role']['perm_sync'] || !isset($options['deleted']) || !$options['deleted']) $params['conditions']['AND']['Attribute.deleted'] = 0; - if (isset($options['group'])) $params['group'] = array_merge(array('Attribute.id'), $options['group']); + if (isset($options['group'])) $params['group'] = empty($options['group']) ? $options['group'] : false; if (Configure::read('MISP.unpublishedprivate')) $params['conditions']['AND'][] = array('OR' => array('Event.published' => 1, 'Event.orgc_id' => $user['org_id'])); if (!empty($options['list'])) { if (!empty($options['event_ids'])) { @@ -2283,7 +2338,6 @@ class Attribute extends AppModel { $fields = array('Attribute.event_id'); $group = false; } - $start = microtime(true); $results = $this->find('list', array( 'conditions' => $params['conditions'], 'recursive' => -1, @@ -2425,7 +2479,21 @@ class Attribute extends AppModel { else return 'Could not save changes.'; } - public function saveAndEncryptAttribute($attribute, $user) { + public function saveAttributes($attributes) { + foreach ($attributes as $k => $attribute) { + if (!empty($attribute['encrypt']) && $attribute['encrypt']) { + $result = $this->handleMaliciousBase64($attribute['event_id'], $attribute['value'], $attribute['data'], array('md5')); + $attribute['data'] = $result['data']; + $attribute['value'] = $attribute['value'] . '|' . $result['md5']; + } + unset($attribute['Attachment']); + $this->create(); + $this->save($attribute); + } + return true; + } + + public function saveAndEncryptAttribute($attribute, $user = false) { $hashes = array('md5' => 'malware-sample', 'sha1' => 'filename|sha1', 'sha256' => 'filename|sha256'); if ($attribute['encrypt']) { $result = $this->handleMaliciousBase64($attribute['event_id'], $attribute['value'], $attribute['data'], array_keys($hashes)); @@ -2603,4 +2671,280 @@ class Attribute extends AppModel { } return $cidrList; } + + public function fetchDistributionData($user) { + $initialDistribution = 5; + if (Configure::read('MISP.default_attribute_distribution') != null) { + if (Configure::read('MISP.default_attribute_distribution') === 'event') { + $initialDistribution = 5; + } else { + $initialDistribution = Configure::read('MISP.default_attribute_distribution'); + } + } + $sgs = $this->SharingGroup->fetchAllAuthorised($user, 'name', 1); + $this->set('sharingGroups', $sgs); + $distributionLevels = $this->distributionLevels; + if (empty($sgs)) { + unset($distributionLevels[4]); + } + return array('sgs' => $sgs, 'levels' => $distributionLevels, 'initial' => $initialDistribution); + } + + public function simpleAddMalwareSample($event_id, $category, $distribution, $sharing_group_id, $comment, $filename, $tmpfile) { + $attributes = array( + 'malware-sample' => array('type' => 'malware-sample', 'data' => 1, 'category' => '', 'to_ids' => 1, 'disable_correlation' => 0, 'object_relation' => 'malware-sample'), + 'filename' => array('type' => 'filename', 'category' => '', 'to_ids' => 0, 'disable_correlation' => 0, 'object_relation' => 'filename'), + 'md5' => array('type' => 'md5', 'category' => '', 'to_ids' => 1, 'disable_correlation' => 0, 'object_relation' => 'md5'), + 'sha1' => array('type' => 'sha1', 'category' => '', 'to_ids' => 1, 'disable_correlation' => 0, 'object_relation' => 'sha1'), + 'sha256' => array('type' => 'sha256', 'category' => '', 'to_ids' => 1, 'disable_correlation' => 0, 'object_relation' => 'sha256'), + 'size-in-bytes' => array('type' => 'size-in-bytes', 'category' => 'Other', 'to_ids' => 0, 'disable_correlation' => 1, 'object_relation' => 'filesize') + ); + $hashes = array('md5', 'sha1', 'sha256'); + $this->Object = ClassRegistry::init('Object'); + $this->ObjectTemplate = ClassRegistry::init('ObjectTemplate'); + $object_template = $this->ObjectTemplate->find('first', array( + 'conditions' => array( + 'ObjectTemplate.uuid' => '688c46fb-5edb-40a3-8273-1af7923e2215' + ), + 'recursive' => -1 + )); + if (empty($object_template)) { + $object_template = array( + 'ObjectTemplate' => array( + 'meta-category' => 'file', + 'name' => 'file', + 'template_uuid' => '688c46fb-5edb-40a3-8273-1af7923e2215', + 'version' => 1, + 'description' => 'File object describing a file with meta-information' + ) + ); + } + $object = array( + 'distribution' => $distribution, + 'sharing_group_id' => $sharing_group_id, + 'meta-category' => $object_template['ObjectTemplate']['meta-category'], + 'name' => $object_template['ObjectTemplate']['name'], + 'template_version' => $object_template['ObjectTemplate']['version'], + 'description' => $object_template['ObjectTemplate']['description'], + 'template_uuid' => $object_template['ObjectTemplate']['uuid'], + 'event_id' => $event_id, + 'comment' => $comment + ); + $result = $this->Event->Attribute->handleMaliciousBase64($event_id, $filename, base64_encode($tmpfile->read()), $hashes); + foreach ($attributes as $k => $v) { + $attribute = array( + 'distribution' => 5, + 'category' => empty($v['category']) ? $category : $v['category'], + 'type' => $v['type'], + 'to_ids' => $v['to_ids'], + 'disable_correlation' => $v['disable_correlation'], + 'object_id' => $this->Object->id, + 'event_id' => $event_id, + 'object_relation' => $v['object_relation'] + ); + if (isset($v['data'])) { + $attribute['data'] = $result['data']; + } + if ($k == 'malware-sample') { + $attribute['value'] = $filename . '|' . $result['md5']; + } else if ($k == 'size-in-bytes') { + $attribute['value'] = $tmpfile->size(); + } else if ($k == 'filename') { + $attribute['value'] = $filename; + } else { + $attribute['value'] = $result[$v['type']]; + } + $object['Attribute'][] = $attribute; + } + return array('Object' => array($object)); + } + + public function advancedAddMalwareSample($tmpfile) { + $execRetval = ''; + $execOutput = array(); + $result = shell_exec('python ' . APP . 'files/scripts/generate_file_objects.py -p ' . $tmpfile->path); + if (!empty($result)) { + $result = json_decode($result, true); + if (isset($result['objects'])) { + $result['Object'] = $result['objects']; + unset($result['objects']); + } + if (isset($result['references'])) { + $result['ObjectReference'] = $result['references']; + unset($result['references']); + } + } + return $result; + } + + // gets an attribute, saves it + // handles encryption, attaching to event/object, logging of issues, tag capturing + public function captureAttribute($attribute, $eventId, $user, $objectId = false, $log = false) { + if ($log == false) { + $log = ClassRegistry::init('Log'); + } + $attribute['event_id'] = $eventId; + $attribute['object_id'] = $objectId ? $objectId : 0; + unset($attribute['id']); + if (isset($attribute['encrypt'])) { + $result = $this->handleMaliciousBase64($eventId, $attribute['value'], $attribute['data'], array('md5')); + } + $fieldList = array( + 'event_id', + 'category', + 'type', + 'value', + 'value1', + 'value2', + 'to_ids', + 'uuid', + 'timestamp', + 'distribution', + 'comment', + 'sharing_group_id', + 'deleted', + 'disable_correlation', + 'object_id', + 'object_relation' + ); + $this->create(); + if (!$this->save($attribute, array('fieldList' => $fieldList))) { + $attribute_short = (isset($attribute['category']) ? $attribute['category'] : 'N/A') . '/' . (isset($attribute['type']) ? $attribute['type'] : 'N/A') . ' ' . (isset($attribute['value']) ? $attribute['value'] : 'N/A'); + $log->create(); + $log->save(array( + 'org' => $user['Organisation']['name'], + 'model' => 'Attribute', + 'model_id' => 0, + 'email' => $user['email'], + 'action' => 'add', + 'user_id' => $user['id'], + 'title' => 'Attribute dropped due to validation for Event ' . $eventId . ' failed: ' . $attribute_short, + 'change' => 'Validation errors: ' . json_encode($this->validationErrors) . ' Full Attribute: ' . json_encode($attribute), + )); + } else { + if (isset($attribute['AttributeTag'])) { + foreach ($attribute['AttributeTag'] as $at) { + unset($at['id']); + $this->AttributeTag->create(); + $at['attribute_id'] = $this->id; + $at['event_id'] = $eventId; + $this->AttributeTag->save($at); + } + } + } + return $attribute; + } + + public function editAttribute($attribute, $eventId, $user, $objectId, $log = false) { + $attribute['event_id'] = $eventId; + $attribute['object_id'] = $objectId; + if (isset($attribute['encrypt'])) { + $result = $this->handleMaliciousBase64($eventId, $attribute['value'], $attribute['data'], array('md5')); + $attribute['data'] = $result['data']; + $attribute['value'] = $attribute['value'] . '|' . $result['md5']; + } + if (isset($attribute['uuid'])) { + $existingAttribute = $this->find('first', array( + 'conditions' => array('Attribute.uuid' => $attribute['uuid']), + 'recursive' => -1 + )); + if (count($existingAttribute)) { + if ($existingAttribute['Attribute']['event_id'] != $eventId || $existingAttribute['Attribute']['object_id'] != $objectId) { + $result = $this->Log->save(array( + 'org' => $user['Organisation']['name'], + 'model' => 'Attribute', + 'model_id' => 0, + 'email' => $user['email'], + 'action' => 'edit', + 'user_id' => $user['id'], + 'title' => 'Duplicate UUID found in attribute', + 'change' => 'An attribute was blocked from being saved due to a duplicate UUID. The uuid in question is: ' . $attribute['uuid'] . '. This can also be due to the same attribute (or an attribute with the same UUID) existing in a different event / object)', + )); + return true; + } + // If a field is not set in the request, just reuse the old value + $recoverFields = array('value', 'to_ids', 'distribution', 'category', 'type', 'comment', 'sharing_group_id', 'object_id', 'object_relation'); + foreach ($recoverFields as $rF) if (!isset($attribute[$rF])) $attribute[$rF] = $existingAttribute['Attribute'][$rF]; + $attribute['id'] = $existingAttribute['Attribute']['id']; + // Check if the attribute's timestamp is bigger than the one that already exists. + // If yes, it means that it's newer, so insert it. If no, it means that it's the same attribute or older - don't insert it, insert the old attribute. + // Alternatively, we could unset this attribute from the request, but that could lead with issues if we decide that we want to start deleting attributes that don't exist in a pushed event. + if (isset($attribute['timestamp'])) { + if ($attribute['timestamp'] <= $existingAttribute['Attribute']['timestamp']) { + return true; + } + } else { + $attribute['timestamp'] = $date; + } + } else { + $this->create(); + } + } else { + $this->create(); + } + $attribute['event_id'] = $eventId; + if ($attribute['distribution'] == 4) { + $attribute['sharing_group_id'] = $this->SharingGroup->captureSG($attribute['SharingGroup'], $user); + } + $fieldList = array( + 'event_id', + 'category', + 'type', + 'value', + 'value1', + 'value2', + 'to_ids', + 'uuid', + 'revision', + 'distribution', + 'timestamp', + 'comment', + 'sharing_group_id', + 'deleted', + 'disable_correlation' + ); + if (!$this->save($attribute, array('fieldList' => $fieldList))) { + $attribute_short = (isset($attribute['category']) ? $attribute['category'] : 'N/A') . '/' . (isset($attribute['type']) ? $attribute['type'] : 'N/A') . ' ' . (isset($attribute['value']) ? $attribute['value'] : 'N/A'); + $this->Log->create(); + $this->Log->save(array( + 'org' => $user['Organisation']['name'], + 'model' => 'Attribute', + 'model_id' => 0, + 'email' => $user['email'], + 'action' => 'edit', + 'user_id' => $user['id'], + 'title' => 'Attribute dropped due to validation for Event ' . $eventId . ' failed: ' . $attribute_short, + 'change' => 'Validation errors: ' . json_encode($this->validationErrors) . ' Full Attribute: ' . json_encode($attribute), + )); + return $this->validationErrors; + } else { + if (isset($attribute['Tag']) && $user['Role']['perm_tagger']) { + foreach ($attribute['Tag'] as $tag) { + $tag_id = $this->AttributeTag->Tag->captureTag($tag, $user); + if ($tag_id) { + // fix the IDs here + $this->AttributeTag->attachTagToAttribute($this->id, $this->id, $tag_id); + } else { + // If we couldn't attach the tag it is most likely because we couldn't create it - which could have many reasons + // However, if a tag couldn't be added, it could also be that the user is a tagger but not a tag editor + // In which case if no matching tag is found, no tag ID is returned. Logging these is pointless as it is the correct behaviour. + if ($user['Role']['perm_tag_editor']) { + $this->Log->create(); + $this->Log->save(array( + 'org' => $user['Organisation']['name'], + 'model' => 'Attrubute', + 'model_id' => $this->id, + 'email' => $user['email'], + 'action' => 'edit', + 'user_id' => $user['id'], + 'title' => 'Failed create or attach Tag ' . $tag['name'] . ' to the attribute.', + 'change' => '' + )); + } + } + } + } + } + return true; + } } diff --git a/app/Model/Event.php b/app/Model/Event.php index 7c4fb643b..3939c28d7 100755 --- a/app/Model/Event.php +++ b/app/Model/Event.php @@ -53,6 +53,8 @@ class Event extends AppModel { 0 => 'Your organisation only', 1 => 'This community only', 2 => 'Connected communities', 3 => 'All communities', 4 => 'Sharing group' ); + private $__fTool = false; + public $shortDist = array(0 => 'Organisation', 1 => 'Community', 2 => 'Connected', 3 => 'All', 4 => ' sharing Group'); private $__assetCache = array(); @@ -313,6 +315,19 @@ class Event extends AppModel { 'finderQuery' => '', 'counterQuery' => '' ), + 'Object' => array( + 'className' => 'MispObject', + 'foreignKey' => 'event_id', + 'dependent' => true, + 'conditions' => '', + 'fields' => '', + 'order' => false, + 'limit' => '', + 'offset' => '', + 'exclusive' => '', + 'finderQuery' => '', + 'counterQuery' => '' + ), 'EventTag' => array( 'className' => 'EventTag', 'dependent' => true, @@ -595,17 +610,14 @@ class Event extends AppModel { // ii. Atttibute has a distribution between 1-3 (community only, connected communities, all orgs) // iii. Attribute has a sharing group that the user is accessible to view $conditionsCorrelation = $this->__buildEventConditionsCorrelation($user, $eventId, $sgids); - $correlations = $this->Correlation->find('all',array( - 'fields' => 'Correlation.event_id', + $correlations = $this->Correlation->find('list', array( + 'fields' => array('Correlation.event_id', 'Correlation.event_id'), 'conditions' => $conditionsCorrelation, 'recursive' => 0, + 'group' => 'Correlation.event_id', 'order' => array('Correlation.event_id DESC'))); - $relatedEventIds = array(); - foreach ($correlations as $correlation) { - $relatedEventIds[] = $correlation['Correlation']['event_id']; - } - $relatedEventIds = array_unique($relatedEventIds); + $relatedEventIds = array_values($correlations); // now look up the event data for these attributes $conditions = array("Event.id" => $relatedEventIds); $fields = array('id', 'date', 'threat_level_id', 'info', 'published', 'uuid', 'analysis', 'timestamp', 'distribution', 'org_id', 'orgc_id'); @@ -696,14 +708,17 @@ class Event extends AppModel { } else { $conditionsCorrelation = array($settings[$context]['correlationModel'] . '.1_event_id' => $id); } + $max_correlations = Configure::read('MISP.max_correlations_per_event'); + if (empty($max_correlations)) $max_correlations = 5000; $correlations = $this->{$settings[$context]['correlationModel']}->find('all',array( 'fields' => $settings[$context]['correlationModel'] . '.*', 'conditions' => $conditionsCorrelation, - 'recursive' => 0, - 'order' => false + 'recursive' => -1, + 'order' => false, + 'limit' => $max_correlations )); $relatedAttributes = array(); - foreach ($correlations as $correlation) { + foreach ($correlations as $k => $correlation) { $current = array( 'id' => $correlation[$settings[$context]['correlationModel']]['event_id'], 'org_id' => $correlation[$settings[$context]['correlationModel']]['org_id'], @@ -713,6 +728,7 @@ class Event extends AppModel { if (empty($relatedAttributes[$correlation[$settings[$context]['correlationModel']][$settings[$context]['parentIdField']]]) || !in_array($current, $relatedAttributes[$correlation[$settings[$context]['correlationModel']][$settings[$context]['parentIdField']]])) { $relatedAttributes[$correlation[$settings[$context]['correlationModel']][$settings[$context]['parentIdField']]][] = $current; } + unset($correlations[$k]); } return $relatedAttributes; } @@ -725,7 +741,7 @@ class Event extends AppModel { * modifies the original data. */ public function cleanupEventArrayFromXML(&$data) { - $objects = array('Attribute', 'ShadowAttribute'); + $objects = array('Attribute', 'ShadowAttribute', 'Object'); foreach ($objects as $object) { // Workaround for different structure in XML/array than what CakePHP expects if (isset($data['Event'][$object]) && is_array($data['Event'][$object]) && count($data['Event'][$object])) { @@ -963,7 +979,7 @@ class Event extends AppModel { private function __updateEventForSync($event, $server) { // rearrange things to be compatible with the Xml::fromArray() - $objectsToRearrange = array('Attribute', 'Orgc', 'SharingGroup', 'EventTag', 'Org', 'ShadowAttribute'); + $objectsToRearrange = array('Attribute', 'Object', 'Orgc', 'SharingGroup', 'EventTag', 'Org', 'ShadowAttribute'); foreach ($objectsToRearrange as $o) { if (isset($event[$o])) { $event['Event'][$o] = $event[$o]; @@ -991,57 +1007,20 @@ class Event extends AppModel { } } } - // remove value1 and value2 from the output - if (isset($event['Event']['Attribute'])) { - foreach ($event['Event']['Attribute'] as $key => &$attribute) { - // do not keep attributes that are private, nor cluster - if (!$server['Server']['internal'] && $attribute['distribution'] < 2) { - unset($event['Event']['Attribute'][$key]); - continue; // stop processing this - } - // Downgrade the attribute from connected communities to community only - if (!$server['Server']['internal'] && $attribute['distribution'] == 2) { - $attribute['distribution'] = 1; - } - // If the attribute has a sharing group attached, make sure it can be transfered - if ($attribute['distribution'] == 4) { - if (!$server['Server']['internal'] && $this->checkDistributionForPush(array('Attribute' => $attribute), $server, 'Attribute') === false) { - unset($event['Event']['Attribute'][$key]); - continue; - } - // Add the local server to the list of instances in the SG - if (isset($attribute['SharingGroup']['SharingGroupServer'])) { - foreach ($attribute['SharingGroup']['SharingGroupServer'] as &$s) { - if ($s['server_id'] == 0) { - $s['Server'] = array('id' => 0, 'url' => Configure::read('MISP.baseurl')); - } - } - } - } - foreach ($attribute['AttributeTag'] as $kt => $tag) { - if (!$tag['Tag']['exportable']) { - unset($attribute['AttributeTag'][$kt]); - } else { - unset($tag['Tag']['org_id']); - $attribute['Tag'][] = $tag['Tag']; - } - } - unset($attribute['AttributeTag']); + // prepare attribute for sync + if (!empty($event['Event']['Attribute'])) { + foreach ($event['Event']['Attribute'] as $key => $attribute) { + $event['Event']['Attribute'][$key] = $this->__updateAttributeForSync($attribute, $server); + if (empty($event['Event']['Attribute'][$key])) unset($event['Event']['Attribute'][$key]); + } + } - // remove value1 and value2 from the output - unset($attribute['value1']); - unset($attribute['value2']); - // also add the encoded attachment - if ($this->Attribute->typeIsAttachment($attribute['type'])) { - $encodedFile = $this->Attribute->base64EncodeAttachment($attribute); - $attribute['data'] = $encodedFile; - } - // Passing the attribute ID together with the attribute could cause the deletion of attributes after a publish/push - // Basically, if the attribute count differed between two instances, and the instance with the lower attribute - // count pushed, the old attributes with the same ID got overwritten. Unsetting the ID before pushing it - // solves the issue and a new attribute is always created. - unset($attribute['id']); + // prepare Object for sync + if (!empty($event['Event']['Object'])) { + foreach ($event['Event']['Object'] as $key => $object) { + $event['Event']['Object'][$key] = $this->__updateObjectForSync($object, $server); + if (empty($event['Event']['Object'][$key])) unset($event['Event']['Object'][$key]); } } @@ -1049,10 +1028,92 @@ class Event extends AppModel { if (!$server['Server']['internal'] && $event['Event']['distribution'] == 2) { $event['Event']['distribution'] = 1; } - $event['Event']['Attribute'] = array_values($event['Event']['Attribute']); + if (!empty($event['Event']['Attribute'])) $event['Event']['Attribute'] = array_values($event['Event']['Attribute']); + if (!empty($event['Event']['Object'])) $event['Event']['Object'] = array_values($event['Event']['Object']); return $event; } + private function __updateObjectForSync($object, $server) { + if (!$server['Server']['internal'] && $object['distribution'] < 2) { + return false; + } + // Downgrade the object from connected communities to community only + if (!$server['Server']['internal'] && $object['distribution'] == 2) { + $object['distribution'] = 1; + } + // If the object has a sharing group attached, make sure it can be transfered + if ($object['distribution'] == 4) { + if (!$server['Server']['internal'] && $this->checkDistributionForPush(array('Object' => $object), $server, 'Object') === false) { + return false; + } + // Add the local server to the list of instances in the SG + if (isset($object['SharingGroup']['SharingGroupServer'])) { + foreach ($object['SharingGroup']['SharingGroupServer'] as &$s) { + if ($s['server_id'] == 0) { + $s['Server'] = array('id' => 0, 'url' => Configure::read('MISP.baseurl')); + } + } + } + } + if (!empty($object['Attribute'])) { + foreach ($object['Attribute'] as $key => $attribute) { + $object['Attribute'][$key] = $this->__updateAttributeForSync($attribute, $server); + if (empty($object['Attribute'][$key])) unset($object['Attribute'][$key]); + } + } + return $object; + } + + private function __updateAttributeForSync($attribute, $server) { + // do not keep attributes that are private, nor cluster + if (!$server['Server']['internal'] && $attribute['distribution'] < 2) { + return false; + } + // Downgrade the attribute from connected communities to community only + if (!$server['Server']['internal'] && $attribute['distribution'] == 2) { + $attribute['distribution'] = 1; + } + + // If the attribute has a sharing group attached, make sure it can be transfered + if ($attribute['distribution'] == 4) { + if (!$server['Server']['internal'] && $this->checkDistributionForPush(array('Attribute' => $attribute), $server, 'Attribute') === false) { + return false; + } + // Add the local server to the list of instances in the SG + if (isset($attribute['SharingGroup']['SharingGroupServer'])) { + foreach ($attribute['SharingGroup']['SharingGroupServer'] as &$s) { + if ($s['server_id'] == 0) { + $s['Server'] = array('id' => 0, 'url' => Configure::read('MISP.baseurl')); + } + } + } + } + foreach ($attribute['AttributeTag'] as $kt => $tag) { + if (!$tag['Tag']['exportable']) { + unset($attribute['AttributeTag'][$kt]); + } else { + unset($tag['Tag']['org_id']); + $attribute['Tag'][] = $tag['Tag']; + } + } + unset($attribute['AttributeTag']); + + // remove value1 and value2 from the output + unset($attribute['value1']); + unset($attribute['value2']); + // also add the encoded attachment + if ($this->Attribute->typeIsAttachment($attribute['type'])) { + $encodedFile = $this->Attribute->base64EncodeAttachment($attribute); + $attribute['data'] = $encodedFile; + } + // Passing the attribute ID together with the attribute could cause the deletion of attributes after a publish/push + // Basically, if the attribute count differed between two instances, and the instance with the lower attribute + // count pushed, the old attributes with the same ID got overwritten. Unsetting the ID before pushing it + // solves the issue and a new attribute is always created. + unset($attribute['id']); + return $attribute; + } + public function downloadEventFromServer($eventId, $server, $HttpSocket=null) { $url = $server['Server']['url']; $authkey = $server['Server']['authkey']; @@ -1286,6 +1347,14 @@ class Event extends AppModel { $isSiteAdmin = $user['Role']['perm_site_admin']; if (isset($options['disableSiteAdmin']) && $options['disableSiteAdmin']) $isSiteAdmin = false; $conditionsAttributes = array(); + $conditionsObjects = array(); + $conditionsObjectReferences = array(); + + if (isset($options['flattenObjects']) && $options['flattenObjects']) { + $flattenObjects = true; + } else { + $flattenObjects = false; + } $sgids = $this->cacheSgids($user, $useCache); // restricting to non-private or same org if the user is not a site-admin. if (!$isSiteAdmin) { @@ -1322,30 +1391,51 @@ class Event extends AppModel { )), '(SELECT events.org_id FROM events WHERE events.id = Attribute.event_id)' => $user['org_id'] ); + + $conditionsObjects['AND'][0]['OR'] = array( + array('AND' => array( + 'Object.distribution >' => 0, + 'Object.distribution !=' => 4, + )), + array('AND' => array( + 'Object.distribution' => 4, + 'Object.sharing_group_id' => $sgids, + )), + '(SELECT events.org_id FROM events WHERE events.id = Object.event_id)' => $user['org_id'] + ); } if ($options['distribution']) { $conditions['AND'][] = array('Event.distribution' => $options['distribution']); $conditionsAttributes['AND'][] = array('Attribute.distribution' => $options['distribution']); + $conditionsObjects['AND'][] = array('Object.distribution' => $options['distribution']); } if ($options['sharing_group_id']) { $conditions['AND'][] = array('Event.sharing_group_id' => $options['sharing_group_id']); $conditionsAttributes['AND'][] = array('Attribute.sharing_group_id' => $options['sharing_group_id']); + $conditionsObjects['AND'][] = array('Object.sharing_group_id' => $options['sharing_group_id']); } if ($options['from']) $conditions['AND'][] = array('Event.date >=' => $options['from']); if ($options['to']) $conditions['AND'][] = array('Event.date <=' => $options['to']); if ($options['last']) $conditions['AND'][] = array('Event.publish_timestamp >=' => $options['last']); if ($options['event_uuid']) $conditions['AND'][] = array('Event.uuid' => $options['event_uuid']); + + $softDeletables = array('Attribute', 'Object', 'ObjectReference'); if (isset($options['deleted']) && $options['deleted']) { if (!$user['Role']['perm_sync']) { - $conditionsAttributes['AND'][] = array( - 'OR' => array( - '(SELECT events.org_id FROM events WHERE events.id = Attribute.event_id)' => $user['org_id'], - 'Attribute.deleted' => 0 - ) - ); + foreach ($softDeletables as $softDeletable) { + ${'conditions' . $softDeletable . 's'}['AND'][] = array( + 'OR' => array( + '(SELECT events.org_id FROM events WHERE events.id = ' . $softDeletable . '.event_id)' => $user['org_id'], + $softDeletable . '.deleted' => 0 + ) + ); + } } - } else $conditionsAttributes['AND']['Attribute.deleted'] = 0; - + } else { + foreach ($softDeletables as $softDeletable) { + ${'conditions' . $softDeletable . 's'}['AND'][$softDeletable . '.deleted'] = 0; + } + } if ($options['idList'] && !$options['tags']) { $conditions['AND'][] = array('Event.id' => $options['idList']); } @@ -1369,7 +1459,8 @@ class Event extends AppModel { // do not expose all the data ... $fields = array('Event.id', 'Event.orgc_id', 'Event.org_id', 'Event.date', 'Event.threat_level_id', 'Event.info', 'Event.published', 'Event.uuid', 'Event.attribute_count', 'Event.analysis', 'Event.timestamp', 'Event.distribution', 'Event.proposal_email_lock', 'Event.user_id', 'Event.locked', 'Event.publish_timestamp', 'Event.sharing_group_id', 'Event.disable_correlation'); - $fieldsAtt = array('Attribute.id', 'Attribute.type', 'Attribute.category', 'Attribute.value', 'Attribute.to_ids', 'Attribute.uuid', 'Attribute.event_id', 'Attribute.distribution', 'Attribute.timestamp', 'Attribute.comment', 'Attribute.sharing_group_id', 'Attribute.deleted', 'Attribute.disable_correlation'); + $fieldsAtt = array('Attribute.id', 'Attribute.type', 'Attribute.category', 'Attribute.value', 'Attribute.to_ids', 'Attribute.uuid', 'Attribute.event_id', 'Attribute.distribution', 'Attribute.timestamp', 'Attribute.comment', 'Attribute.sharing_group_id', 'Attribute.deleted', 'Attribute.disable_correlation', 'Attribute.object_id', 'Attribute.object_relation'); + $fieldsObj = array('*'); $fieldsShadowAtt = array('ShadowAttribute.id', 'ShadowAttribute.type', 'ShadowAttribute.category', 'ShadowAttribute.value', 'ShadowAttribute.to_ids', 'ShadowAttribute.uuid', 'ShadowAttribute.event_uuid', 'ShadowAttribute.event_id', 'ShadowAttribute.old_id', 'ShadowAttribute.comment', 'ShadowAttribute.org_id', 'ShadowAttribute.proposal_to_delete', 'ShadowAttribute.timestamp'); $fieldsOrg = array('id', 'name', 'uuid'); $fieldsServer = array('id', 'url', 'name'); @@ -1395,6 +1486,15 @@ class Event extends AppModel { 'order' => false ), ), + 'Object' => array( + 'fields' => $fieldsObj, + 'conditions' => $conditionsObjects, + 'order' => false, + 'ObjectReference' => array( + 'conditions' => $conditionsObjectReferences, + 'order' => false + ) + ), 'ShadowAttribute' => array( 'fields' => $fieldsShadowAtt, 'conditions' => array('deleted' => 0), @@ -1407,6 +1507,9 @@ class Event extends AppModel { ) ) ); + if ($flattenObjects) { + unset($params['contain']['Object']); + } if ($options['metadata']) { unset($params['contain']['Attribute']); unset($params['contain']['ShadowAttribute']); @@ -1416,12 +1519,41 @@ class Event extends AppModel { } $results = $this->find('all', $params); if (empty($results)) return array(); + // Do some refactoring with the event if (Configure::read('Plugin.Sightings_enable') !== false) { $this->Sighting = ClassRegistry::init('Sighting'); } $userEmails = array(); + $fields = array( + 'common' => array('distribution', 'sharing_group_id', 'uuid'), + 'Attribute' => array('value', 'type', 'category', 'to_ids'), + 'Object' => array('name', 'meta-category') + ); foreach ($results as $eventKey => &$event) { + if (!empty($event['Object'])) { + foreach ($event['Object'] as $k => $object) { + if (!empty($object['ObjectReference'])) { + foreach ($object['ObjectReference'] as $k2 => $reference) { + $type = array('Attribute', 'Object')[$reference['referenced_type']]; + $temp = $this->{$type}->find('first', array( + 'recursive' => -1, + 'fields' => array_merge($fields['common'], $fields[array('Attribute', 'Object')[$reference['referenced_type']]]), + 'conditions' => array('id' => $reference['referenced_id']) + )); + if (!empty($temp)) { + if (!$isSiteAdmin && $user['org_id'] != $event['Event']['orgc_id']) { + if ($temp[$type]['distribution'] == 0 || ($temp[$type]['distribution'] == 4 && !in_array($temp[$type]['sharing_group_id'], $sgsids))) { + unset($object['ObjectReference'][$k2]); + continue; + } + } + $event['Object'][$k]['ObjectReference'][$k2][$type] = $temp[$type]; + } + } + } + } + } if (!$options['sgReferenceOnly'] && $event['Event']['sharing_group_id']) { $event['SharingGroup'] = $sharingGroupData[$event['Event']['sharing_group_id']]['SharingGroup']; } @@ -1548,6 +1680,16 @@ class Event extends AppModel { } } + if ($event['Attribute'][$key]['object_id'] != 0) { + if (!empty($event['Object'])) { + foreach ($event['Object'] as $objectKey => $objectValue) { + if ($event['Attribute'][$key]['object_id'] == $objectValue['id']) { + $event['Object'][$objectKey]['Attribute'][] = $event['Attribute'][$key]; + } + } + } + unset($event['Attribute'][$key]); + } } $event['Attribute'] = array_values($event['Attribute']); } @@ -1796,6 +1938,63 @@ class Event extends AppModel { return true; } + private function __buildAlertEmailObject($user, &$body, &$bodyTempOther, $objects, $owner, $oldpublish) { + foreach ($objects as $object) { + if (!$owner && $object['distribution'] == 0) continue; + if ($object['distribution'] == 4 && !$this->Event->SharingGroup->checkIfAuthorised($user, $object['sharing_group_id'])) continue; + if (isset($oldpublish) && isset($object['timestamp']) && $object['timestamp'] > $oldpublish) { + $body .= '* '; + } else { + $body .= ' '; + } + $body .= $object['name'] . '/' . $object['meta-category'] . "\n"; + if (!empty($object['Attribute'])) { + $body = $this->__buildAlertEmailAttribute($user, $body, $bodyTempOther, $object['Attribute'], $owner, $oldpublish, ' '); + } + } + } + + private function __buildAlertEmailAttribute($user, &$body, &$bodyTempOther, $attributes, $owner, $oldpublish, $indent = ' ') { + $appendlen = 20; + foreach ($attributes as $attribute) { + if (!$owner && $attribute['distribution'] == 0) continue; + if ($attribute['distribution'] == 4 && !$this->Event->SharingGroup->checkIfAuthorised($user, $attribute['sharing_group_id'])) continue; + $ids = ''; + if ($attribute['to_ids']) $ids = ' (IDS)'; + $strRepeatCount = $appendlen - 2 - strlen($attribute['type']); + $strRepeat = ($strRepeatCount > 0) ? str_repeat(' ', $strRepeatCount) : ''; + if (isset($oldpublish) && isset($attribute['timestamp']) && $attribute['timestamp'] > $oldpublish) { + $line = '* ' . $indent . $attribute['category'] . '/' . $attribute['type'] . $strRepeat . ': ' . $attribute['value'] . $ids . " *\n"; + } else { + $line = $indent . $attribute['category'] . '/' . $attribute['type'] . $strRepeat . ': ' . $attribute['value'] . $ids . "\n"; + } + // Defanging URLs (Not "links") emails domains/ips in notification emails + if ('url' == $attribute['type'] || 'uri' == $attribute['type']) { + $line = str_ireplace("http","hxxp", $line); + $line = str_ireplace(".","[.]", $line); + } + else if (in_array($attribute['type'], array('email-src', 'email-dst', 'whois-registrant-email', 'dns-soa-email', 'email-reply-to'))) { + $line = str_replace("@","[at]", $line); + } + else if (in_array($attribute['type'], array('hostname', 'domain', 'ip-src', 'ip-dst', 'domain|ip'))) { + $line = str_replace(".","[.]", $line); + } + if (!empty($attribute['AttributeTag'])) { + $line .= ' - Tags: '; + foreach ($attribute['AttributeTag'] as $k => $aT) { + if ($k > 0) { + $line .= ', '; + } + $line .= $aT['Tag']['name']; + } + $line .= "\n"; + } + if ('other' == $attribute['type']) // append the 'other' attribute types to the bottom. + $bodyTempOther .= $line; + else $body .= $line; + } + } + private function __buildAlertEmailBody($event, $user, $oldpublish, $sgModel) { $owner = false; if ($user['org_id'] == $event['Event']['orgc_id'] || $user['org_id'] == $event['Event']['org_id'] || $user['Role']['perm_site_admin']) $owner = true; @@ -1832,46 +2031,14 @@ class Event extends AppModel { } $body .= '==============================================' . "\n"; } - $body .= 'Attributes (* indicates a new or modified attribute):' . "\n"; $bodyTempOther = ""; - if (isset($event['Attribute'])) { - foreach ($event['Attribute'] as &$attribute) { - if (!$owner && $attribute['distribution'] == 0) continue; - if ($attribute['distribution'] == 4 && !$sgModel->checkIfAuthorised($user, $attribute['sharing_group_id'])) continue; - $ids = ''; - if ($attribute['to_ids']) $ids = ' (IDS)'; - $strRepeatCount = $appendlen - 2 - strlen($attribute['type']); - $strRepeat = ($strRepeatCount > 0) ? str_repeat(' ', $strRepeatCount) : ''; - if (isset($oldpublish) && isset($attribute['timestamp']) && $attribute['timestamp'] > $oldpublish) { - $line = '* ' . $attribute['category'] . '/' . $attribute['type'] . $strRepeat . ': ' . $attribute['value'] . $ids . " *\n"; - } else { - $line = $attribute['category'] . '/' . $attribute['type'] . $strRepeat . ': ' . $attribute['value'] . $ids . "\n"; - } - // Defanging URLs (Not "links") emails domains/ips in notification emails - if ('url' == $attribute['type'] || 'uri' == $attribute['type']) { - $line = str_ireplace("http","hxxp", $line); - $line = str_ireplace(".","[.]", $line); - } - else if (in_array($attribute['type'], array('email-src', 'email-dst', 'whois-registrant-email', 'dns-soa-email', 'email-reply-to'))) { - $line = str_replace("@","[at]", $line); - } - else if (in_array($attribute['type'], array('hostname', 'domain', 'ip-src', 'ip-dst', 'domain|ip'))) { - $line = str_replace(".","[.]", $line); - } - if (!empty($attribute['AttributeTag'])) { - $line .= ' - Tags: '; - foreach ($attribute['AttributeTag'] as $k => $aT) { - if ($k > 0) { - $line .= ', '; - } - $line .= $aT['Tag']['name']; - } - $line .= "\n"; - } - if ('other' == $attribute['type']) // append the 'other' attribute types to the bottom. - $bodyTempOther .= $line; - else $body .= $line; - } + if (!empty($event['Attribute'])) { + $body .= 'Attributes (* indicates a new or modified attribute):' . "\n"; + $this->__buildAlertEmailAttribute($user, $body, $bodyTempOther, $event['Attribute'], $owner, $oldpublish); + } + if (!empty($event['Object'])) { + $body .= 'Objects (* indicates a new or modified object):' . "\n"; + $this->__buildAlertEmailObject($user, $body, $bodyTempOther, $event['Object'], $owner, $oldpublish); } if (!empty($bodyTempOther)) { $body .= "\n"; @@ -2139,17 +2306,71 @@ class Event extends AppModel { } else { if ($fromXml) $data = $this->__captureObjects($data, $user); } - // FIXME chri: validatebut the necessity for all these fields...impact on security ! $fieldList = array( - 'Event' => array('org_id', 'orgc_id', 'date', 'threat_level_id', 'analysis', 'info', 'user_id', 'published', 'uuid', 'timestamp', 'distribution', 'sharing_group_id', 'locked', 'disable_correlation'), - 'Attribute' => array('event_id', 'category', 'type', 'value', 'value1', 'value2', 'to_ids', 'uuid', 'timestamp', 'distribution', 'comment', 'sharing_group_id', 'deleted', 'disable_correlation'), + 'Event' => array( + 'org_id', + 'orgc_id', + 'date', + 'threat_level_id', + 'analysis', + 'info', + 'user_id', + 'published', + 'uuid', + 'timestamp', + 'distribution', + 'sharing_group_id', + 'locked', + 'disable_correlation' + ), + 'Attribute' => array( + 'event_id', + 'category', + 'type', + 'value', + 'to_ids', + 'uuid', + 'timestamp', + 'distribution', + 'comment', + 'sharing_group_id', + 'deleted', + 'disable_correlation', + 'object_id', + 'object_relation' + ), + 'Object' => array( + 'name', + 'meta-category', + 'description', + 'template_uuid', + 'template_version', + 'event_id', + 'uuid', + 'timestamp', + 'distribution', + 'sharing_group_id', + 'comment', + 'deleted' + ), + 'ObjectRelation' => array() ); $saveResult = $this->save(array('Event' => $data['Event']), array('fieldList' => $fieldList['Event'])); $this->Log = ClassRegistry::init('Log'); if ($saveResult) { if ($passAlong) { $this->Server = ClassRegistry::init('Server'); - $server = $this->Server->find('first', array('conditions' => array('Server.id' => $passAlong), 'recursive' => -1, 'fields' => array('Server.name', 'Server.id', 'Server.unpublish_event'))); + $server = $this->Server->find('first', array( + 'conditions' => array( + 'Server.id' => $passAlong + ), + 'recursive' => -1, + 'fields' => array( + 'Server.name', + 'Server.id', + 'Server.unpublish_event' + ) + )); $this->Log->create(); $this->Log->save(array( 'org' => $user['Organisation']['name'], @@ -2170,60 +2391,18 @@ class Event extends AppModel { } } if (isset($data['Event']['Attribute']) && !empty($data['Event']['Attribute'])) { - foreach ($data['Event']['Attribute'] as $k => &$attribute) { - $attribute['event_id'] = $this->id; - unset($attribute['id']); - if (isset($attribute['encrypt'])) { - $saveResult = $this->Attribute->saveAndEncryptAttribute($attribute, $user); - if ($saveResult !== true) { - $validationErrors['Attribute'][$k] = $saveResult; - $attribute_short = (isset($attribute['category']) ? $attribute['category'] : 'N/A') . '/' . (isset($attribute['type']) ? $attribute['type'] : 'N/A') . ' ' . (isset($attribute['value']) ? $attribute['value'] : 'N/A'); - $this->Log->create(); - $this->Log->save(array( - 'org' => $user['Organisation']['name'], - 'model' => 'Attribute', - 'model_id' => 0, - 'email' => $user['email'], - 'action' => 'add', - 'user_id' => $user['id'], - 'title' => 'Attribute dropped due to validation for Event ' . $this->id . ' failed: ' . $attribute_short, - 'change' => json_encode($saveResult ? $saveResult : array()), - )); - } else { - if (isset($attribute['AttributeTag'])) { - foreach ($attribute['AttributeTag'] as $at) { - $this->Attribute->AttributeTag->create(); - $at['attribute_id'] = $this->Attribute->id; - $at['event_id'] = $this->id; - $this->Attribute->AttributeTag->save($at); - } - } - } - } else { - $this->Attribute->create(); - if (!$this->Attribute->save($attribute, array('fieldList' => $fieldList['Attribute']))) { - $validationErrors['Attribute'][$k] = $this->Attribute->validationErrors; - $attribute_short = (isset($attribute['category']) ? $attribute['category'] : 'N/A') . '/' . (isset($attribute['type']) ? $attribute['type'] : 'N/A') . ' ' . (isset($attribute['value']) ? $attribute['value'] : 'N/A'); - $this->Log->create(); - $this->Log->save(array( - 'org' => $user['Organisation']['name'], - 'model' => 'Attribute', - 'model_id' => 0, - 'email' => $user['email'], - 'action' => 'add', - 'user_id' => $user['id'], - 'title' => 'Attribute dropped due to validation for Event ' . $this->id . ' failed: ' . $attribute_short, - 'change' => 'Validation errors: ' . json_encode($this->Attribute->validationErrors) . ' Full Attribute: ' . json_encode($attribute), - )); - } else { - if (isset($attribute['AttributeTag'])) { - foreach ($attribute['AttributeTag'] as $at) { - $this->Attribute->AttributeTag->create(); - $at['attribute_id'] = $this->Attribute->id; - $at['event_id'] = $this->id; - $this->Attribute->AttributeTag->save($at); - } - } + foreach ($data['Event']['Attribute'] as $k => $attribute) { + $data['Event']['Attribute'][$k] = $this->Attribute->captureAttribute($attribute, $this->id, $user, 0, $this->Log); + } + } + if (!empty($data['Event']['Object'])) { + foreach ($data['Event']['Object'] as $object) { + $result = $this->Object->captureObject($object, $this->id, $user, $this->Log); + } + foreach ($data['Event']['Object'] as $object) { + if (isset($object['ObjectReference'])) { + foreach ($object['ObjectReference'] as $objectRef) { + $result = $this->Object->ObjectReference->captureReference($objectRef, $this->id, $user, $this->Log); } } } @@ -2296,136 +2475,45 @@ class Event extends AppModel { } else { return (array('error' => 'Event could not be saved: Could not find the local event.')); } - if (isset($data['Event']['published']) && $data['Event']['published'] && !$user['Role']['perm_publish']) $data['Event']['published'] = 0; + if (!empty($data['Event']['published']) && !$user['Role']['perm_publish']) $data['Event']['published'] = 0; if (!isset($data['Event']['published'])) $data['Event']['published'] = 0; $fieldList = array( - 'Event' => array('date', 'threat_level_id', 'analysis', 'info', 'published', 'uuid', 'distribution', 'timestamp', 'sharing_group_id', 'disable_correlation'), - 'Attribute' => array('event_id', 'category', 'type', 'value', 'value1', 'value2', 'to_ids', 'uuid', 'revision', 'distribution', 'timestamp', 'comment', 'sharing_group_id', 'deleted', 'disable_correlation') + 'date', + 'threat_level_id', + 'analysis', + 'info', + 'published', + 'uuid', + 'distribution', + 'timestamp', + 'sharing_group_id', + 'disable_correlation' ); - $saveResult = $this->save(array('Event' => $data['Event']), array('fieldList' => $fieldList['Event'])); + $saveResult = $this->save(array('Event' => $data['Event']), array('fieldList' => $fieldList)); $this->Log = ClassRegistry::init('Log'); if ($saveResult) { $validationErrors = array(); if (isset($data['Event']['Attribute'])) { $data['Event']['Attribute'] = array_values($data['Event']['Attribute']); foreach ($data['Event']['Attribute'] as $k => $attribute) { - $attribute['event_id'] = $existingEvent['Event']['id']; - if (isset($attribute['encrypt'])) { - if (isset($attribute['uuid'])) { - $existingAttribute = $this->Attribute->findByUuid($attribute['uuid']); - if (!empty($existingAttribute)) { - $this->Log->create(); - $this->Log->save(array( - 'org' => $user['Organisation']['name'], - 'model' => 'Attribute', - 'model_id' => 0, - 'email' => $user['email'], - 'action' => 'add', - 'user_id' => $user['id'], - 'title' => 'Attribute dropped because the encrypt parameter was passed along an attribute that already exists', - 'change' => '', - )); - } - continue; - } - $saveResult = $this->Attribute->saveAndEncryptAttribute($attribute, $user); - if ($saveResult !== true) { - $attribute_short = (isset($attribute['category']) ? $attribute['category'] : 'N/A') . '/' . (isset($attribute['type']) ? $attribute['type'] : 'N/A') . ' ' . (isset($attribute['value']) ? $attribute['value'] : 'N/A'); - $this->Log->create(); - $this->Log->save(array( - 'org' => $user['Organisation']['name'], - 'model' => 'Attribute', - 'model_id' => 0, - 'email' => $user['email'], - 'action' => 'add', - 'user_id' => $user['id'], - 'title' => 'Attribute dropped due to validation for Event ' . $id . ' failed: ' . $attribute_short, - 'change' => json_encode($saveResult ? $saveResult : array()), - )); - } - } else { - if (isset($attribute['uuid'])) { - $existingAttribute = $this->Attribute->findByUuid($attribute['uuid']); - if (count($existingAttribute)) { - if ($existingAttribute['Attribute']['event_id'] != $id) { - $result = $this->Log->save(array( - 'org' => $user['Organisation']['name'], - 'model' => 'Attribute', - 'model_id' => 0, - 'email' => $user['email'], - 'action' => 'edit', - 'user_id' => $user['id'], - 'title' => 'Duplicate UUID found in attribute', - 'change' => 'An attribute was blocked from being saved due to a duplicate UUID. The uuid in question is: ' . $attribute['uuid'], - )); - unset($data['Event']['Attribute'][$k]); - } else { - // If a field is not set in the request, just reuse the old value - $recoverFields = array('value', 'to_ids', 'distribution', 'category', 'type', 'comment', 'sharing_group_id'); - foreach ($recoverFields as $rF) if (!isset($attribute[$rF])) $data['Event']['Attribute'][$k][$rF] = $existingAttribute['Attribute'][$rF]; - $data['Event']['Attribute'][$k]['id'] = $existingAttribute['Attribute']['id']; - // Check if the attribute's timestamp is bigger than the one that already exists. - // If yes, it means that it's newer, so insert it. If no, it means that it's the same attribute or older - don't insert it, insert the old attribute. - // Alternatively, we could unset this attribute from the request, but that could lead with issues if we decide that we want to start deleting attributes that don't exist in a pushed event. - if (isset($data['Event']['Attribute'][$k]['timestamp'])) { - if ($data['Event']['Attribute'][$k]['timestamp'] <= $existingAttribute['Attribute']['timestamp']) { - unset($data['Event']['Attribute'][$k]); - continue; - } - } else { - $data['Event']['Attribute'][$k]['timestamp'] = $date; - } - } - } else { - $this->Attribute->create(); - } - } else { - $this->Attribute->create(); - } + $result = $this->Attribute->editAttribute($attribute, $this->id, $user, 0, $this->Log); + if ($result !== true) { + $validationErrors['Attribute'][] = $result; } - $data['Event']['Attribute'][$k]['event_id'] = $this->id; - if ($data['Event']['Attribute'][$k]['distribution'] == 4) { - $data['Event']['Attribute'][$k]['sharing_group_id'] = $this->SharingGroup->captureSG($data['Event']['Attribute'][$k]['SharingGroup'], $user); + } + } + if (isset($data['Event']['Object'])) { + $data['Event']['Object'] = array_values($data['Event']['Object']); + foreach ($data['Event']['Object'] as $k => $object) { + $result = $this->Object->editObject($object, $this->id, $user, $this->Log); + if ($result !== true) { + $validationErrors['Object'][] = $result; } - if (!$this->Attribute->save($data['Event']['Attribute'][$k], array('fieldList' => $fieldList['Attribute']))) { - $validationErrors['Attribute'][$k] = $this->Attribute->validationErrors; - $attribute_short = (isset($data['Event']['Attribute'][$k]['category']) ? $data['Event']['Attribute'][$k]['category'] : 'N/A') . '/' . (isset($data['Event']['Attribute'][$k]['type']) ? $data['Event']['Attribute'][$k]['type'] : 'N/A') . ' ' . (isset($data['Event']['Attribute'][$k]['value']) ? $data['Event']['Attribute'][$k]['value'] : 'N/A'); - $this->Log->create(); - $this->Log->save(array( - 'org' => $user['Organisation']['name'], - 'model' => 'Attribute', - 'model_id' => 0, - 'email' => $user['email'], - 'action' => 'edit', - 'user_id' => $user['id'], - 'title' => 'Attribute dropped due to validation for Event ' . $this->id . ' failed: ' . $attribute_short, - 'change' => 'Validation errors: ' . json_encode($this->Attribute->validationErrors) . ' Full Attribute: ' . json_encode($attribute), - )); - } else { - if (isset($data['Event']['Attribute'][$k]['Tag']) && $user['Role']['perm_tagger']) { - foreach ($data['Event']['Attribute'][$k]['Tag'] as $tag) { - $tag_id = $this->Attribute->AttributeTag->Tag->captureTag($tag, $user); - if ($tag_id) { - $this->Attribute->AttributeTag->attachTagToAttribute($this->Attribute->id, $this->id, $tag_id); - } else { - // If we couldn't attach the tag it is most likely because we couldn't create it - which could have many reasons - // However, if a tag couldn't be added, it could also be that the user is a tagger but not a tag editor - // In which case if no matching tag is found, no tag ID is returned. Logging these is pointless as it is the correct behaviour. - if ($user['Role']['perm_tag_editor']) { - $this->Log->create(); - $this->Log->save(array( - 'org' => $user['Organisation']['name'], - 'model' => 'Attrubute', - 'model_id' => $this->Attribute->id, - 'email' => $user['email'], - 'action' => 'edit', - 'user_id' => $user['id'], - 'title' => 'Failed create or attach Tag ' . $tag['name'] . ' to the attribute.', - 'change' => '' - )); - } - } - } + } + foreach ($data['Event']['Object'] as $object) { + if (isset($object['ObjectReference'])) { + foreach ($object['ObjectReference'] as $objectRef) { + $result = $this->Object->ObjectReference->captureReference($objectRef, $this->id, $user, $this->Log); } } } @@ -2535,51 +2623,27 @@ class Event extends AppModel { // Uploads this specific event to all remote servers public function uploadEventToServersRouter($id, $passAlong = null) { - // make sure we have all the data of the Event - $event = $this->find('first', array( - 'conditions' => array('Event.id' => $id), - 'recursive' => -1, - 'contain' => array( - 'Attribute' => array( - 'SharingGroup' => array( - 'SharingGroupOrg' => array( - 'fields' => array('id', 'org_id', 'extend'), - 'Organisation' => array( - 'fields' => array('id', 'uuid', 'name') - ) - ), - 'SharingGroupServer' => array( - 'fields' => array('id', 'server_id', 'all_orgs'), - 'Server' => array( - 'fields' => array('id', 'url', 'name') - ) - ), - ), - 'AttributeTag' => array('Tag') - ), - 'EventTag' => array('Tag'), - 'Org' => array('fields' => array('id', 'uuid', 'name', 'local')), - 'Orgc' => array('fields' => array('id', 'uuid', 'name', 'local')), - 'SharingGroup' => array( - 'Organisation' => array( - 'fields' => array('id', 'uuid', 'name', 'local'), - ), - 'SharingGroupOrg' => array( - 'fields' => array('id', 'org_id', 'extend'), - 'Organisation' => array( - 'fields' => array('id', 'uuid', 'name') - ) - ), - 'SharingGroupServer' => array( - 'fields' => array('id', 'server_id', 'all_orgs'), - 'Server' => array( - 'fields' => array('id', 'url', 'name') - ) - ), - ), - ), + $eventOrgcId = $this->find('first', array( + 'conditions' => array('Event.id' => $id), + 'recursive' => -1, + 'fields' => array('Event.orgc_id') )); + // we create a fake site admin user object to fetch the event with everything included + // This replaces the old method of manually just fetching everything, staying consistent + // with the fetchEvent() output + $elevatedUser = array( + 'Role' => array( + 'perm_site_admin' => 1, + 'perm_sync' => 1 + ), + 'org_id' => $eventOrgcId['Event']['orgc_id'] + ); + $elevatedUser['Role']['perm_site_admin'] = 1; + $elevatedUser['Role']['perm_sync'] = 1; + $elevatedUser['Role']['perm_audit'] = 0; + $event = $this->fetchEvent($elevatedUser, array('eventid' => $id, 'includeAttachments' => true, 'includeAllTags' => true)); if (empty($event)) return true; + $event = $event[0]; $event['Event']['locked'] = 1; // get a list of the servers $this->Server = ClassRegistry::init('Server'); @@ -3048,8 +3112,188 @@ class Event extends AppModel { return time() - ($delta * $multiplier); } + private function __prepareAttributeForView( + $attribute, + $correlatedAttributes, + $correlatedShadowAttributes, + $filterType = false, + &$eventWarnings, + $warningLists + ) { + $attribute['objectType'] = 'attribute'; + $include = true; + if ($filterType && !in_array($filterType, array('proposal', 'correlation', 'warning'))) { + if (!in_array($attribute['type'], $this->Attribute->typeGroupings[$filterType])) { + $include = false; + } + } + if ($filterType === 'proposal' && empty($attribute['ShadowAttribute'])) { + $include = false; + } + if ($filterType === 'correlation' && !in_array($attribute['id'], $correlatedAttributes)) { + $include = false; + } + if (!empty($attribute['ShadowAttribute'])) { + $temp = array(); + foreach ($attribute['ShadowAttribute'] as $k => $proposal) { + $result = $this->__prepareProposalForView( + $proposal, + $correlatedShadowAttributes, + $filterType, + $eventWarnings, + $warningLists + ); + if ($result['include']) $temp[] = $result['data']; + } + $attribute['ShadowAttribute'] = $temp; + } + $attribute = $this->__prepareGenericForView($attribute, $eventWarnings, $warningLists); + if ($filterType === 'warning') { + if (empty($attribute['warnings'])) $include = false; + } + return array('include' => $include, 'data' => $attribute); + } + + private function __prepareProposalForView( + $proposal, + $correlatedShadowAttributes, + $filterType = false, + &$eventWarnings, + $warningLists + ) { + if ($proposal['proposal_to_delete']) { + $proposal['objectType'] = 'proposal_delete'; + } else { + $proposal['objectType'] = 'proposal'; + } + + $include = true; + if ($filterType === 'correlation' && !in_array($proposal['id'], $correlatedShadowAttributes)) { + $include = false; + } + if ($filterType && !in_array($filterType, array('proposal', 'correlation', 'warning'))) { + if (!in_array($proposal['type'], $this->Attribute->typeGroupings[$filterType])) { + $include = false; + } + } + $proposal = $this->__prepareGenericForView($proposal, $eventWarnings, $warningLists); + if ($filterType === 'warning') { + if (empty($proposal['warnings'])) $include = false; + } + return array('include' => $include, 'data' => $proposal); + } + + private function __prepareObjectForView( + $object, + $correlatedAttributes, + $correlatedShadowAttributes, + $filterType = false, + &$eventWarnings, + $warningLists + ) { + $object['category'] = $object['meta-category']; + $proposal['objectType'] = 'object'; + // filters depend on child objects + $include = empty($filterType) || $filterType == 'object' || $object['meta-category'] === $filterType; + if ($filterType === 'correlation' || $filterType === 'proposal') { + $include = $this->__checkObjectByFilter($object, $filterType, $correlatedAttributes, $correlatedShadowAttributes); + } + if (!empty($object['Attribute'])) { + $temp = array(); + foreach ($object['Attribute'] as $k => $proposal) { + $result = $this->__prepareAttributeForView( + $proposal, + $correlatedAttributes, + $correlatedShadowAttributes, + false, + $eventWarnings, + $warningLists + ); + if ($result['include']) $temp[] = $result['data']; + } + $object['Attribute'] = $temp; + } + if ($filterType === 'warning') { + $include = $this->__checkObjectByFilter($object, $filterType, $correlatedAttributes, $correlatedShadowAttributes); + } + return array('include' => $include, 'data' => $object); + } + + private function __checkObjectByFilter($object, $filterType, $correlatedAttributes, $correlatedShadowAttributes) { + $include = false; + switch($filterType) { + case 'warning': + if (!empty($object['Attribute'])) { + foreach ($object['Attribute'] as $k => $attribute) { + if (!empty($attribute['warnings'])) { + $include = true; + } + if (!empty($attribute['ShadowAttribute'])) { + foreach ($attribute['ShadowAttribute'] as $shadowAttribute) { + if (!empty($shadowAttribute['warnings'])) { + $include = true; + } + } + } + } + } + break; + case 'correlation': + if (!empty($object['Attribute'])) { + foreach ($object['Attribute'] as $k => $attribute) { + if (in_array($attribute['id'], $correlatedAttributes)) { + $include = true; + } else { + if (!empty($attribute['ShadowAttribute'])) { + foreach ($attribute['ShadowAttribute'] as $k => $shadowAttribute) { + if (in_array($shadowAttribute['id'], $correlatedShadowAttributes)) { + $include = true; + } + } + } + } + } + } + break; + case 'proposal': + if (!empty($object['Attribute'])) { + foreach ($object['Attribute'] as $k => $attribute) { + if (!empty($attribute['ShadowAttribute'])) { + $include = true; + } + } + } + break; + } + return $include; + } + + private function __prepareGenericForView( + $object, + &$eventWarnings, + $warningLists + ) { + if (!$this->__fTool) { + $this->__fTool = new FinancialTool(); + } + if ($object['type'] == 'attachment' && preg_match('/.*\.(jpg|png|jpeg|gif)$/i', $object['value'])) { + $object['image'] = $this->Attribute->base64EncodeAttachment($object); + } + if (isset($object['distribution']) && $object['distribution'] != 4) unset($object['SharingGroup']); + if ($object['objectType'] !== 'object') { + if ($object['category'] === 'Financial fraud') { + if (!$this->__fTool->validateRouter($object['type'], $object['value'])) { + $object['validationIssue'] = true; + } + } + } + $object = $this->Warninglist->checkForWarning($object, $eventWarnings, $warningLists); + return $object; + } + public function rearrangeEventForView(&$event, $passedArgs = array(), $all = false) { - $fTool = new FinancialTool(); + $this->Warninglist = ClassRegistry::init('Warninglist'); + $warningLists = $this->Warninglist->fetchForEventView(); foreach ($event['Event'] as $k => $v) { if (is_array($v)) { $event[$k] = $v; @@ -3065,79 +3309,75 @@ class Event extends AppModel { } } $eventArray = array(); + $eventWarnings = array(); $correlatedAttributes = isset($event['RelatedAttribute']) ? array_keys($event['RelatedAttribute']) : array(); $correlatedShadowAttributes = isset($event['RelatedShadowAttribute']) ? array_keys($event['RelatedShadowAttribute']) : array(); - $totalElements = count($event['Attribute']); + $event['objects'] = array(); foreach ($event['Attribute'] as $attribute) { - $totalElements += isset($attribute['ShadowAttribute']) ? count($attribute['ShadowAttribute']) : 0; - if ($filterType && !in_array($filterType, array('proposal', 'correlation', 'warning'))) if (!in_array($attribute['type'], $this->Attribute->typeGroupings[$filterType])) continue; - if (isset($attribute['distribution']) && $attribute['distribution'] != 4) unset($attribute['SharingGroup']); - $attribute['objectType'] = 0; - if (!empty($attribute['ShadowAttribute'])) { - $attribute['hasChildren'] = 1; - } else { - $attribute['hasChildren'] = 0; - } - if ($filterType === 'proposal' && $attribute['hasChildren'] == 0) continue; - if ($filterType === 'correlation' && !in_array($attribute['id'], $correlatedAttributes)) continue; - if ($attribute['type'] == 'attachment' && preg_match('/.*\.(jpg|png|jpeg|gif)$/i', $attribute['value'])) { - $attribute['image'] = $this->Attribute->base64EncodeAttachment($attribute); - } - $eventArray[] = $attribute; + $result = $this->__prepareAttributeForView( + $attribute, + $correlatedAttributes, + $correlatedShadowAttributes, + $filterType, + $eventWarnings, + $warningLists + ); + if ($result['include']) $event['objects'][] = $result['data']; } unset($event['Attribute']); - if (isset($event['ShadowAttribute'])) { - $totalElements += count($event['ShadowAttribute']); - foreach ($event['ShadowAttribute'] as $shadowAttribute) { - if ($filterType === 'correlation' && !in_array($shadowAttribute['id'], $correlatedShadowAttributes)) continue; - if ($filterType && !in_array($filterType, array('proposal', 'correlation', 'warning'))) if (!in_array($attribute['type'], $this->Attribute->typeGroupings[$filterType])) continue; - $shadowAttribute['objectType'] = 2; - if ($shadowAttribute['type'] == 'attachment' && preg_match('/.*\.(jpg|png|jpeg|gif)$/i', $shadowAttribute['value'])) { - $shadowAttribute['image'] = $this->ShadowAttribute->base64EncodeAttachment($attribute); - } - $eventArray[] = $shadowAttribute; + if (!empty($event['ShadowAttribute'])) { + foreach ($event['ShadowAttribute'] as $proposal) { + $result = $this->__prepareProposalForView( + $proposal, + $correlatedShadowAttributes, + $filterType, + $eventWarnings, + $warningLists + ); + $event['objects'][] = $result['data']; } } + if (!empty($event['Object'])) { + foreach ($event['Object'] as $object) { + $object['objectType'] = 'object'; + $result = $this->__prepareObjectForView( + $object, + $correlatedAttributes, + $correlatedShadowAttributes, + $filterType, + $eventWarnings, + $warningLists + ); + if ($result['include']) $event['objects'][] = $result['data']; + } + } + unset($event['Object']); unset($event['ShadowAttribute']); + $referencedObjectFields = array('meta-category', 'name', 'uuid', 'id'); + foreach ($event['objects'] as $object) { + if (!in_array($object['objectType'], array('attribute', 'object'))) continue; + if (!empty($object['ObjectReference'])) { + foreach ($object['ObjectReference'] as $reference) { + foreach ($event['objects'] as $k => $v) { + $referencedType = isset($reference['Attribute']) ? 'attribute' : 'object'; + if ($v['objectType'] == $referencedType && $reference['referenced_id'] == $v['id']) { + $temp = array(); + foreach ($referencedObjectFields as $field) { + $temp[$field] = $object[$field]; + } + $temp['relationship_type'] = $reference['relationship_type']; + $event['objects'][$k]['referenced_by'][$object['objectType']][] = $temp; + } + } + } + } + } App::uses('CustomPaginationTool', 'Tools'); $customPagination = new CustomPaginationTool(); if ($all) $passedArgs['page'] = 0; - $eventArrayWithProposals = array(); - foreach ($eventArray as $k => &$object) { - if ($object['category'] === 'Financial fraud') { - if (!$fTool->validateRouter($object['type'], $object['value'])) { - $object['validationIssue'] = true; - } - } - if ($object['objectType'] == 0) { - if (isset($object['ShadowAttribute'])) { - $shadowAttributeTemp = $object['ShadowAttribute']; - unset($object['ShadowAttribute']); - $eventArrayWithProposals[] = $object; - foreach ($shadowAttributeTemp as $kk => $shadowAttribute) { - $shadowAttribute['objectType'] = 1; - if ($kk == 0) $shadowAttribute['firstChild'] = true; - if (($kk + 1) == count($shadowAttributeTemp)) $shadowAttribute['lastChild'] = true; - $eventArrayWithProposals[] = $shadowAttribute; - } - } else { - $eventArrayWithProposals[] = $object; - } - } else { - $eventArrayWithProposals[] = $object; - } - unset($eventArray[$k]); - } - $event['objects'] = $eventArrayWithProposals; - $this->Warninglist = ClassRegistry::init('Warninglist'); - $warningLists = $this->Warninglist->fetchForEventView(); - if (!empty($warningLists)) $event = $this->Warninglist->setWarnings($event, $warningLists); - if ($filterType && $filterType == 'warning') { - foreach ($event['objects'] as $k => &$object) if (empty($object['warnings'])) unset($event['objects'][$k]); - $event['objects'] = array_values($event['objects']); - } $params = $customPagination->applyRulesOnArray($event['objects'], $passedArgs, 'events', 'category'); - $params['total_elements'] = $totalElements; + $params['total_elements'] = count($event['objects']); + $event['Event']['warnings'] = $eventWarnings; return $params; } @@ -3426,6 +3666,37 @@ class Event extends AppModel { return $conditions; } + public function prepareEventForView() { + // workaround to get the event dates in to the attribute relations + $relatedDates = array(); + if (!empty($event['RelatedEvent'])) { + foreach ($event['RelatedEvent'] as $relation) { + $relatedDates[$relation['Event']['id']] = $relation['Event']['date']; + } + if (!empty($event['RelatedAttribute'])) { + foreach ($event['RelatedAttribute'] as $key => $relatedAttribute) { + foreach ($relatedAttribute as $key2 => $relation) { + $event['RelatedAttribute'][$key][$key2]['date'] = $relatedDates[$relation['id']]; + } + } + } + } + $dataForView = array( + 'Attribute' => array('attrDescriptions', 'typeDefinitions', 'categoryDefinitions', 'distributionDescriptions', 'distributionLevels', 'shortDist'), + 'Event' => array('fieldDescriptions') + ); + foreach ($dataForView as $m => $variables) { + if ($m === 'Event') { + $currentModel = $this->Event; + } else if ($m === 'Attribute') { + $currentModel = $this->Event->Attribute; + } + foreach ($variables as $alias => $variable) { + $this->set($alias, $currentModel->{$variable}); + } + } + } + public function cacheSgids($user, $useCache = false) { if ($useCache && isset($this->__assetCache['sgids'])) { return $this->__assetCache['sgids']; @@ -3514,4 +3785,16 @@ class Event extends AppModel { private function __destroyCaches() { $this->__assetCache = array(); } + + public function unpublishEvent($id) { + $event = $this->find('first', array( + 'recursive' => -1, + 'conditions' => array('Event.id' => $id) + )); + if (empty($event)) return false; + $event['Event']['published'] = 0; + $date = new DateTime(); + $event['Event']['timestamp'] = $date->getTimestamp(); + return $this->save($event); + } } diff --git a/app/Model/MispObject.php b/app/Model/MispObject.php new file mode 100644 index 000000000..e6904ce81 --- /dev/null +++ b/app/Model/MispObject.php @@ -0,0 +1,556 @@ + array( // TODO Audit, logable + 'userModel' => 'User', + 'userKey' => 'user_id', + 'change' => 'full'), + ); + + public $belongsTo = array( + 'Event' => array( + 'className' => 'Event', + 'foreignKey' => 'event_id' + ), + 'SharingGroup' => array( + 'className' => 'SharingGroup', + 'foreignKey' => 'sharing_group_id' + ), + 'ObjectTemplate' => array( + 'className' => 'ObjectTemplate', + 'foreignKey' => false, + 'dependent' => false, + 'conditions' => array('MispObject.template_uuid' => 'ObjectTemplate.uuid') + ) + ); + + public $hasMany = array( + 'Attribute' => array( + 'className' => 'Attribute', + 'dependent' => true, + ), + 'ObjectReference' => array( + 'className' => 'ObjectReference', + 'dependent' => true, + 'foreignKey' => 'object_id' + ), + ); + + public $validate = array( + ); + + public function beforeValidate($options = array()) { + parent::beforeValidate(); + if (empty($this->data[$this->alias]['comment'])) { + $this->data[$this->alias]['comment'] = ""; + } + // generate UUID if it doesn't exist + if (empty($this->data[$this->alias]['uuid'])) { + $this->data[$this->alias]['uuid'] = CakeText::uuid(); + } + // generate timestamp if it doesn't exist + if (empty($this->data[$this->alias]['timestamp'])) { + $date = new DateTime(); + $this->data[$this->alias]['timestamp'] = $date->getTimestamp(); + } + if (empty($this->data[$this->alias]['template_version'])) { + $this->data[$this->alias]['template_version'] = 1; + } + if (isset($this->data[$this->alias]['deleted']) && empty($this->data[$this->alias]['deleted'])) { + $this->data[$this->alias]['deleted'] = 0; + } + if (!isset($this->data[$this->alias]['distribution']) || $this->data['Object']['distribution'] != 4) $this->data['Object']['sharing_group_id'] = 0; + if (!isset($this->data[$this->alias]['distribution'])) $this->data['Object']['distribution'] = 5; + return true; + } + + public function afterDelete() { + if (!empty($this->data[$this->alias]['id'])) { + $this->ObjectReference->deleteAll( + array( + 'ObjectReference.referenced_type' => 1, + 'ObjectReference.referenced_id' => $this->data[$this->alias]['id'], + ), + false + ); + } + } + + public function saveObject($object, $eventId, $template, $user, $errorBehaviour = 'drop') { + $this->create(); + $templateFields = array( + 'name' => 'name', + 'meta-category' => 'meta-category', + 'description' => 'description', + 'template_version' => 'version', + 'template_uuid' => 'uuid' + ); + foreach ($templateFields as $k => $v) { + $object['Object'][$k] = $template['ObjectTemplate'][$v]; + } + $object['Object']['event_id'] = $eventId; + $result = false; + if ($this->save($object)) { + $id = $this->id; + foreach ($object['Attribute'] as $k => $attribute) { + $object['Attribute'][$k]['object_id'] = $id; + } + $result = $this->Attribute->saveAttributes($object['Attribute']); + } else { + $result = $this->validationErrors; + } + return $id; + } + + public function buildEventConditions($user, $sgids = false) { + if ($user['Role']['perm_site_admin']) return array(); + if ($sgids == false) { + $sgsids = $this->SharingGroup->fetchAllAuthorised($user); + } + return array( + 'OR' => array( + array( + 'AND' => array( + 'Event.distribution >' => 0, + 'Event.distribution <' => 4, + Configure::read('MISP.unpublishedprivate') ? array('Event.published' => 1) : array(), + ), + ), + array( + 'AND' => array( + 'Event.sharing_group_id' => $sgids, + 'Event.distribution' => 4, + Configure::read('MISP.unpublishedprivate') ? array('Event.published' => 1) : array(), + ) + ) + ) + ); + } + + public function buildConditions($user, $sgids = false) { + $conditions = array(); + if (!$user['Role']['perm_site_admin']) { + if ($sgids === false) { + $sgsids = $this->SharingGroup->fetchAllAuthorised($user); + } + $conditions = array( + 'AND' => array( + 'OR' => array( + array( + 'AND' => array( + 'Event.org_id' => $user['org_id'], + ) + ), + array( + 'AND' => array( + $this->buildEventConditions($user, $sgids), + 'OR' => array( + 'Object.distribution' => array('1', '2', '3', '5'), + 'AND '=> array( + 'Object.distribution' => 4, + 'Object.sharing_group_id' => $sgsids, + ) + ) + ) + ) + ) + ) + ); + } + return $conditions; + } + + + // Method that fetches all objects + // very flexible, it's basically a replacement for find, with the addition that it restricts access based on user + // options: + // fields + // contain + // conditions + // order + // group + public function fetchObjects($user, $options = array()) { + $sgsids = $this->SharingGroup->fetchAllAuthorised($user); + $params = array( + 'conditions' => $this->buildConditions($user), + 'recursive' => -1, + 'contain' => array( + 'Event' => array( + 'fields' => array('id', 'info', 'org_id', 'orgc_id'), + ), + 'Attribute' => array( + 'conditions' => array( + 'OR' => array( + array( + 'Event.org_id' => $user['org_id'], + ), + array( + 'OR' => array( + 'Attribute.distribution' => array(1, 2, 3, 5), + array( + 'Attribute.distribution' => 4, + 'Attribute.sharing_group_id' => $sgids + ) + ) + ) + ) + ), + 'ShadowAttribute', + 'AttributeTag' => array( + 'Tag' + ) + ) + ) + ); + if (empty($options['includeAllTags'])) $params['contain']['Attribute']['AttributeTag']['Tag']['conditions']['exportable'] = 1; + if (isset($options['contain'])) $params['contain'] = array_merge_recursive($params['contain'], $options['contain']); + else $option['contain']['Event']['fields'] = array('id', 'info', 'org_id', 'orgc_id'); + if (Configure::read('MISP.proposals_block_attributes') && isset($options['conditions']['AND']['Attribute.to_ids']) && $options['conditions']['AND']['Attribute.to_ids'] == 1) { + $this->Attribute->bindModel(array('hasMany' => array('ShadowAttribute' => array('foreignKey' => 'old_id')))); + $proposalRestriction = array( + 'ShadowAttribute' => array( + 'conditions' => array( + 'AND' => array( + 'ShadowAttribute.deleted' => 0, + 'OR' => array( + 'ShadowAttribute.proposal_to_delete' => 1, + 'ShadowAttribute.to_ids' => 0 + ) + ) + ), + 'fields' => array('ShadowAttribute.id') + ) + ); + $params['contain'] = array_merge($params['contain']['Attribute'], $proposalRestriction); + } + if (isset($options['fields'])) $params['fields'] = $options['fields']; + if (isset($options['conditions'])) $params['conditions']['AND'][] = $options['conditions']; + if (isset($options['order'])) $params['order'] = $options['order']; + if (!isset($options['withAttachments'])) $options['withAttachments'] = false; + else ($params['order'] = array()); + if (!isset($options['enforceWarninglist'])) $options['enforceWarninglist'] = false; + if (!$user['Role']['perm_sync'] || !isset($options['deleted']) || !$options['deleted']) $params['contain']['Attribute']['conditions']['AND']['Attribute.deleted'] = 0; + if (isset($options['group'])) { + $params['group'] = array_merge(array('Object.id'), $options['group']); + } + if (Configure::read('MISP.unpublishedprivate')) $params['conditions']['AND'][] = array('OR' => array('Event.published' => 1, 'Event.orgc_id' => $user['org_id'])); + $results = $this->find('all', $params); + if ($options['enforceWarninglist']) { + $this->Warninglist = ClassRegistry::init('Warninglist'); + $warninglists = $this->Warninglist->fetchForEventView(); + } + $results = array_values($results); + $proposals_block_attributes = Configure::read('MISP.proposals_block_attributes'); + foreach ($results as $key => $objects) { + foreach ($objects as $key2 => $attribute) { + if ($options['enforceWarninglist'] && !$this->Warninglist->filterWarninglistAttributes($warninglists, $attribute['Attribute'], $this->Warninglist)) { + unset($results[$key][$key2]); + continue; + } + if ($proposals_block_attributes) { + if (!empty($attribute['ShadowAttribute'])) { + unset($results[$key][$key2]); + } else { + unset($results[$key][$key2]['ShadowAttribute']); + } + } + if ($options['withAttachments']) { + if ($this->typeIsAttachment($attribute['Attribute']['type'])) { + $encodedFile = $this->base64EncodeAttachment($attribute['Attribute']); + $results[$key][$key2]['Attribute']['data'] = $encodedFile; + } + } + } + } + return $results; + } + + /* + * Prepare the template form view's data, setting defaults, sorting elements + */ + public function prepareTemplate($template, $request = array()) { + $temp = array(); + usort($template['ObjectTemplateElement'], function($a, $b) { + return $a['ui-priority'] < $b['ui-priority']; + }); + $request_rearranged = array(); + $template_object_elements = $template['ObjectTemplateElement']; + unset($template['ObjectTemplateElement']); + if (!empty($request['Attribute'])) { + foreach ($request['Attribute'] as $attribute) { + $request_rearranged[$attribute['object_relation']][] = $attribute; + } + } + foreach ($template_object_elements as $k => $v) { + if (empty($request_rearranged[$v['object_relation']])) { + if (isset($this->Event->Attribute->typeDefinitions[$v['type']])) { + $v['default_category'] = $this->Event->Attribute->typeDefinitions[$v['type']]['default_category']; + $v['to_ids'] = $this->Event->Attribute->typeDefinitions[$v['type']]['to_ids']; + if (empty($v['categories'])) { + $v['categories'] = array(); + foreach ($this->Event->Attribute->categoryDefinitions as $catk => $catv) { + if (in_array($v['type'], $catv['types'])) { + $v['categories'][] = $catk; + } + } + } + $template['ObjectTemplateElement'][] = $v; + } else { + $template['warnings'][] = 'Missing attribute type "' . $v['type'] . '" found. Omitted template element ("' . $template_object_elements[$k]['object_relation'] . '") that would not pass validation due to this.'; + } + } else { + foreach($request_rearranged[$v['object_relation']] as $request_item) { + if (isset($this->Event->Attribute->typeDefinitions[$v['type']])) { + $v['default_category'] = $request_item['category']; + $v['value'] = $request_item['value']; + $v['to_ids'] = $request_item['to_ids']; + $v['comment'] = $request_item['comment']; + if (!empty($request_item['uuid'])) $v['uuid'] = $request_item['uuid']; + if (isset($request_item['data'])) $v['data'] = $request_item['data']; + if (empty($v['categories'])) { + $v['categories'] = array(); + foreach ($this->Event->Attribute->categoryDefinitions as $catk => $catv) { + if (in_array($v['type'], $catv['types'])) { + $v['categories'][] = $catk; + } + } + } + $template['ObjectTemplateElement'][] = $v; + } else { + $template['warnings'][] = 'Missing attribute type "' . $v['type'] . '" found. Omitted template element ("' . $template_object_elements[$k]['object_relation'] . '") that would not pass validation due to this.'; + } + } + } + } + return $template; + } + + /* + * Clean the attribute list up from artifacts introduced by the object form + */ + public function attributeCleanup($attributes) { + if (empty($attributes['Attribute'])) return 'No attribute data found'; + foreach ($attributes['Attribute'] as $k => $attribute) { + if (isset($attribute['save']) && $attribute['save'] == 0) { + unset($attributes['Attribute'][$k]); + continue; + } + if (isset($attribute['value_select'])) { + if ($attribute['value_select'] !== 'Enter value manually') { + $attributes['Attribute'][$k]['value'] = $attribute['value_select']; + } + unset($attributes['Attribute'][$k]['value_select']); + } + if (isset($attribute['Attachment'])) { + // Check if there were problems with the file upload + // only keep the last part of the filename, this should prevent directory attacks + $filename = basename($attribute['Attachment']['name']); + $tmpfile = new File($attribute['Attachment']['tmp_name']); + if ((isset($attribute['Attachment']['error']) && $attribute['Attachment']['error'] == 0) || + (!empty($attribute['Attachment']['tmp_name']) && $attribute['Attachment']['tmp_name'] != 'none') + ) { + if (!is_uploaded_file($tmpfile->path)) + throw new InternalErrorException('PHP says file was not uploaded. Are you attacking me?'); + } else { + return 'Issues with the file attachment for the ' . $attribute['object_relation'] . ' attribute. The error code returned is ' . $attribute['Attachment']['error']; + } + $attributes['Attribute'][$k]['value'] = $attribute['Attachment']['name']; + unset($attributes['Attribute'][$k]['Attachment']); + $attributes['Attribute'][$k]['encrypt'] = $attribute['type'] == 'malware-sample' ? 1 : 0; + $attributes['Attribute'][$k]['data'] = base64_encode($tmpfile->read()); + $tmpfile->delete(); + $tmpfile->close(); + } + unset($attributes['Attribute'][$k]['save']); + } + return $attributes; + } + + public function deltaMerge($object, $objectToSave) { + $object['Object']['comment'] = $objectToSave['Object']['comment']; + $object['Object']['distribution'] = $objectToSave['Object']['distribution']; + if ($object['Object']['distribution'] == 4) { + $object['Object']['sharing_group_id'] = $objectToSave['Object']['sharing_group_id']; + } + $date = new DateTime(); + $object['Object']['timestamp'] = $date->getTimestamp(); + $this->save($object); + $checkFields = array('category', 'value', 'to_ids', 'distribution', 'sharing_group_id', 'comment'); + foreach ($objectToSave['Attribute'] as $newKey => $newAttribute) { + foreach ($object['Attribute'] as $origKey => $originalAttribute) { + if (!empty($newAttribute['uuid'])) { + if ($newAttribute['uuid'] == $originalAttribute['uuid']) { + $different = false; + foreach ($checkFields as $f) { + if ($f == 'sharing_group_id' && empty($newAttribute[$f])) { + $newAttribute[$f] = 0; + } + if ($newAttribute[$f] != $originalAttribute[$f]) $different = true; + } + if ($different) { + $newAttribute['id'] = $originalAttribute['id']; + $newAttribute['event_id'] = $object['Object']['event_id']; + $newAttribute['object_id'] = $object['Object']['id']; + $newAttribute['timestamp'] = $date->getTimestamp(); + $result = $this->Event->Attribute->save(array('Attribute' => $newAttribute), array( + 'category', + 'value', + 'to_ids', + 'distribution', + 'sharing_group_id', + 'comment', + 'timestamp', + 'object_id', + 'event_id' + )); + } + unset($object['Attribute'][$origKey]); + continue 2; + } + } + } + $this->Event->Attribute->create(); + $newAttribute['event_id'] = $object['Object']['event_id']; + $newAttribute['object_id'] = $object['Object']['id']; + $this->Event->Attribute->save($newAttribute); + $attributeArrays['add'][] = $newAttribute; + unset($objectToSave['Attribute'][$newKey]); + } + foreach ($object['Attribute'] as $origKey => $originalAttribute) { + $originalAttribute['deleted'] = 1; + $this->Event->Attribute->save($originalAttribute); + } + return $this->id; + } + + public function captureObject($object, $eventId, $user, $log = false) { + $this->create(); + if (!isset($object['Object'])) { + $object = array('Object' => $object); + } + if (empty($log)) { + $log = ClassRegistry::init('Log'); + } + $object['Object']['event_id'] = $eventId; + if ($this->save($object)) { + $this->Event->unpublishEvent($eventId); + $objectId = $this->id; + $partialFails = array(); + foreach ($object['Object']['Attribute'] as $attribute) { + $this->Attribute->captureAttribute($attribute, $eventId, $user, $objectId, $log); + } + return true; + } else { + $log->create(); + $log->save(array( + 'org' => $user['Organisation']['name'], + 'model' => 'Object', + 'model_id' => 0, + 'email' => $user['email'], + 'action' => 'add', + 'user_id' => $user['id'], + 'title' => 'Object dropped due to validation for Event ' . $eventId . ' failed: ' . $object['Object']['name'], + 'change' => 'Validation errors: ' . json_encode($this->validationErrors) . ' Full Object: ' . json_encode($attribute), + )); + } + return 'fail'; + } + + public function editObject($object, $eventId, $user, $log) { + $object['event_id'] = $eventId; + if (isset($object['uuid'])) { + $existingObject = $this->find('first', array( + 'recursive' => -1, + 'conditions' => array('Object.uuid' => $object['uuid']) + )); + if ($existingObject['Object']['event_id'] != $eventId) { + $this->Log->create(); + $this->Log->save(array( + 'org' => $user['Organisation']['name'], + 'model' => 'Object', + 'model_id' => 0, + 'email' => $user['email'], + 'action' => 'edit', + 'user_id' => $user['id'], + 'title' => 'Duplicate UUID found in object', + 'change' => 'An object was blocked from being saved due to a duplicate UUID. The uuid in question is: ' . $object['uuid'] . '. This can also be due to the same object (or an object with the same UUID) existing in a different event)', + )); + return true; + } + if (empty($existingObject)) { + return $this->captureObject($object, $eventId, $user, $log); + } else { + if (isset($object['timestamp'])) { + if ($existingObject['Object']['timestamp'] >= $object['timestamp']) { + return true; + } + } else { + $object['timestamp'] = $dateObj->getTimestamp(); + } + } + } else { + return $this->captureObject($object, $eventId, $user, $log); + } + // At this point we have an existingObject that we can edit + $recoverFields = array( + 'name', + 'meta-category', + 'description', + 'template_uuid', + 'template_version', + 'distribution', + 'sharing_group_id', + 'comment', + 'deleted' + ); + foreach ($recoverFields as $rF) if (!isset($object[$rF])) $object[$rF] = $existingObject['Object'][$rF]; + $object['id'] = $existingObject['Object']['id']; + $object['uuid'] = $existingObject['Object']['uuid']; + $object['event_id'] = $eventId; + if ($object['distribution'] == 4) { + $object['sharing_group_id'] = $this->SharingGroup->captureSG($object['SharingGroup'], $user); + } + if (!$this->save($object)) { + $this->Log->create(); + $this->Log->save(array( + 'org' => $user['Organisation']['name'], + 'model' => 'Object', + 'model_id' => 0, + 'email' => $user['email'], + 'action' => 'edit', + 'user_id' => $user['id'], + 'title' => 'Attribute dropped due to validation for Event ' . $eventId . ' failed: ' . $object['Object']['name'], + 'change' => 'Validation errors: ' . json_encode($this->validationErrors) . ' Full Object: ' . json_encode($attribute), + )); + return $this->validationErrors; + } else { + $this->Event->unpublishEvent($eventId); + } + if (!empty($object['Attribute'])) { + foreach ($object['Attribute'] as $attribute) { + $result = $this->Attribute->editAttribute($attribute, $eventId, $user, $this->id, $log); + } + } + } + + public function updateTimestamp($id) { + $date = new DateTime(); + $object = $this->find('first', array( + 'recursive' => -1, + 'conditions' => array('Object.id' => $id) + )); + $object['Object']['timestamp'] = $date->getTimestamp(); + $result = $this->save($object); + return $result; + } +} diff --git a/app/Model/ObjectReference.php b/app/Model/ObjectReference.php new file mode 100644 index 000000000..a02dc4d04 --- /dev/null +++ b/app/Model/ObjectReference.php @@ -0,0 +1,216 @@ + array( // TODO Audit, logable + 'userModel' => 'User', + 'userKey' => 'user_id', + 'change' => 'full'), + ); + + public $belongsTo = array( + 'Object' => array( + 'className' => 'MispObject', + 'foreignKey' => 'object_id' + ), + 'ReferencedObject' => array( + 'className' => 'MispObject', + 'foreignKey' => false, + 'conditions' => array( + 'ReferencedObject.id' => 'ObjectReference.referenced_id', + 1 => 'ObjectReference.referenced_type' + ), + ), + 'ReferencedAttribute' => array( + 'className' => 'Attribute', + 'foreignKey' => false, + 'conditions' => array( + 'ReferencedAttribute.id' => 'ObjectReference.referenced_id', + 0 => 'ObjectReference.referenced_type' + ), + ) + ); + + + public $validate = array( + ); + + public function beforeValidate($options = array()) { + parent::beforeValidate(); + if (empty($this->data['ObjectReference']['uuid'])) { + $this->data['ObjectReference']['uuid'] = CakeText::uuid(); + } + $date = new DateTime(); + $this->data['ObjectReference']['timestamp'] = $date->getTimestamp(); + return true; + } + + public function updateTimestamps($id, $objectReference = false) { + if (!$objectReference) { + $objectReference = $this->find('first', array( + 'recursive' => -1, + 'conditions' => array('ObjectReference.id' => $id), + 'fields' => array('event_id', 'object_id') + )); + } + if (empty($objectReference)) return false; + if (!isset($objectReference['ObjectReference'])) { + $objectReference = array('ObjectReference' => $objectReference); + } + $this->Object->updateTimestamp($objectReference['ObjectReference']['object_id']); + $this->Object->Event->unpublishEvent($objectReference['ObjectReference']['event_id']); + } + + public function smartDelete($id, $hard = false) { + if ($hard) { + $result = $this->delete($id); + if ($result) { + $this->updateTimestamps($id); + } + return $result; + } else { + $reference = $this->find('first', array( + 'conditions' => array('ObjectReference.id' => $id), + 'recursive' => -1 + )); + if (empty($reference)) return array('Invalid object reference.'); + $reference['ObjectReference']['deleted'] = 1; + $result = $this->save($reference); + if ($result) { + $this->updateTimestamps($id); + return true; + } + return $this->validationErrors; + } + } + + public function smartSave($objectReference, $eventId) { + $sides = array('object', 'referenced'); + $data = array(); + foreach ($sides as $side) { + $data[$side] = $this->Object->find('first', array( + 'conditions' => array( + 'Object.uuid' => $objectReference[$side . '_uuid'], + 'Object.event_id' => $eventId + ), + 'recursive' => -1, + 'fields' => array('Object.id') + )); + if (empty($data[$side]) && $side == 'referenced') { + $data[$side] = $this->Attribute->find('first', array( + 'conditions' => array( + 'Attribute.uuid' => $objectReference[$side . '_uuid'], + 'Attribute.event_id' => $eventId + ), + 'recursive' => -1, + 'fields' => array('Attribute.id') + )); + $referenced_id = $data[$side]['Attribute']['id']; + $referenced_type = 0; + } else if (!empty($data[$side]) && $side == 'referenced') { + $referenced_id = $data[$side]['Object']['id']; + $referenced_type = 1; + } else if (!empty($data[$side]) && $side = 'object') { + $object_id = $data[$side]['Object']['id']; + } else { + return 'Invalid ' . $side . ' uuid'; + } + } + $this->create(); + $objectReference['referenced_type'] = $referenced_type; + $objectReference['referenced_id'] = $referenced_id; + $objectReference['object_id'] = $object_id; + $objectReference['event_id'] = $eventId; + $result = $this->save(array('ObjectReference' => $objectReference)); + if (!$result) { + return $this->validationErrors; + } else { + $this->updateTimestamps($this->id, $objectReference); + } + return true; + } + + public function captureReference($reference, $eventId, $user, $log = false) { + if ($log == false) { + $log = ClassRegistry::init('Log'); + } + if (isset($reference['uuid'])) { + $existingReference = $this->find('first', array( + 'conditions' => array('ObjectReference.uuid' => $reference['uuid']), + 'recursive' => -1 + )); + if (!empty($existingReference)) { + // ObjectReference not newer than existing one + if (isset($reference['timestamp']) && $reference['timestamp'] <= $existingReference['ObjectReference']['timestamp']) { + return true; + } + $fieldsToUpdate = array('timestamp', 'relationship_type', 'comment', 'deleted'); + foreach ($fieldsToUpdate as $field) { + if (isset($reference[$field])) $existingReference['ObjectReference'][$field] = $reference[$field]; + } + $result = $this->save($existingReference); + if ($result) { + return true; + } else { + return $this->validationErrors; + } + } + } + if (isset($reference['object_uuid'])) { + $conditions = array('Object.uuid' => $reference['object_uuid']); + } else if (isset($reference['object_id'])) { + $conditions = array('Object.id' => $reference['object_id']); + } else { + return true; + } + $sourceObject = $this->Object->find('first', array( + 'recursive' => -1, + 'conditions' => $conditions + )); + if (isset($reference['referenced_uuid'])) { + $conditions[0] = array('Attribute.uuid' => $reference['referenced_uuid']); + $conditions[1] = array('Object.uuid' => $reference['referenced_uuid']); + } else if (isset($reference['object_id'])) { + if ($reference['referenced_type'] == 1) { + $conditions[0] = array('Attribute.id' => $reference['referenced_id']); + $conditions[1] = array('Object.id' => $reference['referenced_id']); + } else { + $conditions = false; + } + } else { + return true; + } + if ($conditions) { + $referencedObject = $this->Object->find('first', array( + 'recursive' => -1, + 'conditions' => $conditions[1] + )); + } + if (!isset($referencedObject)) { + $referencedObject = $this->Attribute->find('first', array( + 'recursive' => -1, + 'conditions' => $conditions[0] + )); + if (empty($referencedObject)) return true; + $referenced_type = 0; + } else { + $referenced_type = 1; + } + $objectTypes = array('Attribute', 'Object'); + if ($sourceObject['Object']['event_id'] != $eventId) return true; + if ($referencedObject[$objectTypes[$referenced_type]]['event_id'] != $eventId) return true; + $this->create(); + unset($reference['id']); + $reference['referenced_type'] = $referenced_type; + $reference['object_id'] = $sourceObject['Object']['id']; + $reference['referenced_id'] = $referencedObject[$objectTypes[$referenced_type]]['id']; + $reference['referenced_uuid'] = $referencedObject[$objectTypes[$referenced_type]]['uuid']; + $reference['object_uuid'] = $sourceObject['Object']['uuid']; + $reference['event_id'] = $eventId; + $this->save(array('ObjectReference' => $reference)); + return true; + } +} diff --git a/app/Model/ObjectRelationship.php b/app/Model/ObjectRelationship.php new file mode 100644 index 000000000..ba48393b6 --- /dev/null +++ b/app/Model/ObjectRelationship.php @@ -0,0 +1,57 @@ + array( // TODO Audit, logable + 'userModel' => 'User', + 'userKey' => 'user_id', + 'change' => 'full'), + ); + + public $validate = array( + 'name' => array( + 'unique' => array( + 'rule' => 'isUnique', + 'message' => 'A relationship with this name already exists.' + ), + 'valueNotEmpty' => array( + 'rule' => array('valueNotEmpty'), + ), + ), + ); + + + public function beforeValidate($options = array()) { + parent::beforeValidate(); + return true; + } + + public function afterFind($results, $primary = false) { + foreach ($results as $k => $result) { + if (!empty($results[$k]['ObjectRelationship']['format'])) { + $results[$k]['ObjectRelationship']['format'] = json_decode($results[$k]['ObjectRelationship']['format'], true); + } + } + return $results; + } + + public function update() { + $relationsFile = APP . 'files/misp-objects/relationships/definition.json'; + if (file_exists($relationsFile)) { + $file = new File($relationsFile); + $relations = json_decode($file->read(), true); + if (!isset($relations['version'])) $relations['version'] = 1; + $this->deleteAll(array('version <' => $relations['version'])); + foreach ($relations['values'] as $k => $relation) { + $relation['format'] = json_encode($relation['format'], true); + $relation['version'] = $relations['version']; + $this->create(); + $this->save($relation); + } + } + return true; + } +} diff --git a/app/Model/ObjectTemplate.php b/app/Model/ObjectTemplate.php new file mode 100644 index 000000000..2ab91d0f8 --- /dev/null +++ b/app/Model/ObjectTemplate.php @@ -0,0 +1,222 @@ + array( // TODO Audit, logable + 'userModel' => 'User', + 'userKey' => 'user_id', + 'change' => 'full'), + ); + + public $belongsTo = array( + 'User' => array( + 'className' => 'User', + 'foreignKey' => 'user_id' + ), + 'Organisation' => array( + 'className' => 'Organisation', + 'foreignKey' => 'org_id' + ) + ); + public $hasMany = array( + 'ObjectTemplateElement' => array( + 'className' => 'ObjectTemplateElement', + 'dependent' => true, + ) + ); + public $validate = array( + ); + + public function afterFind($results, $primary = false) { + foreach ($results as $k => $result) { + if (isset($results[$k]['ObjectTemplate']['requirements'])) { + $results[$k]['ObjectTemplate']['requirements'] = json_decode($results[$k]['ObjectTemplate']['requirements'], true); + } + } + return $results; + } + + public function beforeSave($options = array()) { + $this->data['ObjectTemplate']['requirements'] = empty($this->data['ObjectTemplate']['requirements']) ? '[]' : json_encode($this->data['ObjectTemplate']['requirements']); + return true; + } + + public function update($user) { + $objectsDir = APP . 'files/misp-objects/objects'; + $directories = glob($objectsDir . '/*', GLOB_ONLYDIR); + foreach ($directories as $k => $dir) { + $dir = str_replace($objectsDir, '', $dir); + $directories[$k] = $dir; + } + $updated = array(); + foreach ($directories as $dir) { + if (!file_exists($objectsDir . DS . $dir . DS . 'definition.json')) { + continue; + } + $file = new File($objectsDir . DS . $dir . DS . 'definition.json'); + $template = json_decode($file->read(), true); + $file->close(); + if (!isset($template['version'])) $template['version'] = 1; + $current = $this->find('first', array( + 'fields' => array('MAX(version) AS version', 'uuid'), + 'conditions' => array('uuid' => $template['uuid']), + 'recursive' => -1, + 'group' => array('uuid') + )); + if (!empty($current)) $current['ObjectTemplate']['version'] = $current[0]['version']; + if (empty($current) || $template['version'] > $current['ObjectTemplate']['version']) { + $result = $this->__updateObjectTemplate($template, $current, $user); + if ($result === true) { + $temp = array('name' => $template['name'], 'new' => $template['version']); + if (!empty($current)) $temp['old'] = $current['ObjectTemplate']['version']; + $updated['success'][] = $temp; + } else { + $updated['fails'][] = array('name' => $template['name'], 'fail' => json_encode($result)); + } + } + } + return $updated; + } + + private function __updateObjectTemplate($template, $current, $user) { + $success = false; + $template['requirements'] = array(); + $requirementFields = array('required', 'requiredOneOf'); + foreach ($requirementFields as $field) { + if (isset($template[$field])) { + $template['requirements'][$field] = $template[$field]; + } + } + $template['user_id'] = $user['id']; + $template['org_id'] = $user['org_id']; + $template['fixed'] = 1; + $this->create(); + $result = $this->save($template); + if (!$result) { + return $this->validationErrors; + } + $id = $this->id; + $this->setActive($id); + $fieldsToCompare = array('object_relation', 'type', 'ui-priority', 'categories', 'sane_default', 'values_list', 'multiple'); + foreach ($template['attributes'] as $k => $attribute) { + $attribute['object_relation'] = $k; + $attribute = $this->__convertJSONToElement($attribute); + $this->ObjectTemplateElement->create(); + $attribute['object_template_id'] = $id; + $result = $this->ObjectTemplateElement->save(array('ObjectTemplateElement' => $attribute)); + } + return true; + } + + private function __convertJSONToElement($attribute) { + $result = array(); + $translation_table = array( + 'misp-usage-frequency' => 'frequency', + 'misp-attribute' => 'type', + 'description' => 'description', + 'ui-priority' => 'ui-priority', + 'type' => 'type', + 'disable_correlation' => 'disable_correlation', + 'object_relation' => 'object_relation', + 'categories' => 'categories', + 'sane_default' => 'sane_default', + 'values_list' => 'values_list', + 'multiple' => 'multiple' + ); + foreach ($translation_table as $from => $to) { + if (isset($attribute[$from])) { + $result[$to] = $attribute[$from]; + } + } + return $result; + } + + public function checkTemplateConformity($template, $attributes) { + if (!empty($template['ObjectTemplate']['requirements'])) { + // check for all required attributes + if (!empty($template['ObjectTemplate']['requirements']['required'])) { + foreach ($template['ObjectTemplate']['requirements']['required'] as $requiredField) { + $found = false; + foreach ($attributes['Attribute'] as $attribute) { + if ($attribute['object_relation'] == $requiredField) { + $found = true; + } + } + if (!$found) return 'Could not save the object as a required attribute is not set (' . $requiredField . ')'; + } + } + // check for all required one of attributes + if (!empty($template['ObjectTemplate']['requirements']['requiredOneOf'])) { + $found = false; + foreach ($template['ObjectTemplate']['requirements']['requiredOneOf'] as $requiredField) { + foreach ($attributes['Attribute'] as $attribute) { + if ($attribute['object_relation'] == $requiredField) { + $found = true; + } + } + } + if (!$found) return 'Could not save the object as it requires at least one of the following attributes to be set: ' . implode(', ', $template['ObjectTemplate']['requirements']['requiredOneOf']); + } + } + // check the multiple flag is adhered to + foreach ($template['ObjectTemplateElement'] as $template_attribute) { + if ($template_attribute['multiple'] !== true) { + $found_relations = array(); + foreach ($attributes['Attribute'] as $attribute) { + if ($attribute['object_relation'] == $template_attribute['object_relation']) { + if (!isset($found_relations[$attribute['object_relation']])) { + $found_relations[$attribute['object_relation']] = true; + } else { + return 'Could not save the object as a unique relationship within the object was assigned to more than one attribute. This is only allowed if the multiple flag is set in the object template.'; + } + } + } + } + } + return true; + } + + // simple test to see if there are any object templates - if not trigger update + public function populateIfEmpty($user) { + $result = $this->find('first', array( + 'recursive' => -1, + 'fields' => array('ObjectTemplate.id') + )); + if (empty($result)) { + $this->update($user); + } + return true; + } + + public function setActive($id) { + $template = $this->find('first', array( + 'recursive' => -1, + 'conditions' => array('ObjectTemplate.id' => $id) + )); + if (empty($template)) return false; + if ($template['ObjectTemplate']['active']) { + $template['ObjectTemplate']['active'] = 0; + $this->save($template); + return 0; + } + $similar_templates = $this->find('all', array( + 'recursive' => -1, + 'conditions' => array( + 'ObjectTemplate.uuid' => $template['ObjectTemplate']['uuid'], + 'NOT' => array( + 'ObjectTemplate.id' => $template['ObjectTemplate']['id'] + ) + ) + )); + $template['ObjectTemplate']['active'] = 1; + $this->save($template); + foreach ($similar_templates as $st) { + $st['ObjectTemplate']['active'] = 0; + $this->save($st); + } + return 1; + } +} diff --git a/app/Model/ObjectTemplateElement.php b/app/Model/ObjectTemplateElement.php new file mode 100644 index 000000000..202d81474 --- /dev/null +++ b/app/Model/ObjectTemplateElement.php @@ -0,0 +1,45 @@ + array( // TODO Audit, logable + 'userModel' => 'User', + 'userKey' => 'user_id', + 'change' => 'full'), + ); + + public $belongsTo = array( + ); + + public $validate = array( + ); + + public function afterFind($results, $primary = false) { + foreach ($results as $k => $result) { + if (isset($results[$k]['ObjectTemplateElement']['categories'])) { + $results[$k]['ObjectTemplateElement']['categories'] = json_decode($results[$k]['ObjectTemplateElement']['categories'], true); + } + if (isset($results[$k]['ObjectTemplateElement']['values_list'])) { + $results[$k]['ObjectTemplateElement']['values_list'] = json_decode($results[$k]['ObjectTemplateElement']['values_list'], true); + } + if (isset($results[$k]['ObjectTemplateElement']['sane_default'])) { + $results[$k]['ObjectTemplateElement']['sane_default'] = json_decode($results[$k]['ObjectTemplateElement']['sane_default'], true); + } + } + return $results; + } + + public function beforeSave($options = array()) { + if (empty($this->data['ObjectTemplateElement']['description'])) { + $this->data['ObjectTemplateElement']['description'] = ''; + } + $json_fields = array('categories', 'values_list', 'sane_default'); + foreach ($json_fields as $field) { + $this->data['ObjectTemplateElement'][$field] = empty($this->data['ObjectTemplateElement'][$field]) ? '[]' : json_encode($this->data['ObjectTemplateElement'][$field]); + } + return true; + } +} diff --git a/app/Model/Role.php b/app/Model/Role.php index ecd4c85f9..2e37840cf 100644 --- a/app/Model/Role.php +++ b/app/Model/Role.php @@ -66,7 +66,8 @@ class Role extends AppModel { 'perm_template' => array('id' => 'RolePermTemplate', 'text' => 'Template Editor', 'readonlyenabled' => false), 'perm_sharing_group' => array('id' => 'RolePermSharingGroup', 'text' => 'Sharing Group Editor', 'readonlyenabled' => false), 'perm_delegate' => array('id' => 'RolePermDelegate', 'text' => 'Delegations Access', 'readonlyenabled' => false), - 'perm_sighting' => array('id' => 'RolePermSighting', 'text' => 'Sighting Creator', 'readonlyenabled' => true) + 'perm_sighting' => array('id' => 'RolePermSighting', 'text' => 'Sighting Creator', 'readonlyenabled' => true), + 'perm_object_template' => array('id' => 'RolePermObjectTemplate', 'text' => 'Object Template Editor', 'readonlyenabled' => false), ); public $premissionLevelName = array('Read Only', 'Manage Own Events', 'Manage Organisation Events', 'Manage and Publish Organisation Events'); diff --git a/app/Model/Server.php b/app/Model/Server.php index 342de9496..1ad6c4178 100644 --- a/app/Model/Server.php +++ b/app/Model/Server.php @@ -137,6 +137,15 @@ class Server extends AppModel { 'type' => 'boolean', 'null' => true ), + 'max_correlations_per_event' => array( + 'level' => 1, + 'description' => 'Sets the maximum number of correlations that can be fetched with a single event. For extreme edge cases this can prevent memory issues. The default value is 5k.', + 'value' => 5000, + 'errorMessage' => '', + 'test' => 'testForNumeric', + 'type' => 'numeric', + 'null' => true + ), 'maintenance_message' => array( 'level' => 2, 'description' => 'The message that users will see if the instance is not live.', diff --git a/app/Model/Warninglist.php b/app/Model/Warninglist.php index 691ece79d..4ea65e62c 100644 --- a/app/Model/Warninglist.php +++ b/app/Model/Warninglist.php @@ -232,6 +232,23 @@ class Warninglist extends AppModel{ return $warninglists; } + public function checkForWarning($object, &$eventWarnings, $warningLists) { + if ($object['to_ids']) { + foreach ($warningLists as $list) { + if (in_array('ALL', $list['types']) || in_array($object['type'], $list['types'])) { + $result = $this->__checkValue($list['values'], $object['value'], $object['type'], $list['Warninglist']['type']); + if (!empty($result)) { + $object['warnings'][$result][] = $list['Warninglist']['name']; + if (!in_array($list['Warninglist']['name'], $eventWarnings)) { + $eventWarnings[$list['Warninglist']['id']] = $list['Warninglist']['name']; + } + } + } + } + } + return $object; + } + public function setWarnings(&$event, &$warninglists) { if (empty($event['objects'])) return $event; $eventWarnings = array(); diff --git a/app/Plugin/SysLogLogable/Model/Behavior/SysLogLogableBehavior.php b/app/Plugin/SysLogLogable/Model/Behavior/SysLogLogableBehavior.php index 1264edf7c..1ac306936 100644 --- a/app/Plugin/SysLogLogable/Model/Behavior/SysLogLogableBehavior.php +++ b/app/Plugin/SysLogLogable/Model/Behavior/SysLogLogableBehavior.php @@ -65,6 +65,9 @@ class SysLogLogableBehavior extends LogableBehavior { foreach ( $Model->data[$Model->alias] as $key => $value ) { if (isset($Model->data[$Model->alias][$Model->primaryKey]) && !empty($this->old) && isset($this->old[$Model->alias][$key])) { $old = $this->old[$Model->alias][$key]; + if (is_array($old)) { + $old = json_encode($old, true); + } } else { $old = ''; } diff --git a/app/View/Attributes/add_attachment.ctp b/app/View/Attributes/add_attachment.ctp index 97e0ef3ff..9dc1e9e83 100644 --- a/app/View/Attributes/add_attachment.ctp +++ b/app/View/Attributes/add_attachment.ctp @@ -61,9 +61,16 @@ 'type' => 'checkbox', 'checked' => false, 'data-content' => isset($attrDescriptions['signature']['formdesc']) ? $attrDescriptions['signature']['formdesc'] : $attrDescriptions['signature']['desc'], - 'label' => 'IDS (encrypt and hash)', - // 'after' => $this->Html->div('forminfo', 'Tick this box to neutralize the sample. Every malware sample will be zipped with the password "infected"', ''), - //'after' => '
Tick this box to neutralize the sample. Every malware sample will be zipped with the password "infected"', + 'label' => 'IDS (encrypt and hash)' + )); + ?> +
+ Form->input('advanced', array( + 'type' => 'checkbox', + 'checked' => false, + 'data-content' => isset($attrDescriptions['signature']['formdesc']) ? $attrDescriptions['signature']['formdesc'] : $attrDescriptions['signature']['desc'], + 'label' => 'Advanced extraction (if installed)', )); ?> diff --git a/app/View/Attributes/edit.ctp b/app/View/Attributes/edit.ctp index 2b8bfc414..555749ba3 100644 --- a/app/View/Attributes/edit.ctp +++ b/app/View/Attributes/edit.ctp @@ -8,10 +8,14 @@ 'empty' => '(choose one)', 'label' => 'Category ' . $this->element('formInfo', array('type' => 'category')) )); - echo $this->Form->input('type', array( + $typeInputData = array( 'empty' => '(first choose category)', - 'label' => 'Type ' . $this->element('formInfo', array('type' => 'type')) - )); + 'label' => 'Type ' . $this->element('formInfo', array('type' => 'type')), + ); + if ($objectAttribute) { + $typeInputData[] = 'disabled'; + } + echo $this->Form->input('type', $typeInputData); ?>
Form->input('to_ids', array( 'label' => 'for Intrusion Detection System', )); - echo $this->Form->input('batch_import', array( - 'type' => 'checkbox', - )); + if (!$objectAttribute) { + echo $this->Form->input('batch_import', array( + 'type' => 'checkbox', + )); + } ?>

" id="warning-message">Warning: You are about to share data that is of a sensitive nature (Attribution / targeting data). Make sure that you are authorised to share this.

@@ -103,18 +109,24 @@ $(document).ready(function() { else $('#SGContainer').hide(); }); - $("#AttributeCategory").on('change', function(e) { - formCategoryChanged('Attribute'); - if ($(this).val() === 'Attribution' || $(this).val() === 'Targeting data') { - $("#warning-message").show(); - } else { - $("#warning-message").hide(); - } - if ($(this).val() === 'Internal reference') { - $("#AttributeDistribution").val('0'); - $('#SGContainer').hide(); - } - }); + + $("#AttributeCategory").on('change', function(e) { + formCategoryChanged('Attribute'); + if ($(this).val() === 'Attribution' || $(this).val() === 'Targeting data') { + $("#warning-message").show(); + } else { + $("#warning-message").hide(); + } + if ($(this).val() === 'Internal reference') { + $("#AttributeDistribution").val('0'); + $('#SGContainer').hide(); + } + }); + $("#AttributeCategory, #AttributeType, #AttributeDistribution").change(function() { initPopoverContent('Attribute'); diff --git a/app/View/Elements/Events/View/row_attribute.ctp b/app/View/Elements/Events/View/row_attribute.ctp new file mode 100644 index 000000000..eae0851c2 --- /dev/null +++ b/app/View/Elements/Events/View/row_attribute.ctp @@ -0,0 +1,267 @@ + + + + + + + + + + + + + + + + + +   + + +
+
+ +
+ + + +
:
+ +
+
+
+ +
+ + +
+ +
> + > + element('/Events/View/value_field', array('object' => $object, 'linkClass' => $linkClass)); + ?> + + 0, 2 => 1); + $valueParts = explode('|', $object['value']); + foreach ($components as $component => $valuePart) { + if (isset($object['warnings'][$component]) && isset($valueParts[$valuePart])) { + foreach ($object['warnings'][$component] as $warning) $temp .= '' . h($valueParts[$valuePart]) . ': ' . h($warning) . '
'; + } + } + echo '  '; + } + ?> +
+ + +
+ element('ajaxAttributeTags', array('attributeId' => $object['id'], 'attributeTags' => $object['AttributeTag'], 'tagAccess' => ($isSiteAdmin || $mayModify || $me['org_id'] == $event['Event']['org_id']) )); ?> +
+ + +
+
+   +
+ + + + > + + + + + + + + + +
+
+ +
+ + + +
+
class="inline-field-solid" ondblclick="activateField('Attribute', '', 'distribution', );"> + + + +
+ + element('/Events/View/sighting_field', array( + 'object' => $object, + 'tr_class' => $tr_class, + 'page' => $page + )); + endif; + ?> + + + + + +   + + + + + + +   + +   + + C + + + + + + + $proposal) { + echo $this->element('/Events/View/row_' . $proposal['objectType'], array( + 'object' => $proposal, + 'mayModify' => $mayModify, + 'mayChangeCorrelation' => $mayChangeCorrelation, + 'page' => $page, + 'fieldCount' => $fieldCount, + 'child' => $propKey == $lastElement ? 'last' : true, + 'objectContainer' => $child + )); + } + } +?> diff --git a/app/View/Elements/Events/View/row_object.ctp b/app/View/Elements/Events/View/row_object.ctp new file mode 100644 index 000000000..90bf015fc --- /dev/null +++ b/app/View/Elements/Events/View/row_object.ctp @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + Name: + +
+
+ Meta-category:
+ Description:
+ Template: +
+ element('/Events/View/row_object_reference', array( + 'deleted' => $deleted, + 'object' => $object + )); + if (!empty($object['referenced_by'])) { + echo $this->element('/Events/View/row_object_referenced_by', array( + 'deleted' => $deleted, + 'object' => $object + )); + } + ?> + + + +
+
class="inline-field-solid" ondblclick="activateField('', '', 'distribution', );"> + + +   +
+ +   +   + + + + + + + + + + $attribute) { + echo $this->element('/Events/View/row_' . $attribute['objectType'], array( + 'object' => $attribute, + 'mayModify' => $mayModify, + 'mayChangeCorrelation' => $mayChangeCorrelation, + 'page' => $page, + 'fieldCount' => $fieldCount, + 'child' => $attrKey == $lastElement ? 'last' : true + )); + } + } +?> diff --git a/app/View/Elements/Events/View/row_object_reference.ctp b/app/View/Elements/Events/View/row_object_reference.ctp new file mode 100644 index 000000000..7523da62e --- /dev/null +++ b/app/View/Elements/Events/View/row_object_reference.ctp @@ -0,0 +1,50 @@ +References: +' . $temp[1] . ''; + } + } + echo $refCount . ' '; + if (!empty($object['ObjectReference'])): +?> + + + +
+ +    + + + + +
+ +
diff --git a/app/View/Elements/Events/View/row_object_referenced_by.ctp b/app/View/Elements/Events/View/row_object_referenced_by.ctp new file mode 100644 index 000000000..5af76b072 --- /dev/null +++ b/app/View/Elements/Events/View/row_object_referenced_by.ctp @@ -0,0 +1,32 @@ +Referenced by: + + + +
+ $reference): + foreach ($reference as $ref): + if ($type == 'object') { + $uuid = $ref['uuid']; + $output = ' (' . $ref['meta-category'] . ': ' . $ref['name'] . ')'; + } else { + $uuid = $ref['uuid']; + $output = ' (' . $ref['category'] . '/' . $ref['type'] . ': "' . $ref['value'] . '")'; + } +?> +    + + + +
+ +
diff --git a/app/View/Elements/Events/View/row_proposal.ctp b/app/View/Elements/Events/View/row_proposal.ctp new file mode 100644 index 000000000..f8fe4c30d --- /dev/null +++ b/app/View/Elements/Events/View/row_proposal.ctp @@ -0,0 +1,212 @@ + +> + + + + + + + + + + + + +
+ +
+ + + Html->image('orgs/' . h($object['Org']['name']) . '.png', array('alt' => h($object['Org']['name']), 'title' => h($object['Org']['name']), 'style' => 'width:24px; height:24px')); + else echo h($object['Org']['name']); + } + } else { ?> +   + + + +
+
+ +
+ + +
+
+ +
+ + +
+ +
> + > + element('/Events/View/value_field', array('object' => $object, 'linkClass' => $linkClass)); + ?> + + 0, 2 => 1); + $valueParts = explode('|', $object['value']); + foreach ($components as $component => $valuePart) { + if (isset($object['warnings'][$component]) && isset($valueParts[$valuePart])) { + foreach ($object['warnings'][$component] as $warning) $temp .= '' . h($valueParts[$valuePart]) . ': ' . h($warning) . '
'; + } + } + echo '  '; + } + ?> +
+ + + +
+   +
+ +   + + + +
+
+   +
+ +   + + + + + + + +
+
+ +
+ +   + +   +   + + + Form->create('Shadow_Attribute', array('id' => 'ShadowAttribute_' . $object['id'] . '_accept', 'url' => '/shadow_attributes/accept/' . $object['id'], 'style' => 'display:none;')); + echo $this->Form->end(); + ?> + + + + + + diff --git a/app/View/Elements/Events/View/row_proposal_delete.ctp b/app/View/Elements/Events/View/row_proposal_delete.ctp new file mode 100644 index 000000000..0550f0248 --- /dev/null +++ b/app/View/Elements/Events/View/row_proposal_delete.ctp @@ -0,0 +1,70 @@ + +> + + + + + + + + + + + + + + + + DELETE + + Form->create('Shadow_Attribute', array('id' => 'ShadowAttribute_' . $object['id'] . '_accept', 'url' => '/shadow_attributes/accept/' . $object['id'], 'style' => 'display:none;')); + echo $this->Form->end(); + ?> + + + + + + diff --git a/app/View/Elements/Events/View/sighting_field.ctp b/app/View/Elements/Events/View/sighting_field.ctp new file mode 100644 index 000000000..c4380fcff --- /dev/null +++ b/app/View/Elements/Events/View/sighting_field.ctp @@ -0,0 +1,35 @@ + + + Form->create('Sighting', array('id' => 'Sighting_' . $object['id'], 'url' => '/sightings/add/' . $object['id'], 'style' => 'display:none;')); + echo $this->Form->input('type', array('label' => false, 'id' => 'Sighting_' . $object['id'] . '_type')); + echo $this->Form->end(); + ?> + + +   +   +   + + + + + ' . h($s) . '/' . h($f) . '/' . h($e) . ')'; ?> + + + + element('sparkline', array('id' => $object['id'], 'csv' => $temp)); + } + ?> + diff --git a/app/View/Elements/Events/View/value_field.ctp b/app/View/Elements/Events/View/value_field.ctp new file mode 100644 index 000000000..d03e2e911 --- /dev/null +++ b/app/View/Elements/Events/View/value_field.ctp @@ -0,0 +1,49 @@ +'; + } else { + $filenameHash = explode('|', nl2br(h($object['value']))); + if (strrpos($filenameHash[0], '\\')) { + $filepath = substr($filenameHash[0], 0, strrpos($filenameHash[0], '\\')); + $filename = substr($filenameHash[0], strrpos($filenameHash[0], '\\')); + echo h($filepath); + echo '' . h($filename) . ''; + } else { + echo '' . h($filenameHash[0]) . ''; + } + if (isset($filenameHash[1])) echo '
' . $filenameHash[1]; + } + } else if (strpos($object['type'], '|') !== false) { + $separator = in_array($object['type'], array('ip-dst|port', 'ip-src|port')) ? ':' : '
'; + $separator_pos = strpos('|', $object['value']); + $final_value = h($object['value']); + echo substr_replace(h($object['value']), $separator, $separator_pos, strlen($separator)); + } else if ('vulnerability' == $object['type']) { + $cveUrl = (is_null(Configure::read('MISP.cveurl'))) ? "http://www.google.com/search?q=" : Configure::read('MISP.cveurl'); + echo $this->Html->link($sigDisplay, $cveUrl . $sigDisplay, array('target' => '_blank', 'class' => $linkClass)); + } else if ('link' == $object['type']) { + echo $this->Html->link($sigDisplay, $sigDisplay, array('class' => $linkClass)); + } else if ('cortex' == $object['type']) { + echo '
Cortex object
'; + } else if ('text' == $object['type']) { + if ($object['category'] == 'External analysis' && preg_match('/[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/i', $object['value'])) { + echo '' . h($object['value']) . ''; + } else { + $sigDisplay = str_replace("\r", '', h($sigDisplay)); + $sigDisplay = str_replace(" ", ' ', $sigDisplay); + echo nl2br($sigDisplay); + } + } else if ('hex' == $object['type']) { + $sigDisplay = str_replace("\r", '', $sigDisplay); + echo '' . nl2br(h($sigDisplay)) . ' '; + } else { + $sigDisplay = str_replace("\r", '', $sigDisplay); + echo nl2br(h($sigDisplay)); + } + if (isset($object['validationIssue'])) echo '  '; +?> diff --git a/app/View/Elements/Events/eventIndexTable.ctp b/app/View/Elements/Events/eventIndexTable.ctp index 14188e9a6..d29bcb7e2 100644 --- a/app/View/Elements/Events/eventIndexTable.ctp +++ b/app/View/Elements/Events/eventIndexTable.ctp @@ -69,7 +69,7 @@ - + " class="attribute_row"> + + Form->input('Attribute.' . $k . '.save', array( + 'type' => 'checkbox', + 'checked' => in_array($k, $enabledRows), + 'label' => false, + 'div' => false + )); + ?> + + + 'hidden', + 'value' => $element['object_relation'], + 'label' => false, + 'div' => false + ); + echo $this->Form->input('Attribute.' . $k . '.object_relation', $formSettings); + if ($action == 'edit') { + echo $this->Form->input('Attribute.' . $k . '.uuid', array( + 'type' => 'hidden', + 'label' => false, + 'div' => false, + 'value' => !empty($element['uuid']) ? $element['uuid'] : '' + )); + } + $formSettings = array( + 'type' => 'hidden', + 'value' => $element['type'], + 'label' => false, + 'div' => false + ); + echo $this->Form->input('Attribute.' . $k . '.type', $formSettings); + echo '' . Inflector::humanize(h($element['object_relation'])) . ''; + if (!empty($template['ObjectTemplate']['requirements']['required']) && in_array($element['object_relation'], $template['ObjectTemplate']['requirements']['required'])) { + echo '' . '(*)' . ''; + } + echo ' :: ' . h($element['type']) . ''; + ?> + + + + + + array_combine($element['categories'], $element['categories']), + 'default' => $element['default_category'], + 'style' => 'margin-bottom:0px;', + 'label' => false, + 'div' => false + ); + echo $this->Form->input('Attribute.' . $k . '.category', $formSettings); + ?> + + + element( + 'Objects/object_value_field', + array( + 'element' => $element, + 'k' => $k + ) + ); + ?> + + + Form->input('Attribute.' . $k . '.to_ids', array( + 'type' => 'checkbox', + 'checked' => $element['to_ids'], + 'label' => false, + 'div' => false + )); + ?> + + + Form->input('Attribute.' . $k . '.distribution', array( + 'class' => 'Attribute_distribution_select', + 'options' => $distributionData['levels'], + 'default' => !empty($element['distribution']) ? $element['distribution'] : $distributionData['initial'], + 'style' => 'margin-bottom:0px;', + 'label' => false, + 'div' => false + )); + ?> +
+ Form->input('Attribute.' . $k . '.sharing_group_id', array( + 'class' => 'Attribute_sharing_group_id_select', + 'options' => $distributionData['sgs'], + 'default' => !empty($element['sharing_group_id']) ? $element['sharing_group_id'] : false, + 'label' => false, + 'div' => false, + 'style' => 'display:none;margin-bottom:0px;', + )); + ?> + + + Form->input('Attribute.' . $k . '.comment', array( + 'type' => 'textarea', + 'style' => 'height:20px;width:400px;', + 'required' => false, + 'allowEmpty' => true, + 'label' => false, + 'div' => false, + 'value' => empty($element['comment']) ? '' : $element['comment'] + )); + ?> + + diff --git a/app/View/Elements/Objects/object_value_field.ctp b/app/View/Elements/Objects/object_value_field.ctp new file mode 100644 index 000000000..081969e1c --- /dev/null +++ b/app/View/Elements/Objects/object_value_field.ctp @@ -0,0 +1,56 @@ +
+ Form->file('Attribute.' . $k . '.Attachment', array( + 'class' => 'Attribute_attachment' + )); + else: + if (empty($element['values_list']) && empty($element['sane_default'])): + echo $this->Form->input('Attribute.' . $k . '.value', array( + 'type' => 'textarea', + 'required' => false, + 'allowEmpty' => true, + 'style' => 'height:20px;width:400px;', + 'label' => false, + 'div' => false, + 'value' => empty($element['value']) ? '' : $element['value'] + )); + else: + if (empty($element['values_list'])) { + $list = $element['sane_default']; + $list[] = 'Enter value manually'; + } else { + $list = $element['values_list']; + } + $list = array_combine($list, $list); + ?> +
+ Form->input('Attribute.' . $k . '.value_select', array( + 'class' => 'Attribute_value_select', + 'style' => 'width:414px;margin-bottom:0px;', + 'options' => array_combine($list, $list), + 'label' => false, + 'div' => false, + 'value' => empty($element['value']) ? '' : $element['value'] + )); + ?> +
+ Form->input('Attribute.' . $k . '.value', array( + 'class' => 'Attribute_value', + 'type' => 'textarea', + 'required' => false, + 'allowEmpty' => true, + 'style' => 'height:20px;width:400px;display:none;', + 'label' => false, + 'div' => false, + 'value' => empty($element['value']) ? '' : $element['value'] + )); + ?> +
+ +
diff --git a/app/View/Elements/Servers/View/row_attribute.ctp b/app/View/Elements/Servers/View/row_attribute.ctp new file mode 100644 index 000000000..dd68f3fb2 --- /dev/null +++ b/app/View/Elements/Servers/View/row_attribute.ctp @@ -0,0 +1,146 @@ + + + + + + +
+
+ +
+ + + +
:
+ +
+
+
+ +
+ + +
+ +
> + > + element('/Events/View/value_field', array('object' => $object, 'linkClass' => $linkClass)); + ?> + + 0, 2 => 1); + $valueParts = explode('|', $object['value']); + foreach ($components as $component => $valuePart) { + if (isset($object['warnings'][$component]) && isset($valueParts[$valuePart])) { + foreach ($object['warnings'][$component] as $warning) $temp .= '' . h($valueParts[$valuePart]) . ': ' . h($warning) . '
'; + } + } + echo '  '; + } + ?> +
+ + +
+ element('ajaxAttributeTags', array('attributeId' => $object['id'], 'attributeTags' => $object['Tag'], 'tagAccess' => false)); + ?> +
+ + +
+
+   +
+ + + + + + + + +
+
+ +
+ + + diff --git a/app/View/Elements/Servers/View/row_object.ctp b/app/View/Elements/Servers/View/row_object.ctp new file mode 100644 index 000000000..87dabd35d --- /dev/null +++ b/app/View/Elements/Servers/View/row_object.ctp @@ -0,0 +1,51 @@ + + + + + + + Name: + +
+
+ Meta-category:
+ Description:
+ Template: +
+ element('/Servers/View/row_object_reference', array( + 'object' => $object + )); + if (!empty($object['referenced_by'])) { + echo $this->element('/Servers/View/row_object_referenced_by', array( + 'object' => $object + )); + } + ?> + +   +   + + $attribute) { + echo $this->element('/Servers/View/row_' . $attribute['objectType'], array( + 'object' => $attribute, + 'page' => $page, + 'fieldCount' => $fieldCount, + 'child' => $attrKey == $lastElement ? 'last' : true + )); + } + } +?> diff --git a/app/View/Elements/Servers/View/row_object_reference.ctp b/app/View/Elements/Servers/View/row_object_reference.ctp new file mode 100644 index 000000000..5f043dea8 --- /dev/null +++ b/app/View/Elements/Servers/View/row_object_reference.ctp @@ -0,0 +1,35 @@ +References: + + + + +
+ +    + + + +
+ +
diff --git a/app/View/Elements/Servers/View/row_object_referenced_by.ctp b/app/View/Elements/Servers/View/row_object_referenced_by.ctp new file mode 100644 index 000000000..e0801b3fe --- /dev/null +++ b/app/View/Elements/Servers/View/row_object_referenced_by.ctp @@ -0,0 +1,32 @@ +Referenced by: + + + +
+ $reference): + foreach ($reference as $ref): + if ($type == 'object') { + $uuid = $ref['uuid']; + $output = ' (' . $ref['meta-category'] . ': ' . $ref['name'] . ')'; + } else { + $uuid = $ref['uuid']; + $output = ' (' . $ref['category'] . '/' . $ref['type'] . ': "' . $ref['value'] . '")'; + } +?> +    + + + +
+ +
diff --git a/app/View/Elements/Servers/View/value_field.ctp b/app/View/Elements/Servers/View/value_field.ctp new file mode 100644 index 000000000..d03e2e911 --- /dev/null +++ b/app/View/Elements/Servers/View/value_field.ctp @@ -0,0 +1,49 @@ +'; + } else { + $filenameHash = explode('|', nl2br(h($object['value']))); + if (strrpos($filenameHash[0], '\\')) { + $filepath = substr($filenameHash[0], 0, strrpos($filenameHash[0], '\\')); + $filename = substr($filenameHash[0], strrpos($filenameHash[0], '\\')); + echo h($filepath); + echo '' . h($filename) . ''; + } else { + echo '' . h($filenameHash[0]) . ''; + } + if (isset($filenameHash[1])) echo '
' . $filenameHash[1]; + } + } else if (strpos($object['type'], '|') !== false) { + $separator = in_array($object['type'], array('ip-dst|port', 'ip-src|port')) ? ':' : '
'; + $separator_pos = strpos('|', $object['value']); + $final_value = h($object['value']); + echo substr_replace(h($object['value']), $separator, $separator_pos, strlen($separator)); + } else if ('vulnerability' == $object['type']) { + $cveUrl = (is_null(Configure::read('MISP.cveurl'))) ? "http://www.google.com/search?q=" : Configure::read('MISP.cveurl'); + echo $this->Html->link($sigDisplay, $cveUrl . $sigDisplay, array('target' => '_blank', 'class' => $linkClass)); + } else if ('link' == $object['type']) { + echo $this->Html->link($sigDisplay, $sigDisplay, array('class' => $linkClass)); + } else if ('cortex' == $object['type']) { + echo '
Cortex object
'; + } else if ('text' == $object['type']) { + if ($object['category'] == 'External analysis' && preg_match('/[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/i', $object['value'])) { + echo '' . h($object['value']) . ''; + } else { + $sigDisplay = str_replace("\r", '', h($sigDisplay)); + $sigDisplay = str_replace(" ", ' ', $sigDisplay); + echo nl2br($sigDisplay); + } + } else if ('hex' == $object['type']) { + $sigDisplay = str_replace("\r", '', $sigDisplay); + echo '' . nl2br(h($sigDisplay)) . ' '; + } else { + $sigDisplay = str_replace("\r", '', $sigDisplay); + echo nl2br(h($sigDisplay)); + } + if (isset($object['validationIssue'])) echo '  '; +?> diff --git a/app/View/Elements/Servers/eventattribute.ctp b/app/View/Elements/Servers/eventattribute.ctp new file mode 100644 index 000000000..97dad28bb --- /dev/null +++ b/app/View/Elements/Servers/eventattribute.ctp @@ -0,0 +1,148 @@ +params->params['paging']['Event']['page'])) { + if ($this->params->params['paging']['Event']['page'] == 0) $all = true; + $page = $this->params->params['paging']['Event']['page']; + } else { + $page = 0; + } + $fieldCount = 8; +?> + +
+
+ + + + + + + + + + + + + 'attribute', + 3 => 'object' + ); + $focusedRow = false; + foreach ($event['objects'] as $k => $object): + $insertBlank = false; + echo $this->element('/Servers/View/row_' . $object['objectType'], array( + 'object' => $object, + 'k' => $k, + 'page' => $page, + 'fieldCount' => $fieldCount + )); + if (!empty($focus) && ($object['objectType'] == 'object' || $object['objectType'] == 'attribute') && $object['uuid'] == $focus) { + $focusedRow = $k; + } + if ($object['objectType'] == 'object'): + ?> + + +
Paginator->sort('timestamp', 'Date');?>Paginator->sort('category');?>Paginator->sort('type');?>Paginator->sort('value');?>TagsPaginator->sort('comment');?>Related EventsFeed hitsPaginator->sort('to_ids', 'IDS');?>
+
+ + +Js->writeBuffer(); +?> diff --git a/app/View/Elements/ajaxAttributeTags.ctp b/app/View/Elements/ajaxAttributeTags.ctp index 9d1d944a1..7a6c2a232 100644 --- a/app/View/Elements/ajaxAttributeTags.ctp +++ b/app/View/Elements/ajaxAttributeTags.ctp @@ -2,13 +2,26 @@ $tag); $tagClass = $full ? 'tagFirstHalf' : 'tag'; ?>
- - -
x
- + + + + + +
x
+
+
$data) { $sightingsData['data'][$aid]['html'] = ''; @@ -36,7 +37,9 @@
Filters: -
All (Paginator->params()['total_elements']); ?>)
+
All
@@ -174,419 +177,35 @@ Actions $object): - $extra = ''; - $extra2 = ''; - $extra3 = ''; - $linkClass = 'white'; - $currentType = 'denyForm'; - if ($object['objectType'] == 0 ) { - $currentType = 'Attribute'; - if ($object['hasChildren'] == 1) { - $extra = 'highlight1'; - $extra3 = 'highlightBlueSides highlightBlueTop'; - } else { - $linkClass = ''; - } - if (!$mayModify) $currentType = 'ShadowAttribute'; - } else { - if (isset($object['proposal_to_delete']) && $object['proposal_to_delete']) { - $extra = 'highlight3'; - unset($object['type']); - } else $extra = 'highlight2'; - + $elements = array( + 0 => 'attribute', + 1 => 'proposal', + 2 => 'proposal_delete', + 3 => 'object' + ); + $focusedRow = false; + foreach ($event['objects'] as $k => $object) { + $insertBlank = false; + echo $this->element('/Events/View/row_' . $object['objectType'], array( + 'object' => $object, + 'k' => $k, + 'mayModify' => $mayModify, + 'mayChangeCorrelation' => $mayChangeCorrelation, + 'page' => $page, + 'fieldCount' => $fieldCount + )); + if (!empty($focus) && ($object['objectType'] == 'object' || $object['objectType'] == 'attribute') && $object['uuid'] == $focus) { + $focusedRow = $k; } - if ($object['objectType'] == 1) { - $extra2 = '1'; - $extra3 = 'highlightBlueSides'; - if (isset($object['firstChild'])) { - $extra3 .= ' highlightBlueTop'; - } - if (isset($object['lastChild'])) { - $extra3 .= ' highlightBlueBottom'; - } - } - if (isset($object['deleted']) && $object['deleted']) { - $extra .= ' background-light-red'; - } - $extra .= (isset($object['deleted']) && $object['deleted']) ? ' background-light-red' : ''; - ?> - - - - - - - - - - - - - - - -
- -
- - - Html->image('orgs/' . h($object['Org']['name']) . '.png', array('alt' => h($object['Org']['name']), 'title' => h($object['Org']['name']), 'style' => 'width:24px; height:24px')); - else echo h($object['Org']['name']); - } - } else { ?> -   - - - -
-
- -
- - -
-
- -
- - -
- -
> - > - '; - } else { - $t = ($object['objectType'] == 0 ? 'attributes' : 'shadow_attributes'); - $filenameHash = explode('|', nl2br(h($object['value']))); - if (strrpos($filenameHash[0], '\\')) { - $filepath = substr($filenameHash[0], 0, strrpos($filenameHash[0], '\\')); - $filename = substr($filenameHash[0], strrpos($filenameHash[0], '\\')); - echo h($filepath); - echo '' . h($filename) . ''; - } else { - echo '' . h($filenameHash[0]) . ''; - } - if (isset($filenameHash[1])) echo '
' . $filenameHash[1]; - } - } else if (strpos($object['type'], '|') !== false) { - $filenameHash = explode('|', $object['value']); - echo h($filenameHash[0]); - if (isset($filenameHash[1])) { - $separator = '
'; - if (in_array($object['type'], array('ip-dst|port', 'ip-src|port'))) { - $separator = ':'; - } - echo $separator . h($filenameHash[1]); - } - } else if ('vulnerability' == $object['type']) { - if (! is_null(Configure::read('MISP.cveurl'))) { - $cveUrl = Configure::read('MISP.cveurl'); - } else { - $cveUrl = "http://www.google.com/search?q="; - } - echo $this->Html->link($sigDisplay, $cveUrl . $sigDisplay, array('target' => '_blank', 'class' => $linkClass)); - } else if ('link' == $object['type']) { - echo $this->Html->link($sigDisplay, $sigDisplay, array('class' => $linkClass)); - } else if ('cortex' == $object['type']) { - echo '
Cortex object
'; - } else if ('text' == $object['type']) { - if ($object['category'] == 'External analysis' && preg_match('/[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/i', $object['value'])) { - echo '' . h($object['value']) . ''; - } else { - $sigDisplay = str_replace("\r", '', h($sigDisplay)); - $sigDisplay = str_replace(" ", ' ', $sigDisplay); - echo nl2br($sigDisplay); - } - } else if ('hex' == $object['type']) { - $sigDisplay = str_replace("\r", '', $sigDisplay); - echo '' . nl2br(h($sigDisplay)) . ' '; - } else { - $sigDisplay = str_replace("\r", '', $sigDisplay); - echo nl2br(h($sigDisplay)); - } - if (isset($object['validationIssue'])) echo '  '; - ?> -
- 0, 2 => 1); - $valueParts = explode('|', $object['value']); - foreach ($components as $component => $valuePart) { - if (isset($object['warnings'][$component]) && isset($valueParts[$valuePart])) { - foreach ($object['warnings'][$component] as $warning) $temp .= '' . h($valueParts[$valuePart]) . ': ' . h($warning) . '
'; - } - } - echo '  '; - } - ?> -
- - - -
- element('ajaxAttributeTags', array('attributeId' => $object['id'], 'attributeTags' => $object['AttributeTag'], 'tagAccess' => ($isSiteAdmin || $mayModify || $me['org_id'] == $event['Event']['org_id']) )); ?> -
- -   - - - -
-
-   -
- - - - > - - -   - - - - - - - - -
-
- -
- - - -
-
class="inline-field-solid" ondblclick="activateField('', '', 'distribution', );"> - - -   -
- - - - - Form->create('Sighting', array('id' => 'Sighting_' . $object['id'], 'url' => '/sightings/add/' . $object['id'], 'style' => 'display:none;')); - echo $this->Form->input('type', array('label' => false, 'id' => 'Sighting_' . $object['id'] . '_type')); - echo $this->Form->end(); - ?> - - -   -   -   - - - - - ' . h($s) . '/' . h($f) . '/' . h($e) . ')'; ?> - - - - - element('sparkline', array('id' => $object['id'], 'csv' => $temp)); - } - ?> - - - - - - - -   - - - - - - -   - -   - - C - - - - Form->create('Shadow_Attribute', array('id' => 'ShadowAttribute_' . $object['id'] . '_accept', 'url' => '/shadow_attributes/accept/' . $object['id'], 'style' => 'display:none;')); - echo $this->Form->end(); - ?> - - - - - - + if ( + ($object['objectType'] == 'attribute' && !empty($object['ShadowAttribute'])) || + $object['objectType'] == 'object' + ): + ?> +
@@ -608,7 +227,6 @@ attributes or the appropriate distribution level. If you think there is a mistak + +Js->writeBuffer(); // Write cached scripts diff --git a/app/View/ObjectReferences/ajax/delete.ctp b/app/View/ObjectReferences/ajax/delete.ctp new file mode 100644 index 000000000..f8eaa76a3 --- /dev/null +++ b/app/View/ObjectReferences/ajax/delete.ctp @@ -0,0 +1,29 @@ +
+Form->create('ObjectReference', array('style' => 'margin:0px;', 'id' => 'PromptForm', 'url' => $url)); + if ($hard) $hard = '/true'; +?> +Object reference Deletion +
+

Are you sure you want to delete Object reference #?

+ + + + + + +
+ Yes + + + No +
+
+Form->end(); +?> +
diff --git a/app/View/ObjectTemplateElements/ajax/view_elements.ctp b/app/View/ObjectTemplateElements/ajax/view_elements.ctp new file mode 100644 index 000000000..fffd44f62 --- /dev/null +++ b/app/View/ObjectTemplateElements/ajax/view_elements.ctp @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + $item): +?> + + + + + + + + + + + + +
Paginator->sort('object_relation');?>Paginator->sort('type');?>Paginator->sort('multiple');?>Paginator->sort('ui-priority', 'UI-priority');?>Paginator->sort('description');?>CategoriesSane defaultsList of valid Values
     + '; + } + } + ?> +
+

+Paginator->counter(array('format' => __('Page {:page} of {:pages}, showing {:current} records out of {:count} total, starting on record {:start}, ending on {:end}'))); +?> +

+ + + +Js->writeBuffer(); ?> diff --git a/app/View/ObjectTemplates/ajax/getToggleField.ctp b/app/View/ObjectTemplates/ajax/getToggleField.ctp new file mode 100644 index 000000000..b4aa6143d --- /dev/null +++ b/app/View/ObjectTemplates/ajax/getToggleField.ctp @@ -0,0 +1,4 @@ +Form->create('ObjectTemplate', array('url' => '/ObjectTemplates/activate', 'id' => 'ObjectTemplateIndexForm')); +echo $this->Form->input('data', array('label' => false, 'style' => 'display:none;')); +echo $this->Form->end(); diff --git a/app/View/ObjectTemplates/ajax/object_choice.ctp b/app/View/ObjectTemplates/ajax/object_choice.ctp new file mode 100644 index 000000000..6b44dc68b --- /dev/null +++ b/app/View/ObjectTemplates/ajax/object_choice.ctp @@ -0,0 +1,57 @@ +
+ Select Object Category +
+ + + + +
All Objects
+
+
Cancel
+
+ diff --git a/app/View/ObjectTemplates/ajax/selectObject.ctp b/app/View/ObjectTemplates/ajax/selectObject.ctp new file mode 100644 index 000000000..e69de29bb diff --git a/app/View/ObjectTemplates/index.ctp b/app/View/ObjectTemplates/index.ctp new file mode 100644 index 000000000..9b804463f --- /dev/null +++ b/app/View/ObjectTemplates/index.ctp @@ -0,0 +1,133 @@ +
+

Object Template index

+ +
+ Form->create('ObjectTemplate', array('url' => '/ObjectTemplates/activate')); + echo $this->Form->input('data', array('label' => false, 'style' => 'display:none;')); + echo $this->Form->end(); + } + ?> +
+
+ + Enabled + All + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Paginator->sort('active');?>Paginator->sort('id');?>Paginator->sort('name');?>Paginator->sort('uuid');?>Paginator->sort('org_id', 'Organisation');?>Paginator->sort('version');?>Paginator->sort('meta-category');?>Paginator->sort('description');?>RequirementsActions
> + /> + >> + + + + + >>>>>> + $requirements): + ?> +
+ +   
+ +
+

+ Paginator->counter(array( + 'format' => __('Page {:page} of {:pages}, showing {:current} records out of {:count} total, starting on record {:start}, ending on {:end}') + )); + ?> +

+ +
+element('side_menu', array('menuList' => 'objectTemplates', 'menuItem' => 'index')); diff --git a/app/View/ObjectTemplates/view.ctp b/app/View/ObjectTemplates/view.ctp new file mode 100644 index 000000000..6847710c0 --- /dev/null +++ b/app/View/ObjectTemplates/view.ctp @@ -0,0 +1,52 @@ +element('side_menu', array('menuList' => 'objectTemplates', 'menuItem' => 'view')); +?> +
+
+
+

Object Template

+
+
Object Template ID
+
+
Name
+
+
Organisation
+
+
Uuid
+
+
Version
+
+
Meta-category
+
+
Description
+
+
Requirements
+
+ $requirements): + ?> +
+ +   
+ +
+
+
+
+
+
+ diff --git a/app/View/Objects/add.ctp b/app/View/Objects/add.ctp new file mode 100644 index 000000000..d1efafecb --- /dev/null +++ b/app/View/Objects/add.ctp @@ -0,0 +1,196 @@ +
+Form->create('Object', array('id', 'url' => $url, 'enctype' => 'multipart/form-data')); +?> +

+
+
+
Object Template
+
+ +   +
+
Description
+
+   +
+ +
Requirements
+
+ Required: ' . h(implode(', ', $template['ObjectTemplate']['requirements']['required'])) . '
'; + } + if (!empty($template['ObjectTemplate']['requirements']['requiredOneOf'])) { + echo 'Required one of: ' . h(implode(', ', $template['ObjectTemplate']['requirements']['requiredOneOf'])); + } + ?> +
+ +
Meta category
+
+   +
+
Distribution
+
+ Form->input('Object.distribution', array( + 'class' => 'Object_distribution_select', + 'options' => $distributionData['levels'], + 'default' => $distributionData['initial'], + 'label' => false, + 'style' => 'margin-bottom:5px;', + 'div' => false + )); + echo $this->Form->input('Object.sharing_group_id', array( + 'class' => 'Object_sharing_group_id_select', + 'options' => $distributionData['sgs'], + 'label' => false, + 'div' => false, + 'style' => 'display:none;margin-bottom:5px;' + )); + ?> +
+
Comment
+
+ Form->input('Object.comment', array( + 'type' => 'textarea', + 'style' => 'height:20px;width:400px;', + 'required' => false, + 'allowEmpty' => true, + 'label' => false, + 'div' => false, + 'value' => empty($template['Object']['comment']) ? '' : $template['Object']['comment'] + )); + ?> +
+
+
+ + Warning, issues found with the template: +
+ '; + } + ?> +
+ + + + + + + + + + + + + $element): + $row_list[] = $k; + echo $this->element( + 'Objects/object_add_attributes', + array( + 'element' => $element, + 'k' => $k, + 'action' => $action, + 'enabledRows' => $enabledRows + ) + ); + if ($element['multiple']): + $lastOfType = true; + $lookAheadArray = array_slice($template['ObjectTemplateElement'], $k, count($template['ObjectTemplateElement']), true); + if (count($lookAheadArray) > 1) { + foreach ($lookAheadArray as $k2 => $temp) { + if ($k2 == $k) continue; + if ($temp['object_relation'] == $element['object_relation']) { + $lastOfType = false; + } + } + } + if ($lastOfType): + ?> + + + + + +
SaveName :: typeDescriptionCategoryValueIDSDistributionComment
+ +
+ + +
+ + + + + + +
+ Submit + + + + Cancel +
+
+ + + Form->button('Submit', array('class' => 'btn btn-primary')); + endif; + echo $this->Form->end(); + ?> +
+element('side_menu', array('menuList' => 'event', 'menuItem' => 'addObject', 'event' => $event)); + } +?> + diff --git a/app/View/Objects/ajax/delete.ctp b/app/View/Objects/ajax/delete.ctp new file mode 100644 index 000000000..7f6e4c87e --- /dev/null +++ b/app/View/Objects/ajax/delete.ctp @@ -0,0 +1,25 @@ +
+Form->create('Object', array('style' => 'margin:0px;', 'id' => 'PromptForm')); + if ($hard) $hard = '/true'; +?> +Object Deletion +
+

Are you sure you want to delete Object #?

+ + + + + + +
+ Yes + + + No +
+
+Form->end(); +?> +
diff --git a/app/View/Objects/get_row.ctp b/app/View/Objects/get_row.ctp new file mode 100644 index 000000000..ef4de00e3 --- /dev/null +++ b/app/View/Objects/get_row.ctp @@ -0,0 +1,10 @@ +element( + 'Objects/object_add_attributes', + array( + 'element' => $element, + 'k' => $k, + 'appendValue' => '0' + ) + ); +?> diff --git a/app/View/Objects/revise_object.ctp b/app/View/Objects/revise_object.ctp new file mode 100644 index 000000000..8e61e1f71 --- /dev/null +++ b/app/View/Objects/revise_object.ctp @@ -0,0 +1,56 @@ +
+

Object pre-save review

+

Make sure that the below Object reflects your expectation before submiting it.

+ Form->create('Object', array('id', 'url' => $url)); + $formSettings = array( + 'type' => 'hidden', + 'value' => json_encode($data, true), + 'label' => false, + 'div' => false + ); + echo $this->Form->input('data', $formSettings); + ?> +
+ Name:
+ Meta-category:
+ Distribution: +
+ Comment:
+ Attributes:
+ ' . h($attribute['object_relation']) . ':
'; + foreach ($attributeFields as $field): + if ($field == 'to_ids') $attribute[$field] = $attribute[$field] ? 'Yes' : 'No'; + if (isset($attribute[$field])): + ?> + :
+ +
+ Form->button('Submit', array('class' => 'btn btn-primary')); + ?> + Cancel + Form->end(); + ?> + +
+element('side_menu', array('menuList' => 'event', 'menuItem' => 'addObject', 'event' => $event)); +?> diff --git a/app/View/Servers/preview_event.ctp b/app/View/Servers/preview_event.ctp index c03050600..738e02cef 100644 --- a/app/View/Servers/preview_event.ctp +++ b/app/View/Servers/preview_event.ctp @@ -108,145 +108,7 @@

- params->params['paging']['Event']['page']) && $this->params->params['paging']['Event']['page'] == 0) $all = true; - ?> - -
- - - - - - - - - - - - - - - $object): - $extra = $extra2 = $extra3 = ''; - $currentType = 'denyForm'; - if ($object['objectType'] == 0 ) { - $currentType = 'Attribute'; - if ($object['hasChildren'] == 1) { - $extra = 'highlight1'; - $extra3 = 'highlightBlueSides highlightBlueTop'; - } - } else $extra = 'highlight2'; - if ($object['objectType'] == 1) { - $extra2 = '1'; - $extra3 = 'highlightBlueSides'; - if (isset($object['firstChild'])) { - $extra3 .= ' highlightBlueTop'; - } - if (isset($object['lastChild'])) { - $extra3 .= ' highlightBlueBottom'; - } - } - ?> - - - - - - - - - - - - - - -
Paginator->sort('date');?>Paginator->sort('category');?>Paginator->sort('type');?>Paginator->sort('value');?>TagsPaginator->sort('comment');?>Related EventsPaginator->sort('to_ids', 'IDS');?>Paginator->sort('distribution');?>
- - - - -   - -
    - -
  • - -
-
-   -
-
- + element('Servers/eventattribute'); ?>
'input', 'label' => 'Category ' . $this->element('formInfo', array('type' => 'category')), )); + $typeInputData = array( + 'empty' => '(first choose category)', + 'label' => 'Type ' . $this->element('formInfo', array('type' => 'type')), + ); + if ($objectAttribute) { + $typeInputData[] = 'disabled'; + } if (!$attachment) { - echo $this->Form->input('type', array( - 'empty' => '(first choose category)', - 'label' => 'Type ' . $this->element('formInfo', array('type' => 'type')), - )); + echo $this->Form->input('type', $typeInputData); } ?>
diff --git a/app/files/misp-objects b/app/files/misp-objects new file mode 160000 index 000000000..10b21c6aa --- /dev/null +++ b/app/files/misp-objects @@ -0,0 +1 @@ +Subproject commit 10b21c6aacb536e7646158f950e6ad972293c830 diff --git a/app/files/scripts/generate_file_objects.py b/app/files/scripts/generate_file_objects.py new file mode 100644 index 000000000..326984537 --- /dev/null +++ b/app/files/scripts/generate_file_objects.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import argparse +import json + +try: + from pymisp import MISPEncode + from pymisp.tools import make_binary_objects +except ImportError: + pass + + +def check(): + missing_dependencies = {'pydeep': False, 'lief': False, 'magic': False, 'pymisp': False} + try: + import pymisp # noqa + except ImportError: + missing_dependencies['pymisp'] = 'Please install pydeep: pip install pymisp' + try: + import pydeep # noqa + except ImportError: + missing_dependencies['pydeep'] = 'Please install pydeep: pip install git+https://github.com/kbandla/pydeep.git' + try: + import lief # noqa + except ImportError: + missing_dependencies['lief'] = 'Please install lief, documentation here: https://github.com/lief-project/LIEF' + try: + import magic # noqa + except ImportError: + missing_dependencies['magic'] = 'Please install python-magic: pip install python-magic.' + return json.dumps(missing_dependencies) + + +def make_objects(path): + to_return = {'objects': [], 'references': []} + fo, peo, seos = make_binary_objects(path) + + if seos: + for s in seos: + to_return['objects'].append(s) + if s.ObjectReference: + to_return['references'] += s.ObjectReference + + if peo: + to_return['objects'].append(peo) + if peo.ObjectReference: + to_return['references'] += peo.ObjectReference + + if fo: + to_return['objects'].append(fo) + if fo.ObjectReference: + to_return['references'] += fo.ObjectReference + return json.dumps(to_return, cls=MISPEncode) + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='Extract indicators out of binaries and returns MISP objects.') + group = parser.add_mutually_exclusive_group() + group.add_argument("-p", "--path", help="Path to process.") + group.add_argument("-c", "--check", action='store_true', help="Check the dependencies.") + args = parser.parse_args() + + if args.check: + print(check()) + if args.path: + obj = make_objects(args.path) + print(obj) diff --git a/app/files/taxonomies b/app/files/taxonomies index 6a6168b4a..e89715212 160000 --- a/app/files/taxonomies +++ b/app/files/taxonomies @@ -1 +1 @@ -Subproject commit 6a6168b4a5235ce74053a3319a4e30c0ded54c61 +Subproject commit e89715212ce7dfa2e29df07aa28a679bcdff64dd diff --git a/app/webroot/css/main.css b/app/webroot/css/main.css index 104015f86..1658136fb 100644 --- a/app/webroot/css/main.css +++ b/app/webroot/css/main.css @@ -1,5 +1,5 @@ /* bootstrap changes */ -body{ +body { font-size: 12px; } @@ -7,11 +7,11 @@ input, button, select, textarea{ } -input[type="file"]{ +input[type="file"] { margin-bottom: 10px; } -button .full-width{ +button .full-width { width:100%; } @@ -272,7 +272,6 @@ td.highlight1 { td.highlight2 { background-color: #747170 !important; color: #ffffff !important; - } td.highlight3 { @@ -1512,6 +1511,10 @@ a.proposal_link_red:hover { color: white !important; } +tr.deleted-attribute td { + background-color: #d3d7cf !important; +} + .background-deleted { background: repeating-linear-gradient( 45deg, @@ -1607,6 +1610,21 @@ a.discrete { border-radius:5px; } +.redHighlightedBlock { + border:1px solid red; + background-color:white; + padding:5px; + border-radius:5px; + height:228px; + width:320px; + overflow-y:scroll; + line-height:12px; +} + +.indent { + text-indent: 2em; +} + .tooltip-inner { white-space:pre-wrap !important; } @@ -1714,6 +1732,182 @@ table.table.table-striped tr.deleted_row td { background-color:#f4f4f4; } +tr.tableInsetOrangeFirst td:first-child { + box-shadow: + inset 0px 2px 0px 0px #f57900, + inset 3px 0px 0px 0px #f57900; + border-top:0px !important; +} + +tr.tableInsetOrangeFirst td { + box-shadow: + inset 0px 2px 0px 0px #f57900; + border-top:0px !important; +} + +tr.tableInsetOrangeFirst td:last-child { + box-shadow: + inset 0px 2px 0px 0px #f57900, + inset -2px 0px 0px 0px #f57900; + border-top:0px !important; +} + +tr.tableInsetOrangeMiddle td:first-child { + box-shadow: + inset 3px 0px 0px 0px #f57900; + border-top:0px !important; +} + +tr.tableInsetOrangeMiddle td:last-child { + box-shadow: + inset -3px 0px 0px 0px #f57900; + border-top:0px !important; +} + +tr.tableInsetOrangeLast td:first-child { + box-shadow: + inset 0px -3px 0px 0px #f57900, + inset 3px 0px 0px 0px #f57900; + border-top:0px !important; +} + +tr.tableInsetOrangeLast td { + box-shadow: + inset 0px -3px 0px 0px #f57900; + border-top:0px !important; +} + +tr.tableInsetOrangeLast td:last-child { + box-shadow: + inset 0px -3px 0px 0px #f57900, + inset -3px 0px 0px 0px #f57900; + border-top:0px !important; +} + +tr.tableInsetOrange td:first-child { + box-shadow: + inset 0px 2px 0px 0px #f57900, + inset 0px -3px 0px 0px #f57900, + inset 2px 0px 0px 0px #f57900; + border-top:0px !important; +} + +tr.tableInsetOrange td { + box-shadow: + inset 0px 2px 0px 0px #f57900, + inset 0px -3px 0px 0px #f57900; + border-top:0px !important; +} + +tr.tableInsetOrange td:last-child { + box-shadow: + inset 0px 2px 0px 0px #f57900, + inset 0px -3px 0px 0px #f57900, + inset -2px 0px 0px 0px #f57900; + border-top:0px !important; +} + +.tableHighlightBorder { + border-radius: 3px !important; + border-width: 3px; + border-style: solid; +} + +.tableHighlightBorderTop { + border-radius: 3px !important; + border-width: 3px; + border-style: solid; + border-bottom: 0px; +} + +.tableHighlightBorderCenter { + border-radius: 3px !important; + border-width: 3px; + border-style: solid; + border-bottom: 0px; + border-top: 0px; +} + +.tableHighlightBorderBottom { + border-radius: 3px !important; + border-width: 3px; + border-style:solid; + border-top: 0px; +} + +tr.tableHighlightBorderBottom td { + border-top:0px !important; + margin-bottom:10px; +} + +.borderRed { + border-color:#cc0000; +} + +.borderOrange { + border-color:#f57900; +} + +.borderBlue { + border-color:#3465a4; +} + +tr.darkOrangeRow td, .darkOrangeElement { + background-color: #ce5c00 !important; + color: #ffffff; + border-top:0px !important; +} + +tr.blueRow td, .blueElement { + background-color: #3465a4 !important; + color: #ffffff; + border-top:0px !important; +} + +tr.lightBlueRow td, .lightBlueElement { + background-color: #729fcf !important; + border-top:0px !important; +} + +tr.redRow td, .redElement { + background-color: #cc0000 !important; + color: #ffffff; + border-top:0px !important; +} + +.blank_table_row { + height: 10px; +} + +tr.blank_table_row td { + background-color: #ffffff !important; +} + +.down-expand-button { + text-align:center !important; + border:0px; + padding:0px !important; + width:100%; + cursor: pointer; + cursor: hand; +} + +table tr:hover .down-expand-button { + background: rgb(221, 221, 221) !important; +} + +.wrap-text { + word-wrap: break-word; +} + +.nowrap { + word-wrap:nowrap; +} + +.strikethrough { + text-decoration: line-through; +} + @-webkit-keyframes rotation { from {-webkit-transform: rotate(0deg);} to {-webkit-transform: rotate(359deg);} diff --git a/app/webroot/js/misp.js b/app/webroot/js/misp.js index 9d4216f42..3d978c3de 100644 --- a/app/webroot/js/misp.js +++ b/app/webroot/js/misp.js @@ -4,8 +4,9 @@ String.prototype.ucfirst = function() { function deleteObject(type, action, id, event) { var destination = 'attributes'; - var alternateDestinations = ['shadow_attributes', 'template_elements', 'taxonomies']; + var alternateDestinations = ['shadow_attributes', 'template_elements', 'taxonomies', 'objects', 'object_references']; if (alternateDestinations.indexOf(type) > -1) destination = type; + else destination = type; url = "/" + destination + "/" + action + "/" + id; $.get(url, function(data) { openPopup("#confirmation_box"); @@ -161,6 +162,12 @@ function toggleSetting(e, setting, id) { replacementForm = '/favourite_tags/getToggleField/'; searchString = 'Adding'; break; + case 'activate_object_template': + formID = '#ObjectTemplateIndexForm'; + dataDiv = '#ObjectTemplateData'; + replacementForm = '/ObjectTemplates/getToggleField/'; + searchString = 'activated'; + break; } $(dataDiv).val(id); var formData = $(formID).serialize(); @@ -776,7 +783,7 @@ function loadAttributeTags(id) { dataType:"html", cache: false, success:function (data, textStatus) { - $("#ShadowAttribute_"+id+"_tr .attributeTagContainer").html(data); + $("#Attribute_"+id+"_tr .attributeTagContainer").html(data); }, url:"/tags/showAttributeTag/" + id }); @@ -877,6 +884,9 @@ function submitPopoverForm(context_id, referer, update_context_id) { url = "/sightings/add/" + context_id; closePopover = false; break; + case 'addObjectReference': + url = "/objectReferences/add/" + context_id; + break; } if (url !== null) { $.ajax({ @@ -902,7 +912,7 @@ function submitPopoverForm(context_id, referer, update_context_id) { $('#sightingsListAllToggle').removeClass('btn-inverse'); $('#sightingsListAllToggle').addClass('btn-primary'); } - if (context == 'event' && (referer == 'add' || referer == 'massEdit' || referer == 'replaceAttributes')) eventUnpublish(); + if (context == 'event' && (referer == 'add' || referer == 'massEdit' || referer == 'replaceAttributes' || referer == 'addObjectReference')) eventUnpublish(); $(".loading").hide(); }, type:"post", @@ -1939,6 +1949,27 @@ function freetextImportResultsSubmit(id, count) { }); } +function objectTemplateViewContent(context, id) { + var url = "/objectTemplateElements/viewElements/" + id + "/" + context; + $.ajax({ + url: url, + type:'GET', + beforeSend: function (XMLHttpRequest) { + $(".loading").show(); + }, + error: function(){ + $('#ajaxContent').html('An error has occured, please reload the page.'); + }, + success: function(response){ + $('#ajaxContent').html(response); + }, + complete: function() { + $(".loading").hide(); + }, + }); + +} + function organisationViewContent(context, id) { organisationViewButtonHighlight(context); var action = "/organisations/landingpage/"; @@ -2544,6 +2575,25 @@ function filterAttributes(filter, id) { }); } +function pivotObjectReferences(url, uuid) { + url += '/focus:' + uuid; + console.log(url); + $.ajax({ + type:"get", + url:url, + beforeSend: function (XMLHttpRequest) { + $(".loading").show(); + }, + success:function (data) { + $("#attributes_div").html(data); + $(".loading").hide(); + }, + error:function() { + showMessage('fail', 'Something went wrong - could not fetch attributes.'); + } + }); +} + function toggleDeletedAttributes(url) { url = url.replace(/view\//i, 'viewEventAttributes/'); if (url.indexOf('deleted:') > -1) { @@ -3054,6 +3104,123 @@ $(".cortex-json").click(function() { $("#gray_out").fadeIn(); }); +// Show $(id) if the enable parameter evaluates to true. Hide it otherwise +function checkAndEnable(id, enable) { + if (enable) { + $(id).show(); + } else { + $(id).hide(); + } +} + +// Show and enable checkbox $(id) if the enable parameter evaluates to true. Hide and disable it otherwise. +function checkAndEnableCheckbox(id, enable) { + if (enable) { + $(id).removeAttr("disabled"); + $(id).prop('checked', true); + } else { + $(id).prop('checked', false); + $(id).attr("disabled", true); + } +} + +function enableDisableObjectRows(rows) { + rows.forEach(function(i) { + if ($("#Attribute" + i + "ValueSelect").length != 0) { + checkAndEnableCheckbox("#Attribute" + i + "Save", true); + } else if ($("#Attribute" + i + "Attachment").length != 0) { + checkAndEnableCheckbox("#Attribute" + i + "Save", $("#Attribute" + i + "Attachment").val() != ""); + } else { + checkAndEnableCheckbox("#Attribute" + i + "Save", $("#Attribute" + i + "Value").val() != ""); + } + $("#Attribute" + i + "Value").bind('input propertychange', function() { + checkAndEnableCheckbox("#Attribute" + i + "Save", $(this).val() != ""); + }); + $("#Attribute" + i + "Attachment").on('change', function() { + checkAndEnableCheckbox("#Attribute" + i + "Save", $("#Attribute" + i + "Attachment").val() != ""); + }); + }); +} + +function objectReferenceInput() { + var types = ["Attribute", "Object"]; + for (var type in types) { + for (var k in targetEvent[types[type]]) { + if (targetEvent[types[type]][k]['uuid'] == $('#ObjectReferenceUuid').val()) { + $('#targetSelect').val($('#ObjectReferenceUuid').val()); + changeObjectReferenceSelectOption(); + } + } + } +} + +function objectReferenceCheckForCustomRelationship() { + var relationship_type_field = $('#ObjectReferenceRelationshipTypeSelect option:selected'); + var relationship_type = $(relationship_type_field).val(); + if (relationship_type == 'custom') { + $('#ObjectReferenceRelationshipType').parent().removeClass('hidden'); + } else { + $('#ObjectReferenceRelationshipType').parent().addClass('hidden'); + } +} + +function changeObjectReferenceSelectOption() { + var object = $('#targetSelect option:selected'); + var uuid = $(object).val(); + $('#ObjectReferenceUuid').val(uuid); + var type = $(object).data('type'); + if (type == "Attribute") { + $('#targetData').html(""); + for (var k in targetEvent[type][uuid]) { + if ($.inArray(k, ['uuid', 'category', 'type', 'value', 'to_ids']) !== -1) { + $('#targetData').append('
:
'); + $('#' + uuid + '_' + k + '_key').text(k); + $('#' + uuid + '_' + k + '_data').text(targetEvent[type][uuid][k]); + } + } + } else { + $('#targetData').html(""); + for (var k in targetEvent[type][uuid]) { + if (k == 'Attribute') { + $('#targetData').append('
Attributes:'); + for (attribute in targetEvent[type][uuid]['Attribute']) { + for (k2 in targetEvent[type][uuid]['Attribute'][attribute]) { + if ($.inArray(k2, ['category', 'type', 'value', 'to_ids']) !== -1) { + $('#targetData').append('
:
'); + $('#' + targetEvent[type][uuid]['Attribute'][attribute]['uuid'] + '_' + k2 + '_key').text(k2); + $('#' + targetEvent[type][uuid]['Attribute'][attribute]['uuid'] + '_' + k2 + '_data').text(targetEvent[type][uuid]['Attribute'][attribute][k2]); + } + } + $('#targetData').append('
'); + } + } else { + if ($.inArray(k, ['name', 'uuid', 'meta-category']) !== -1) { + $('#targetData').append('
:
'); + $('#' + uuid + '_' + k + '_key').text(k); + $('#' + uuid + '_' + k + '_data').text(targetEvent[type][uuid][k]); + } + } + } + } +} + +$('.add_object_attribute_row').click(function() { + var template_id = $(this).data('template-id'); + var object_relation = $(this).data('object-relation'); + var k = $('#last-row').data('last-row'); + var k = k+1; + $('#last-row').data('last-row', k); + url = "/objects/get_row/" + template_id + "/" + object_relation + "/" + k; + $.get(url, function(data) { + $('#row_' + object_relation + '_expand').before($(data).fadeIn()).html(); + }); +}); + +$('.quickToggleCheckbox').toggle(function() { + var url = $(this).data('checkbox-url'); + +}); + (function(){ "use strict"; $(".datepicker").datepicker({