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

pull/5526/head
chrisr3d 2020-01-20 11:45:02 +01:00
commit ec9328c50c
61 changed files with 2609 additions and 154 deletions

2
PyMISP

@ -1 +1 @@
Subproject commit 30d916376e2a0f69b72e907ada370579372776bf
Subproject commit adf97dfeff25d28837ade5e8b47fc1cc28bbb082

View File

@ -1 +1 @@
{"major":2, "minor":4, "hotfix":119}
{"major":2, "minor":4, "hotfix":120}

View File

@ -46,10 +46,12 @@ class AppController extends Controller
public $helpers = array('Utility', 'OrgImg', 'FontAwesome', 'UserName');
private $__queryVersion = '94';
public $pyMispVersion = '2.4.119';
public $phpmin = '7.0';
public $phprec = '7.2';
private $__queryVersion = '96';
public $pyMispVersion = '2.4.120';
public $phpmin = '7.2';
public $phprec = '7.4';
public $pythonmin = '3.6';
public $pythonrec = '3.7';
public $isApiAuthed = false;
public $baseurl = '';

View File

@ -864,7 +864,7 @@ class AttributesController extends AppController
$skipTimeCheck = true;
}
if ($skipTimeCheck || $this->request->data['Attribute']['timestamp'] > $existingAttribute['Attribute']['timestamp']) {
$recoverFields = array('value', 'to_ids', 'distribution', 'category', 'type', 'comment');
$recoverFields = array('value', 'to_ids', 'distribution', 'category', 'type', 'comment', 'first_seen', 'last_seen');
foreach ($recoverFields as $rF) {
if (!isset($this->request->data['Attribute'][$rF])) {
$this->request->data['Attribute'][$rF] = $existingAttribute['Attribute'][$rF];
@ -1064,7 +1064,7 @@ class AttributesController extends AppController
if (!$this->_isRest()) {
$this->Attribute->Event->insertLock($this->Auth->user(), $this->Attribute->data['Attribute']['event_id']);
}
$validFields = array('value', 'category', 'type', 'comment', 'to_ids', 'distribution');
$validFields = array('value', 'category', 'type', 'comment', 'to_ids', 'distribution', 'first_seen', 'last_seen');
$changed = false;
if (empty($this->request->data['Attribute'])) {
$this->request->data = array('Attribute' => $this->request->data);
@ -1662,7 +1662,7 @@ class AttributesController extends AppController
unset($this->request->data['to_ids']);
$this->request->data['ignore'] = 1;
}
$paramArray = array('value' , 'type', 'category', 'org', 'tags', 'from', 'to', 'last', 'eventid', 'withAttachments', 'uuid', 'publish_timestamp', 'timestamp', 'enforceWarninglist', 'to_ids', 'deleted', 'includeEventUuid', 'event_timestamp', 'threat_level_id', 'includeEventTags');
$paramArray = array('value' , 'type', 'category', 'org', 'tags', 'from', 'to', 'last', 'eventid', 'withAttachments', 'uuid', 'publish_timestamp', 'timestamp', 'enforceWarninglist', 'to_ids', 'deleted', 'includeEventUuid', 'event_timestamp', 'threat_level_id', 'includeEventTags', 'first_seen', 'last_seen');
$filterData = array(
'request' => $this->request,
'named_params' => $this->params['named'],
@ -2152,7 +2152,7 @@ class AttributesController extends AppController
public function fetchViewValue($id, $field = null)
{
$validFields = array('value', 'comment', 'type', 'category', 'to_ids', 'distribution', 'timestamp');
$validFields = array('value', 'comment', 'type', 'category', 'to_ids', 'distribution', 'timestamp', 'first_seen', 'last_seen');
if (!isset($field) || !in_array($field, $validFields)) {
throw new MethodNotAllowedException(__('Invalid field requested.'));
}
@ -2199,7 +2199,7 @@ class AttributesController extends AppController
public function fetchEditForm($id, $field = null)
{
$validFields = array('value', 'comment', 'type', 'category', 'to_ids', 'distribution');
$validFields = array('value', 'comment', 'type', 'category', 'to_ids', 'distribution', 'first_seen', 'last_seen');
if (!isset($field) || !in_array($field, $validFields)) {
throw new MethodNotAllowedException(__('Invalid field requested.'));
}

View File

@ -144,6 +144,7 @@ class ACLComponent extends Component
'getEventGraphReferences' => array('*'),
'getEventGraphTags' => array('*'),
'getEventGraphGeneric' => array('*'),
'getEventTimeline' => array('*'),
'genDistributionGraph' => array('*'),
'getDistributionGraph' => array('*'),
'getReferenceData' => array('*'),
@ -277,6 +278,11 @@ class ACLComponent extends Component
'edit' => array('perm_add'),
'get_row' => array('perm_add'),
'orphanedObjectDiagnostics' => array(),
'editField' => array('perm_add'),
'fetchEditForm' => array('perm_add'),
'fetchViewValue' => array('*'),
'quickAddAttributeForm' => array('perm_add'),
'quickFetchTemplateWithValidObjectAttributes' => array('perm_add'),
'proposeObjectsFromAttributes' => array('*'),
'groupAttributesIntoObject' => array('perm_add'),
'revise_object' => array('perm_add'),

View File

@ -22,13 +22,13 @@ class RestResponseComponent extends Component
'add' => array(
'description' => "POST a MISP Attribute JSON to this API to create an Attribute.",
'mandatory' => array('value', 'type'),
'optional' => array('category', 'to_ids', 'uuid', 'distribution', 'sharing_group_id', 'timestamp', 'comment', 'data', 'encrypt'),
'optional' => array('category', 'to_ids', 'uuid', 'distribution', 'sharing_group_id', 'timestamp', 'comment', 'data', 'encrypt', 'first_seen', 'last_seen'),
'params' => array('event_id')
),
'edit' => array(
'description' => "POST a MISP Attribute JSON to this API to update an Attribute. If the timestamp is set, it has to be newer than the existing Attribute.",
'mandatory' => array(),
'optional' => array('value', 'type', 'category', 'to_ids', 'uuid', 'distribution', 'sharing_group_id', 'timestamp', 'comment', 'date', 'encrypt'),
'optional' => array('value', 'type', 'category', 'to_ids', 'uuid', 'distribution', 'sharing_group_id', 'timestamp', 'comment', 'date', 'encrypt', 'first_seen', 'last_seen'),
'params' => array('attribute_id')
),
'deleteSelected' => array(
@ -40,7 +40,7 @@ class RestResponseComponent extends Component
'restSearch' => array(
'description' => "Search MISP using a list of filter parameters and return the data in the selected format. The search is available on an event and an attribute level, just select the scope via the URL (/events/restSearch vs /attributes/restSearch). Besides the parameters listed, other, format specific ones can be passed along (for example: requested_attributes and includeContext for the CSV export). This API allows pagination via the page and limit parameters.",
'mandatory' => array('returnFormat'),
'optional' => array('page', 'limit', 'value' , 'type', 'category', 'org', 'tags', 'date', 'last', 'eventid', 'withAttachments', 'uuid', 'publish_timestamp', 'timestamp', 'attribute_timestamp', 'enforceWarninglist', 'to_ids', 'deleted', 'includeEventUuid', 'includeEventTags', 'event_timestamp', 'threat_level_id', 'eventinfo', 'includeProposals', 'includeDecayScore', 'includeFullModel', 'decayingModel', 'excludeDecayed', 'score'),
'optional' => array('page', 'limit', 'value' , 'type', 'category', 'org', 'tags', 'date', 'last', 'eventid', 'withAttachments', 'uuid', 'publish_timestamp', 'timestamp', 'attribute_timestamp', 'enforceWarninglist', 'to_ids', 'deleted', 'includeEventUuid', 'includeEventTags', 'event_timestamp', 'threat_level_id', 'eventinfo', 'includeProposals', 'includeDecayScore', 'includeFullModel', 'decayingModel', 'excludeDecayed', 'score', 'first_seen', 'last_seen'),
'params' => array()
)
),
@ -392,6 +392,7 @@ class RestResponseComponent extends Component
if (isset($this->__convertActionToMessage[$controller][$action['action']])) {
$stringifiedAction = $this->__convertActionToMessage[$controller][$action['action']];
}
$response['saved'] = false;
$response['name'] = 'Could not ' . $stringifiedAction . ' ' . Inflector::singularize($controller);
$response['message'] = $response['name'];
$response['url'] = $this->__generateURL($action, $controller, $id);
@ -405,6 +406,8 @@ class RestResponseComponent extends Component
if (!$message) {
$message = Inflector::singularize($controller) . ' ' . $action['action'] . ((substr($action['action'], -1) == 'e') ? 'd' : 'ed');
}
$response['saved'] = true;
$response['success'] = true;
$response['name'] = $message;
$response['message'] = $response['name'];
$response['url'] = $this->__generateURL($action, $controller, $id);
@ -914,6 +917,12 @@ class RestResponseComponent extends Component
'operators' => array('equal'),
'help' => __('A valid external auth key')
),
'first_seen' => array(
'input' => 'text',
'type' => 'string',
'operators' => array('equal'),
'help' => 'A valid ISO 8601 datetime format, up to milli-seconds. i.e.: 2019-06-13T15:56:56.856074+02:00'
),
'fixed_event' => array(
'input' => 'select',
'type' => 'integer',
@ -1036,6 +1045,12 @@ class RestResponseComponent extends Component
'operators' => array('equal', 'not_equal'),
'help' => __('Events published within the last x amount of time, where x can be defined in days, hours, minutes (for example 5d or 12h or 30m)')
),
'last_seen' => array(
'input' => 'text',
'type' => 'string',
'operators' => array('equal'),
'help' => 'A valid ISO 8601 datetime format, up to milli-seconds. i.e.: 2019-06-13T15:56:56.856074+02:00'
),
'limit' => array(
'input' => 'number',
'type' => 'integer',
@ -1630,33 +1645,37 @@ class RestResponseComponent extends Component
// add dynamic data and overwrite name collisions
switch($field) {
case "returnFormat":
$this->__overwriteReturnFormat($scope, $fieldsConstraint[$field]);
$this->__overwriteReturnFormat($scope, $action, $fieldsConstraint[$field]);
break;
case "type":
$this->__overwriteType($scope, $fieldsConstraint[$field]);
$this->__overwriteType($scope, $action, $fieldsConstraint[$field]);
break;
case "category":
$this->__overwriteCategory($scope, $fieldsConstraint[$field]);
$this->__overwriteCategory($scope, $action, $fieldsConstraint[$field]);
break;
case "decayingModel":
$this->__overwriteDecayingModel($scope, $fieldsConstraint[$field]);
break;
case "distribution":
$this->__overwriteDistribution($scope, $fieldsConstraint[$field]);
$this->__overwriteDistribution($scope, $action, $fieldsConstraint[$field]);
break;
case "tag":
case "tags":
case "EventTag":
$this->__overwriteTags($scope, $fieldsConstraint[$field]);
$this->__overwriteTags($scope, $action, $fieldsConstraint[$field]);
break;
case "nationality":
$this->__overwriteNationality($scope, $fieldsConstraint[$field]);
$this->__overwriteNationality($scope, $action, $fieldsConstraint[$field]);
break;
case "action":
$this->__overwriteAction($scope, $fieldsConstraint[$field]);
$this->__overwriteAction($scope, $action, $fieldsConstraint[$field]);
break;
case "role_id":
$this->__overwriteRoleId($scope, $fieldsConstraint[$field]);
$this->__overwriteRoleId($scope, $action, $fieldsConstraint[$field]);
break;
case "first_seen":
case "last_seen":
$this->__overwriteSeen($scope, $action, $fieldsConstraint[$field]);
break;
default:
break;
@ -1672,7 +1691,7 @@ class RestResponseComponent extends Component
}
// Fetch the correct values based on the scope, then overwrite default value
private function __overwriteReturnFormat($scope, &$field) {
private function __overwriteReturnFormat($scope, $action, &$field) {
switch($scope) {
case "Attribute":
$field['values'] = array_keys(ClassRegistry::init($scope)->validFormats);
@ -1682,7 +1701,7 @@ class RestResponseComponent extends Component
break;
}
}
private function __overwriteType($scope, &$field) {
private function __overwriteType($scope, $action, &$field) {
$field['input'] = 'select';
switch($scope) {
case "Attribute":
@ -1700,10 +1719,10 @@ class RestResponseComponent extends Component
}
}
private function __overwriteCategory($scope, &$field) {
private function __overwriteCategory($scope, $action, &$field) {
$field['values'] = array_keys(ClassRegistry::init("Attribute")->categoryDefinitions);
}
private function __overwriteDistribution($scope, &$field) {
private function __overwriteDistribution($scope, $action, &$field) {
$field['values'] = array();
foreach(ClassRegistry::init("Attribute")->distributionLevels as $d => $text) {
$field['values'][] = array('label' => $text, 'value' => $d);
@ -1720,7 +1739,7 @@ class RestResponseComponent extends Component
$field['values'][] = array('label' => h($model_name), 'value' => $i);
}
}
private function __overwriteTags($scope, &$field) {
private function __overwriteTags($scope, $action, &$field) {
$this->{$scope} = ClassRegistry::init("Tag");
$tags = $this->{$scope}->find('list', array(
'recursive' => -1,
@ -1733,13 +1752,13 @@ class RestResponseComponent extends Component
}
$field['values'] = $tags;
}
private function __overwriteNationality($scope, &$field) {
private function __overwriteNationality($scope, $action, &$field) {
$field['values'] = ClassRegistry::init("Organisation")->countries;
}
private function __overwriteAction($scope, &$field) {
private function __overwriteAction($scope, $action, &$field) {
$field['values'] = array_keys(ClassRegistry::init("Log")->actionDefinitions);
}
private function __overwriteRoleId($scope, &$field) {
private function __overwriteRoleId($scope, $action, &$field) {
$this->{$scope} = ClassRegistry::init("Role");
$roles = $this->{$scope}->find('list', array(
'recursive' => -1,
@ -1747,5 +1766,10 @@ class RestResponseComponent extends Component
));
$field['values'] = $roles;
}
private function __overwriteSeen($scope, $action, &$field) {
if ($action == 'restSearch') {
$field['help'] = __('Seen within the last x amount of time, where x can be defined in days, hours, minutes (for example 5d or 12h or 30m)');
}
}
}

View File

@ -10,7 +10,7 @@ class RestSearchComponent extends Component
'published', 'timestamp','enforceWarninglist', 'to_ids', 'deleted', 'includeEventUuid', 'event_timestamp', 'threat_level_id', 'includeEventTags',
'includeProposals', 'returnFormat', 'limit', 'page', 'requested_attributes', 'includeContext', 'headerless',
'includeWarninglistHits', 'attackGalaxy', 'object_relation', 'includeSightings', 'includeCorrelations', 'includeDecayScore',
'decayingModel', 'excludeDecayed', 'modelOverrides', 'includeFullModel', 'score', 'attribute_timestamp'
'decayingModel', 'excludeDecayed', 'modelOverrides', 'includeFullModel', 'score', 'attribute_timestamp', 'first_seen', 'last_seen'
),
'Event' => array(
'returnFormat', 'value', 'type', 'category', 'org', 'tags', 'searchall', 'from', 'to', 'last', 'eventid', 'withAttachments',

View File

@ -4502,6 +4502,32 @@ class EventsController extends AppController
return $json;
}
public function getEventTimeline($id, $type = 'event')
{
$validTools = array('event');
if (!in_array($type, $validTools)) {
throw new MethodNotAllowedException('Invalid type.');
}
App::uses('EventTimelineTool', 'Tools');
$grapher = new EventTimelineTool();
$data = $this->request->is('post') ? $this->request->data : array();
$dataFiltering = array_key_exists('filtering', $data) ? $data['filtering'] : array();
$extended = isset($this->params['named']['extended']) ? 1 : 0;
$grapher->construct($this->Event, $this->Auth->user(), $dataFiltering, $extended);
$json = $grapher->get_timeline($id);
array_walk_recursive($json, function (&$item, $key) {
if (!mb_detect_encoding($item, 'utf-8', true)) {
$item = utf8_encode($item);
}
});
$this->response->type('json');
return new CakeResponse(array('body' => json_encode($json), 'status' => 200, 'type' => 'json'));
}
public function getDistributionGraph($id, $type = 'event')
{
$extended = isset($this->params['named']['extended']) ? 1 : 0;

View File

@ -358,7 +358,7 @@ class ObjectsController extends AppController
$this->set('element', $element);
}
public function edit($id, $update_template_available=false)
public function edit($id, $update_template_available=false, $onlyAddNewAttribute=false)
{
$id = $this->Toolbox->findIdByUuid($this->MispObject, $id);
$object = $this->MispObject->fetchObjects($this->Auth->user(), array('conditions' => array('Object.id' => $id)));
@ -413,7 +413,7 @@ class ObjectsController extends AppController
unset($this->request->data['Object']);
}
$objectToSave = $this->MispObject->attributeCleanup($this->request->data);
$objectToSave = $this->MispObject->deltaMerge($object, $objectToSave);
$objectToSave = $this->MispObject->deltaMerge($object, $objectToSave, $onlyAddNewAttribute);
// 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
@ -431,9 +431,25 @@ class ObjectsController extends AppController
return $this->RestResponse->saveFailResponse('Objects', 'add', false, $id, $this->response->type());
}
} else {
$this->MispObject->Event->unpublishEvent($object['Object']['event_id']);
$this->Flash->success('Object saved.');
$this->redirect(array('controller' => 'events', 'action' => 'view', $object['Object']['event_id']));
$message = __('Object attributes saved.');
$error_message = __('Object attributes could not be saved.');
if ($this->request->is('ajax')) {
$this->autoRender = false;
if (is_numeric($objectToSave)) {
$this->MispObject->Event->unpublishEvent($object['Object']['event_id']);
return new CakeResponse(array('body'=> json_encode(array('saved' => true, 'success' => $message)), 'status'=>200, 'type' => 'json'));
} else {
return new CakeResponse(array('body'=> json_encode(array('saved' => true, 'errors' => $error_message)), 'status'=>200, 'type' => 'json'));
}
} else {
if (is_numeric($objectToSave)) {
$this->MispObject->Event->unpublishEvent($object['Object']['event_id']);
$this->Flash->success('Object saved.');
} else {
$this->Flash->error($error_message);
}
$this->redirect(array('controller' => 'events', 'action' => 'view', $object['Object']['event_id']));
}
}
} else {
$enabledRows = array();
@ -471,6 +487,320 @@ class ObjectsController extends AppController
$this->render('add');
}
// 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.
public function editField($id)
{
if (Validation::uuid($id)) {
$this->MispObject->recursive = -1;
$temp = $this->MispObject->findByUuid($id);
if ($temp == null) {
throw new NotFoundException(__('Invalid object'));
}
$id = $temp['Object']['id'];
} elseif (!is_numeric($id)) {
throw new NotFoundException(__('Invalid event id.'));
}
if ((!$this->request->is('post') && !$this->request->is('put'))) {
throw new MethodNotAllowedException(__('This function can only be accessed via POST or PUT'));
}
$object = $this->MispObject->find('first', array(
'conditions' => array('Object.id' => $id),
'contain' => 'Event',
'recursive' => -1
));
if (empty($object)) {
return $this->RestResponse->saveFailResponse('Objects', 'edit', false, 'Invalid object');
}
if (!$this->_isSiteAdmin()) {
if ($this->MispObject->data['Event']['orgc_id'] == $this->Auth->user('org_id')
&& (($this->userRole['perm_modify'] && $this->MispObject->data['Event']['user_id'] != $this->Auth->user('id'))
|| $this->userRole['perm_modify_org'])) {
// Allow the edit
} else {
return $this->RestResponse->saveFailResponse('Objects', 'edit', false, 'Invalid attribute');
}
}
$validFields = array('comment', 'distribution', 'first_seen', 'last_seen');
$changed = false;
if (empty($this->request->data['Object'])) {
$this->request->data = array('Object' => $this->request->data);
if (empty($this->request->data['Object'])) {
throw new MethodNotAllowedException('Invalid input.');
}
}
$seen_changed = false;
foreach ($this->request->data['Object'] as $changedKey => $changedField) {
if (!in_array($changedKey, $validFields)) {
throw new MethodNotAllowedException('Invalid field.');
}
if ($object['Object'][$changedKey] == $changedField) {
$this->autoRender = false;
return $this->RestResponse->saveSuccessResponse('Objects', 'edit', $id, false, 'nochange');
}
$seen_changed = $changedKey == 'first_seen' || $changedKey == 'last_seen';
$object['Object'][$changedKey] = $changedField;
$changed = true;
}
$forcedSeenOnElements = array();
if (!$changed) {
return $this->RestResponse->saveSuccessResponse('Objects', 'edit', $id, false, 'nochange');
} elseif ($seen_changed) {
$forcedSeenOnElements[$changedKey] = $changedField;
}
$date = new DateTime();
$object['Object']['timestamp'] = $date->getTimestamp();
$object = $this->MispObject->syncObjectAndAttributeSeen($object, $forcedSeenOnElements);
if ($this->MispObject->save($object)) {
$event = $this->MispObject->Event->find('first', array(
'recursive' => -1,
'fields' => array('id', 'published', 'timestamp', 'info', 'uuid'),
'conditions' => array(
'id' => $object['Object']['event_id'],
)));
$event['Event']['timestamp'] = $date->getTimestamp();
$event['Event']['published'] = 0;
if ($seen_changed) {
$this->MispObject->Attribute->saveAttributes($object['Attribute']);
}
$this->MispObject->Event->save($event, array('fieldList' => array('published', 'timestamp', 'info')));
return $this->RestResponse->saveSuccessResponse('Objects', 'edit', $id, false, 'Field updated');
} else {
return $this->RestResponse->saveFailResponse('Objects', 'edit', false, $this->MispObject->validationErrors);
}
}
public function fetchViewValue($id, $field = null)
{
$validFields = array('timestamp', 'comment', 'distribution', 'first_seen', 'last_seen');
if (!isset($field) || !in_array($field, $validFields)) {
throw new MethodNotAllowedException('Invalid field requested.');
}
if (!$this->request->is('ajax')) {
throw new MethodNotAllowedException('This function can only be accessed via AJAX.');
}
$params = array(
'conditions' => array('Object.id' => $id),
'fields' => array('id', 'distribution', 'event_id', $field),
'contain' => array(
'Event' => array(
'fields' => array('distribution', 'id', 'org_id'),
)
),
'flatten' => 1
);
$object = $this->MispObject->fetchObjectSimple($this->Auth->user(), $params);
if (empty($object)) {
throw new NotFoundException(__('Invalid object'));
}
$object = $object[0];
$result = $object['Object'][$field];
if ($field == 'distribution') {
$result=$this->MispObject->shortDist[$result];
}
$this->set('value', $result);
$this->layout = 'ajax';
$this->render('ajax/objectViewFieldForm');
}
public function fetchEditForm($id, $field = null)
{
$validFields = array('distribution', 'comment', 'first_seen', 'last_seen');
if (!isset($field) || !in_array($field, $validFields)) {
throw new MethodNotAllowedException('Invalid field requested.');
}
if (!$this->request->is('ajax')) {
throw new MethodNotAllowedException('This function can only be accessed via AJAX.');
}
$fields = array('id', 'distribution', 'event_id');
$fields[] = $field;
$params = array(
'conditions' => array('Object.id' => $id),
'fields' => $fields,
'contain' => array(
'Event' => array(
'fields' => array('distribution', 'id', 'user_id', 'orgc_id', 'org_id'),
)
)
);
$object = $this->MispObject->fetchObjectSimple($this->Auth->user(), $params);
if (empty($object)) {
throw new NotFoundException(__('Invalid attribute'));
}
$object = $object[0];
if (!$this->_isSiteAdmin()) {
if ($object['Event']['orgc_id'] == $this->Auth->user('org_id')
&& (($this->userRole['perm_modify'] && $object['Event']['user_id'] != $this->Auth->user('id'))
|| $this->userRole['perm_modify_org'])) {
// Allow the edit
} else {
throw new NotFoundException(__('Invalid object'));
}
}
$this->layout = 'ajax';
if ($field == 'distribution') {
$distributionLevels = $this->MispObject->shortDist;
unset($distributionLevels[4]);
$this->set('distributionLevels', $distributionLevels);
}
$this->set('object', $object['Object']);
$fieldURL = ucfirst($field);
$this->render('ajax/objectEdit' . $fieldURL . 'Form');
}
// Construct a template with valid object attributes to add to an object
public function quickFetchTemplateWithValidObjectAttributes($id) {
$this->MispObject->id = $id;
if (!$this->MispObject->exists()) {
if ($this->request->is('ajax')) {
return $this->RestResponse->saveFailResponse('Objects', 'add', false, 'Invalid object', $this->response->type());
} else {
throw new NotFoundException(__('Invalid object'));
}
}
$fields = array('template_uuid', 'template_version', 'id');
$params = array(
'conditions' => array('Object.id' => $id),
'fields' => $fields,
'flatten' => 1,
);
// fetchObjects restrict access based on user
$object = $this->MispObject->fetchObjects($this->Auth->user(), $params);
if (empty($object)) {
if ($this->request->is('ajax')) {
return $this->RestResponse->saveFailResponse('Objects', 'add', false, 'Invalid object', $this->response->type());
} else {
throw new NotFoundException(__('Invalid object'));
}
} else {
$object = $object[0];
}
// get object attributes already set
$objectRelation = array();
foreach($object['Attribute'] as $attr) {
$objectRelation[$attr['object_relation']] = 1;
}
$objectRelation = array_keys($objectRelation);
// get object attribute defined in the object's template
$template = $this->MispObject->ObjectTemplate->find('first', array(
'conditions' => array(
'ObjectTemplate.uuid' => $object['Object']['template_uuid'],
'ObjectTemplate.version' => $object['Object']['template_version'],
),
'recursive' => -1,
'flatten' => 1,
'contain' => 'ObjectTemplateElement'
));
if (empty($template)) {
if ($this->request->is('ajax')) {
return $this->RestResponse->saveFailResponse('Objects', 'add', false, 'Invalid template', $this->response->type());
} else {
throw new NotFoundException(__('Invalid template'));
}
}
// unset object invalid object attribute
foreach($template['ObjectTemplateElement'] as $i => $objAttr) {
if (in_array($objAttr['object_relation'], $objectRelation) && !$objAttr['multiple']) {
unset($template['ObjectTemplateElement'][$i]);
}
}
if ($this->request->is('get') || $this->request->is('post')) {
$this->set('template', $template);
$this->set('objectId', $object['Object']['id']);
$items = array();
foreach ($template['ObjectTemplateElement'] as $objectAttribute) {
$name = sprintf('%s :: %s', $objectAttribute['object_relation'], $objectAttribute['type']);
$items[] = array(
'name' => $name,
'value' => '/objects/quickAddAttributeForm/' . $object['Object']['id'] . '/' . $objectAttribute['object_relation'],
'template' => array(
'name' => $name,
'infoExtra' => $objectAttribute['description'],
)
);
}
$this->set('options', array(
'flag_redraw_chosen' => true
));
$this->set('items', $items);
$this->render('/Elements/generic_picker');
} else {
return $template;
}
}
/**
* GET: Returns a form allowing to add a valid object attribute to an object
* POST/PUT: Add the attribute to the object
*/
public function quickAddAttributeForm($id, $fieldName = null) {
if ($this->request->is('GET')) {
if (!isset($fieldName)) {
throw new MethodNotAllowedException('No field requested.');
}
$this->MispObject->id = $id;
if (!$this->MispObject->exists()) {
throw new NotFoundException(__('Invalid object'));
}
$fields = array('template_uuid', 'template_version', 'id', 'event_id');
$params = array(
'conditions' => array('Object.id' => $id),
'fields' => $fields,
'flatten' => 1,
);
// fetchObjects restrict access based on user
$object = $this->MispObject->fetchObjects($this->Auth->user(), $params);
if (empty($object)) {
throw new NotFoundException(__('Invalid object'));
} else {
$object = $object[0];
}
$template = $this->MispObject->ObjectTemplate->find('first', array(
'conditions' => array(
'ObjectTemplate.uuid' => $object['Object']['template_uuid'],
'ObjectTemplate.version' => $object['Object']['template_version'],
),
'recursive' => -1,
'flatten' => 1,
'contain' => array(
'ObjectTemplateElement' => array('conditions' => array(
'object_relation' => $fieldName
))
)
));
if (empty($template)) {
throw new NotFoundException(__('Invalid object'));
}
if (empty($template['ObjectTemplateElement'])) {
throw new NotFoundException(__('Invalid fields') . ' `' . h($fieldName) . '`');
}
// check if fields can be added
foreach($object['Attribute'] as $i => $objAttr) {
$objectAttrFromTemplate = $template['ObjectTemplateElement'][0];
if ($objAttr['object_relation'] == $fieldName && !$objectAttrFromTemplate['multiple']) {
throw new NotFoundException(__('Invalid field'));
}
}
$template = $this->MispObject->prepareTemplate($template, $object);
$this->layout = 'ajax';
$this->set('object', $object['Object']);
$template_element = $template['ObjectTemplateElement'][0];
unset($template_element['value']); // avoid filling if multiple
$this->set('template_element', $template_element);
$distributionData = $this->MispObject->Event->Attribute->fetchDistributionData($this->Auth->user());
$this->set('distributionData', $distributionData);
$info = array();
foreach ($distributionData['levels'] as $key => $value) {
$info['distribution'][$key] = array('key' => $value, 'desc' => $this->MispObject->Event->Attribute->distributionDescriptions[$key]['formdesc']);
}
$this->set('info', $info);
$this->render('ajax/quickAddAttributeForm');
} else if ($this->request->is('post') || $this->request->is('put')) {
return $this->edit($this->request->data['Object']['id'], false, true);
}
}
public function delete($id, $hard = false)
{
$id = $this->Toolbox->findIdByUuid($this->MispObject, $id);

View File

@ -1135,6 +1135,9 @@ class ServersController extends AppController
$this->set('phpversion', phpversion());
$this->set('phpmin', $this->phpmin);
$this->set('phprec', $this->phprec);
$this->set('pythonmin', $this->pythonmin);
$this->set('pythonrec', $this->pythonrec);
$this->set('pymisp', $this->pymisp);
}
}

View File

@ -96,7 +96,7 @@ class ShadowAttributesController extends AppController
$this->Attribute->delete($activeAttribute['Attribute']['id']);
} else {
// Update the live attribute with the shadow data
$fieldsToUpdate = array('value1', 'value2', 'value', 'type', 'category', 'comment', 'to_ids');
$fieldsToUpdate = array('value1', 'value2', 'value', 'type', 'category', 'comment', 'to_ids', 'first_seen', 'last_seen');
foreach ($fieldsToUpdate as $f) {
$activeAttribute['Attribute'][$f] = $shadow[$f];
}
@ -415,7 +415,7 @@ class ShadowAttributesController extends AppController
array(
'conditions' => array('ShadowAttribute.id' => $this->ShadowAttribute->id),
'recursive' => -1,
'fields' => array('id', 'old_id', 'event_id', 'type', 'category', 'value', 'comment','to_ids', 'uuid', 'event_org_id', 'email', 'deleted', 'timestamp')
'fields' => array('id', 'old_id', 'event_id', 'type', 'category', 'value', 'comment','to_ids', 'uuid', 'event_org_id', 'email', 'deleted', 'timestamp', 'first_seen', 'last_seen')
)
);
$this->set('ShadowAttribute', $sa['ShadowAttribute']);
@ -689,12 +689,12 @@ class ShadowAttributesController extends AppController
if ($attachment) {
$fields = array(
'static' => array('old_id' => 'Attribute.id', 'uuid' => 'Attribute.uuid', 'event_id' => 'Attribute.event_id', 'event_uuid' => 'Event.uuid', 'event_org_id' => 'Event.orgc_id', 'category' => 'Attribute.category', 'type' => 'Attribute.type'),
'optional' => array('value', 'to_ids', 'comment')
'optional' => array('value', 'to_ids', 'comment', 'first_seen', 'last_seen')
);
} else {
$fields = array(
'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')
'optional' => array('category', 'type', 'value', 'to_ids', 'comment', 'first_seen', 'last_seen')
);
if ($existingAttribute['Attribute']['object_id']) {
unset($fields['optional']['type']);
@ -731,7 +731,7 @@ class ShadowAttributesController extends AppController
array(
'conditions' => array('ShadowAttribute.id' => $this->ShadowAttribute->id),
'recursive' => -1,
'fields' => array('id', 'old_id', 'event_id', 'type', 'category', 'value', 'comment','to_ids', 'uuid', 'event_org_id', 'email', 'deleted', 'timestamp')
'fields' => array('id', 'old_id', 'event_id', 'type', 'category', 'value', 'comment','to_ids', 'uuid', 'event_org_id', 'email', 'deleted', 'timestamp', 'first_seen', 'last_seen')
)
);
$this->set('ShadowAttribute', $sa['ShadowAttribute']);
@ -833,6 +833,8 @@ class ShadowAttributesController extends AppController
'type' => $existingAttribute['Attribute']['type'],
'to_ids' => $existingAttribute['Attribute']['to_ids'],
'value' => $existingAttribute['Attribute']['value'],
'first_seen' => $existingAttribute['Attribute']['first_seen'],
'last_seen' => $existingAttribute['Attribute']['last_seen'],
'email' => $this->Auth->user('email'),
'org_id' => $this->Auth->user('org_id'),
'proposal_to_delete' => true,
@ -873,7 +875,7 @@ class ShadowAttributesController extends AppController
'recursive' => -1,
'contain' => 'Event',
'fields' => array(
'ShadowAttribute.id', 'ShadowAttribute.old_id', 'ShadowAttribute.event_id', 'ShadowAttribute.type', 'ShadowAttribute.category', 'ShadowAttribute.uuid', 'ShadowAttribute.to_ids', 'ShadowAttribute.value', 'ShadowAttribute.comment', 'ShadowAttribute.org_id',
'ShadowAttribute.id', 'ShadowAttribute.old_id', 'ShadowAttribute.event_id', 'ShadowAttribute.type', 'ShadowAttribute.category', 'ShadowAttribute.uuid', 'ShadowAttribute.to_ids', 'ShadowAttribute.value', 'ShadowAttribute.comment', 'ShadowAttribute.org_id', 'ShadowAttribute.first_seen', 'ShadowAttribute.last_seen',
'Event.id', 'Event.orgc_id', 'Event.org_id', 'Event.distribution', 'Event.uuid'
),
'conditions' => array('AND' => array('ShadowAttribute.id' => $id, $distConditions, 'ShadowAttribute.deleted' => 0))
@ -944,7 +946,7 @@ class ShadowAttributesController extends AppController
}
$params = array(
'conditions' => $conditions,
'fields' => array('ShadowAttribute.id', 'ShadowAttribute.old_id', 'ShadowAttribute.event_id', 'ShadowAttribute.type', 'ShadowAttribute.category', 'ShadowAttribute.uuid', 'ShadowAttribute.to_ids', 'ShadowAttribute.value', 'ShadowAttribute.comment', 'ShadowAttribute.org_id', 'ShadowAttribute.timestamp'),
'fields' => array('ShadowAttribute.id', 'ShadowAttribute.old_id', 'ShadowAttribute.event_id', 'ShadowAttribute.type', 'ShadowAttribute.category', 'ShadowAttribute.uuid', 'ShadowAttribute.to_ids', 'ShadowAttribute.value', 'ShadowAttribute.comment', 'ShadowAttribute.org_id', 'ShadowAttribute.timestamp', 'ShadowAttribute.first_seen', 'ShadowAttribute.last_seen'),
'contain' => array(
'Event' => array(
'fields' => array('id', 'org_id', 'info', 'orgc_id', 'uuid'),
@ -1124,7 +1126,7 @@ class ShadowAttributesController extends AppController
}
}
$keys = array_flip(array('uuid', 'event_id', 'value', 'type', 'category', 'to_ids'));
$keys = array_flip(array('uuid', 'event_id', 'value', 'type', 'category', 'to_ids', 'first_seen', 'last_seen'));
$proposal = array_intersect_key($attribute['Attribute'], $keys);
$proposal['email'] = $this->Auth->user('email');

View File

@ -232,12 +232,12 @@
$event = $this->__get_filtered_event($id);
$this->__json['items'] = array();
$this->__json['relations'] = array();
$this->__json['existing_object_relation'] = array();
if (empty($event)) {
return $this->__json;
}
if (!empty($event['Object'])) {
$object = $event['Object'];
} else {
@ -312,7 +312,7 @@
if (empty($event)) {
return $this->__json;
}
if (!empty($event['Object'])) {
$object = $event['Object'];
} else {
@ -419,7 +419,7 @@
if (empty($event)) {
return $this->__json;
}
if (!empty($event['Object'])) {
$object = $event['Object'];
} else {

View File

@ -0,0 +1,138 @@
<?php
class EventTimelineTool
{
private $__lookupTables = array();
private $__user = false;
private $__json = array();
private $__eventModel = false;
private $__refModel = false;
# Will be use latter on
private $__related_events = array();
private $__related_attributes = array();
public function construct($eventModel, $user, $filterRules, $extended_view=0)
{
$this->__eventModel = $eventModel;
$this->__objectTemplateModel = $eventModel->Object->ObjectTemplate;
$this->__user = $user;
$this->__filterRules = $filterRules;
$this->__json = array();
$this->__extended_view = $extended_view;
$this->__lookupTables = array(
'analysisLevels' => $this->__eventModel->analysisLevels,
'distributionLevels' => $this->__eventModel->Attribute->distributionLevels
);
return true;
}
public function construct_for_ref($refModel, $user)
{
$this->__refModel = $refModel;
$this->__user = $user;
$this->__json = array();
return true;
}
private function __get_event($id)
{
$fullevent = $this->__eventModel->fetchEvent($this->__user, array('eventid' => $id, 'flatten' => 0, 'includeTagRelations' => 1, 'extended' => $this->__extended_view));
$event = array();
if (empty($fullevent)) {
return $event;
}
if (!empty($fullevent[0]['Object'])) {
$event['Object'] = $fullevent[0]['Object'];
} else {
$event['Object'] = array();
}
if (!empty($fullevent[0]['Attribute'])) {
$event['Attribute'] = $fullevent[0]['Attribute'];
} else {
$event['Attribute'] = array();
}
return $event;
}
public function get_timeline($id)
{
$event = $this->__get_event($id);
$this->__json['items'] = array();
if (empty($event)) {
return $this->__json;
}
if (!empty($event['Object'])) {
$object = $event['Object'];
} else {
$object = array();
}
if (!empty($event['Attribute'])) {
$attribute = $event['Attribute'];
} else {
$attribute = array();
}
// extract links and node type
foreach ($attribute as $attr) {
$toPush = array(
'id' => $attr['id'],
'uuid' => $attr['uuid'],
'content' => $attr['value'],
'event_id' => $attr['event_id'],
'group' => 'attribute',
'timestamp' => $attr['timestamp'],
'first_seen' => $attr['first_seen'],
'last_seen' => $attr['last_seen'],
);
$this->__json['items'][] = $toPush;
}
foreach ($object as $obj) {
$toPush_obj = array(
'id' => $obj['id'],
'uuid' => $obj['uuid'],
'content' => $obj['name'],
'group' => 'object',
'meta-category' => $obj['meta-category'],
'template_uuid' => $obj['template_uuid'],
'event_id' => $obj['event_id'],
'timestamp' => $obj['timestamp'],
'Attribute' => array(),
);
$toPush_obj['first_seen'] = $obj['first_seen'];
$toPush_obj['last_seen'] = $obj['last_seen'];
$toPush_obj['first_seen_overwrite'] = false;
$toPush_obj['last_seen_overwrite'] = false;
foreach ($obj['Attribute'] as $obj_attr) {
// replaced *_seen based on object attribute
if ($obj_attr['object_relation'] == 'first-seen' && is_null($toPush_obj['first_seen'])) {
$toPush_obj['first_seen'] = $obj_attr['value']; // replace first_seen of the object to seen of the element
$toPush_obj['first_seen_overwrite'] = true;
} elseif ($obj_attr['object_relation'] == 'last-seen' && is_null($toPush_obj['last_seen'])) {
$toPush_obj['last_seen'] = $obj_attr['value']; // replace last_seen of the object to seen of the element
$toPush_obj['last_seen_overwrite'] = true;
}
$toPush_attr = array(
'id' => $obj_attr['id'],
'uuid' => $obj_attr['uuid'],
'content' => $obj_attr['value'],
'contentType' => $obj_attr['object_relation'],
'event_id' => $obj_attr['event_id'],
'group' => 'object_attribute',
'timestamp' => $obj_attr['timestamp'],
);
$toPush_obj['Attribute'][] = $toPush_attr;
}
$this->__json['items'][] = $toPush_obj;
}
return $this->__json;
}
}

View File

@ -21,19 +21,19 @@ class AdminSetting extends AppModel
$setting_object = $this->find('first', array(
'conditions' => array('setting' => $setting)
));
if (!empty($setting_object)) {
$setting_object['AdminSetting']['value'] = $value;
} else {
$this->create();
$setting_object['AdminSetting'] = array('setting' => $setting, 'value' => $value);
}
$this->deleteAll(array('setting' => $setting));
$this->create();
$setting_object['AdminSetting'] = array('setting' => $setting, 'value' => $value);
if ($this->save($setting_object)) {
return true;
} else {
$this->create();
$this->save($setting_object);
return $this->validationErrors;
}
}
public function getSetting($setting)
{
$setting_object = $this->find('first', array(

View File

@ -77,10 +77,21 @@ class AppModel extends Model
27 => false, 28 => false, 29 => false, 30 => false, 31 => false, 32 => false,
33 => false, 34 => false, 35 => false, 36 => false, 37 => false, 38 => false,
39 => false, 40 => false, 41 => false, 42 => false, 43 => false, 44 => false,
45 => false
45 => false, 46 => false
);
public $advanced_updates_description = array(
'seenOnAttributeAndObject' => array(
'title' => 'First seen/Last seen Attribute table',
'description' => 'Update the Attribute table to support first_seen and last_seen feature, with a microsecond resolution.',
'liveOff' => true, # should the instance be offline for users other than site_admin
'recommendBackup' => true, # should the update recommend backup
'exitOnError' => false, # should the update exit on error
'requirements' => 'MySQL version must be >= 5.6', # message stating the requirements necessary for the update
'record' => false, # should the update success be saved in the admin_table
// 'preUpdate' => 'seenOnAttributeAndObjectPreUpdate', # Function to execute before the update. If it throws an error, it cancels the update
'url' => '/servers/updateDatabase/seenOnAttributeAndObject/' # url pointing to the funcion performing the update
),
);
public $actions_description = array(
'verifyGnuPGkeys' => array(
@ -135,6 +146,9 @@ class AppModel extends Model
// this could become useful in the future
public function updateMISP($command)
{
if (!$this->_isSiteAdmin() && (!Configure::read('MISP.live') || $this->_isRest())) {
return false;
}
$dbUpdateSuccess = false;
switch ($command) {
case '2.4.20':
@ -223,6 +237,9 @@ class AppModel extends Model
$dbUpdateSuccess = $this->updateDatabase($command);
$this->__addServerPriority();
break;
case 46:
$dbUpdateSuccess = $this->updateDatabase('seenOnAttributeAndObject');
break;
default:
$dbUpdateSuccess = $this->updateDatabase($command);
break;
@ -1323,6 +1340,58 @@ class AppModel extends Model
$sqlArray[] = 'ALTER TABLE `threads` DROP `org`;';
$sqlArray[] = 'ALTER TABLE `users` DROP `org`;';
break;
case 'seenOnAttributeAndObject':
$sqlArray[] =
"ALTER TABLE `attributes`
DROP INDEX uuid,
DROP INDEX event_id,
DROP INDEX sharing_group_id,
DROP INDEX type,
DROP INDEX category,
DROP INDEX value1,
DROP INDEX value2,
DROP INDEX object_id,
DROP INDEX object_relation;
";
$sqlArray[] = "ALTER TABLE `attributes` DROP INDEX deleted"; // deleted index may not be present
$sqlArray[] = "ALTER TABLE `attributes` DROP INDEX comment"; // for replayability
$sqlArray[] = "ALTER TABLE `attributes` DROP INDEX first_seen"; // for replayability
$sqlArray[] = "ALTER TABLE `attributes` DROP INDEX last_seen"; // for replayability
$sqlArray[] =
"ALTER TABLE `attributes`
ADD COLUMN `first_seen` BIGINT(20) NULL DEFAULT NULL,
ADD COLUMN `last_seen` BIGINT(20) NULL DEFAULT NULL,
MODIFY comment TEXT COLLATE utf8_unicode_ci
;";
$indexArray[] = array('attributes', 'uuid');
$indexArray[] = array('attributes', 'event_id');
$indexArray[] = array('attributes', 'sharing_group_id');
$indexArray[] = array('attributes', 'type');
$indexArray[] = array('attributes', 'category');
$indexArray[] = array('attributes', 'value1', 255);
$indexArray[] = array('attributes', 'value2', 255);
$indexArray[] = array('attributes', 'object_id');
$indexArray[] = array('attributes', 'object_relation');
$indexArray[] = array('attributes', 'deleted');
$indexArray[] = array('attributes', 'first_seen');
$indexArray[] = array('attributes', 'last_seen');
$sqlArray[] = "
ALTER TABLE `objects`
ADD `first_seen` BIGINT(20) NULL DEFAULT NULL,
ADD `last_seen` BIGINT(20) NULL DEFAULT NULL,
MODIFY comment TEXT COLLATE utf8_unicode_ci
;";
$indexArray[] = array('objects', 'first_seen');
$indexArray[] = array('objects', 'last_seen');
$sqlArray[] = "
ALTER TABLE `shadow_attributes`
ADD `first_seen` BIGINT(20) NULL DEFAULT NULL,
ADD `last_seen` BIGINT(20) NULL DEFAULT NULL,
MODIFY comment TEXT COLLATE utf8_unicode_ci
;";
$indexArray[] = array('shadow_attributes', 'first_seen');
$indexArray[] = array('shadow_attributes', 'last_seen');
break;
default:
return false;
break;
@ -1341,7 +1410,7 @@ class AppModel extends Model
$this->__setUpdateProgress(0, $total_update_count, $command);
$str_index_array = array();
foreach($indexArray as $toIndex) {
$str_index_array[] = __('Indexing ') . implode($toIndex, '->');
$str_index_array[] = __('Indexing ') . sprintf('%s -> %s', $toIndex[0], $toIndex[1]);
}
$this->__setUpdateCmdMessages(array_merge($sqlArray, $str_index_array));
$flagStop = false;
@ -1417,11 +1486,21 @@ class AppModel extends Model
foreach ($indexArray as $i => $iA) {
$this->__setUpdateProgress(count($sqlArray)+$i, false);
if (isset($iA[2])) {
$this->__addIndex($iA[0], $iA[1], $iA[2]);
$indexSuccess = $this->__addIndex($iA[0], $iA[1], $iA[2]);
} else {
$this->__addIndex($iA[0], $iA[1]);
$indexSuccess = $this->__addIndex($iA[0], $iA[1]);
}
if ($indexSuccess['success']) {
$this->__setUpdateResMessages(count($sqlArray)+$i, __('Successfuly indexed ') . sprintf('%s -> %s', $iA[0], $iA[1]));
} else {
$this->__setUpdateResMessages(count($sqlArray)+$i, sprintf('%s %s %s %s',
__('Failed to add index'),
sprintf('%s -> %s', $iA[0], $iA[1]),
__('The returned error is:') . PHP_EOL,
$indexSuccess['errorMessage']
));
$this->__setUpdateError(count($sqlArray)+$i);
}
$this->__setUpdateResMessages(count($sqlArray)+$i, __('Successfuly indexed ') . implode($iA, '->'));
}
}
$this->__setUpdateProgress(count($sqlArray)+count($indexArray), false);
@ -1517,10 +1596,12 @@ class AppModel extends Model
}
$result = true;
$duplicate = false;
$errorMessage = '';
try {
$this->query($addIndex);
} catch (Exception $e) {
$duplicate = (strpos($e->getMessage(), '1061') !== false);
$errorMessage = $e->getMessage();
$result = false;
}
$this->Log->create();
@ -1531,9 +1612,14 @@ class AppModel extends Model
'email' => 'SYSTEM',
'action' => 'update_database',
'user_id' => 0,
'title' => ($result ? 'Added index ' : 'Failed to add index ') . $field . ' to ' . $table . ($duplicate ? ' (index already set)' : ''),
'change' => ($result ? 'Added index ' : 'Failed to add index ') . $field . ' to ' . $table . ($duplicate ? ' (index already set)' : ''),
'title' => ($result ? 'Added index ' : 'Failed to add index ') . $field . ' to ' . $table . ($duplicate ? ' (index already set)' : $errorMessage),
'change' => ($result ? 'Added index ' : 'Failed to add index ') . $field . ' to ' . $table . ($duplicate ? ' (index already set)' : $errorMessage),
));
$additionResult = array('success' => $result || $duplicate);
if (!$result) {
$additionResult['errorMessage'] = $errorMessage;
}
return $additionResult;
}
public function cleanCacheFiles()
@ -1624,6 +1710,29 @@ class AppModel extends Model
return true;
}
// Try to create a table with a BIGINT(20)
public function seenOnAttributeAndObjectPreUpdate() {
$sqlArray[] = "CREATE TABLE IF NOT EXISTS testtable (
`testfield` BIGINT(6) NULL DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;";
try {
foreach($sqlArray as $i => $sql) {
$this->query($sql);
}
} catch (Exception $e) {
throw new Exception('Pre update test failed: ' . PHP_EOL . $sql . PHP_EOL . ' The returned error is: ' . $e->getMessage());
}
// clean up
$sqlArray[] = "DROP TABLE testtable;";
foreach($sqlArray as $i => $sql) {
$this->query($sql);
}
}
public function failingPreUpdate() {
throw new Exception('Yolo fail');
}
public function runUpdates($verbose = false, $useWorker = true, $processId = false)
{
$this->AdminSetting = ClassRegistry::init('AdminSetting');
@ -1653,7 +1762,6 @@ class AppModel extends Model
$job = $this->Job->find('first', array(
'conditions' => array('Job.id' => $processId)
));
if (!empty($updates)) {
// Exit if updates are locked.
// This is not as reliable as a real lock implementation

View File

@ -35,7 +35,7 @@ class Attribute extends AppModel
);
public $defaultFields = array(
'id', 'event_id', 'object_id', 'object_relation', 'category', 'type', 'value', 'to_ids', 'uuid', 'timestamp', 'distribution', 'sharing_group_id', 'comment', 'deleted', 'disable_correlation'
'id', 'event_id', 'object_id', 'object_relation', 'category', 'type', 'value', 'to_ids', 'uuid', 'timestamp', 'distribution', 'sharing_group_id', 'comment', 'deleted', 'disable_correlation', 'first_seen', 'last_seen'
);
public $distributionDescriptions = array(
@ -375,7 +375,9 @@ class Attribute extends AppModel
'deleted',
'disable_correlation',
'object_id',
'object_relation'
'object_relation',
'first_seen',
'last_seen'
);
public $searchResponseTypes = array(
@ -527,6 +529,16 @@ class Attribute extends AppModel
'rule' => array('inList', array('0', '1', '2', '3', '4', '5')),
'message' => 'Options: Your organisation only, This community only, Connected communities, All communities, Sharing group, Inherit event',
'required' => true
),
'first_seen' => array(
'rule' => array('datetimeOrNull'),
'required' => false,
'message' => array('Invalid ISO 8601 format')
),
'last_seen' => array(
'rule' => array('datetimeOrNull'),
'required' => false,
'message' => array('Invalid ISO 8601 format')
)
);
@ -605,6 +617,7 @@ class Attribute extends AppModel
if (isset($v['Attribute']['object_relation']) && $v['Attribute']['object_relation'] === null) {
$results[$k]['Attribute']['object_relation'] = '';
}
$results[$k] = $this->UTCToISODatetime($results[$k], $this->alias);
}
return $results;
}
@ -636,6 +649,13 @@ class Attribute extends AppModel
$this->data['Attribute']['value2'] = '';
}
}
$this->data = $this->ISODatetimeToUTC($this->data, $this->alias);
// update correlation... (only needed here if there's an update)
if ($this->id || !empty($this->data['Attribute']['id'])) {
$this->__beforeSaveCorrelation($this->data['Attribute']);
}
// always return true after a beforeSave()
return true;
}
@ -854,6 +874,16 @@ class Attribute extends AppModel
$date = new DateTime();
$this->data['Attribute']['timestamp'] = $date->getTimestamp();
}
// parse first_seen different formats
if (isset($this->data['Attribute']['first_seen'])) {
$this->data['Attribute']['first_seen'] = $this->data['Attribute']['first_seen'] === '' ? null : $this->data['Attribute']['first_seen'];
}
// parse last_seen different formats
if (isset($this->data['Attribute']['last_seen'])) {
$this->data['Attribute']['last_seen'] = $this->data['Attribute']['last_seen'] === '' ? null : $this->data['Attribute']['last_seen'];
}
// TODO: add explanatory comment
// TODO: i18n?
$result = $this->runRegexp($this->data['Attribute']['type'], $this->data['Attribute']['value']);
@ -986,6 +1016,20 @@ class Attribute extends AppModel
return $this->runValidation($value, $this->data['Attribute']['type']);
}
// check whether the variable is null or datetime
public function datetimeOrNull($fields)
{
$k = array_keys($fields)[0];
$seen = $fields[$k];
try {
new DateTime($seen);
$returnValue = true;
} catch (Exception $e) {
$returnValue = false;
}
return $returnValue || is_null($seen);
}
private $__hexHashLengths = array(
'authentihash' => 64,
'md5' => 32,
@ -2139,6 +2183,50 @@ class Attribute extends AppModel
return $fails;
}
public function ISODatetimeToUTC($data, $alias)
{
// convert into utc and micro sec
if (!empty($data[$alias]['first_seen'])) {
$d = new DateTime($data[$alias]['first_seen']);
$d->setTimezone(new DateTimeZone('GMT'));
$fs_sec = $d->format('U');
$fs_micro = $d->format('u');
$fs_micro = str_pad($fs_micro, 6, "0", STR_PAD_LEFT);
$fs = $fs_sec . $fs_micro;
$data[$alias]['first_seen'] = $fs;
}
if (!empty($data[$alias]['last_seen'])) {
$d = new DateTime($data[$alias]['last_seen']);
$d->setTimezone(new DateTimeZone('GMT'));
$ls_sec = $d->format('U');
$ls_micro = $d->format('u');
$ls_micro = str_pad($ls_micro, 6, "0", STR_PAD_LEFT);
$ls = $ls_sec . $ls_micro;
$data[$alias]['last_seen'] = $ls;
}
return $data;
}
public function UTCToISODatetime($data, $alias)
{
if (!empty($data[$alias]['first_seen'])) {
$fs = $data[$alias]['first_seen'];
$fs_sec = intval($fs / 1000000); // $fs is in micro (10^6)
$fs_micro = $fs % 1000000;
$fs_micro = str_pad($fs_micro, 6, "0", STR_PAD_LEFT);
$fs = $fs_sec . '.' . $fs_micro;
$data[$alias]['first_seen'] = DateTime::createFromFormat('U.u', $fs)->format('Y-m-d\TH:i:s.uP');
}
if (!empty($data[$alias]['last_seen'])) {
$ls = $data[$alias]['last_seen'];
$ls_sec = intval($ls / 1000000); // $ls is in micro (10^6)
$ls_micro = $ls % 1000000;
$ls_micro = str_pad($ls_micro, 6, "0", STR_PAD_LEFT);
$ls = $ls_sec . '.' . $ls_micro;
$data[$alias]['last_seen'] = DateTime::createFromFormat('U.u', $ls)->format('Y-m-d\TH:i:s.uP');
}
return $data;
}
public function hids($user, $type, $tags = '', $from = false, $to = false, $last = false, $jobId = false, $enforceWarninglist = false)
{
@ -3528,6 +3616,7 @@ class Attribute extends AppModel
$defaultDistribution = Configure::read('MISP.default_attribute_distribution');
}
}
$saveResult = true;
foreach ($attributes as $k => $attribute) {
if (!empty($attribute['encrypt']) && $attribute['encrypt']) {
$attribute = $this->onDemandEncrypt($attribute);
@ -3537,9 +3626,9 @@ class Attribute extends AppModel
}
unset($attribute['Attachment']);
$this->create();
$this->save($attribute);
$saveResult = $saveResult && $this->save($attribute);
}
return true;
return $saveResult;
}
public function onDemandEncrypt($attribute)
@ -3657,6 +3746,32 @@ class Attribute extends AppModel
return $conditions;
}
public function setTimestampSeenConditions($timestamp, $conditions, $scope = 'Attribute.first_seen', $returnRaw = false)
{
if (is_array($timestamp)) {
$timestamp[0] = intval($this->Event->resolveTimeDelta($timestamp[0])) * 1000000; // seen in stored in micro-seconds in the DB
$timestamp[1] = intval($this->Event->resolveTimeDelta($timestamp[1])) * 1000000; // seen in stored in micro-seconds in the DB
if ($timestamp[0] > $timestamp[1]) {
$temp = $timestamp[0];
$timestamp[0] = $timestamp[1];
$timestamp[1] = $temp;
}
$conditions['AND'][] = array($scope . ' >=' => $timestamp[0]);
$conditions['AND'][] = array($scope . ' <=' => $timestamp[1]);
} else {
$timestamp = intval($this->Event->resolveTimeDelta($timestamp)) * 1000000; // seen in stored in micro-seconds in the DB
if ($scope == 'Attribute.first_seen') {
$conditions['AND'][] = array($scope . ' >=' => $timestamp);
} else {
$conditions['AND'][] = array($scope . ' <=' => $timestamp);
}
}
if ($returnRaw) {
return $timestamp;
}
return $conditions;
}
public function setToIDSConditions($to_ids, $conditions)
{
if ($to_ids === 'exclude') {
@ -3985,7 +4100,7 @@ class Attribute extends AppModel
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');
$recoverFields = array('value', 'to_ids', 'distribution', 'category', 'type', 'comment', 'sharing_group_id', 'object_id', 'object_relation', 'first_seen', 'last_seen');
foreach ($recoverFields as $rF) {
if (!isset($attribute[$rF])) {
$attribute[$rF] = $existingAttribute['Attribute'][$rF];
@ -4055,7 +4170,9 @@ class Attribute extends AppModel
'comment',
'sharing_group_id',
'deleted',
'disable_correlation'
'disable_correlation',
'first_seen',
'last_seen'
);
if ($objectId) {
$fieldList[] = 'object_id';
@ -4225,6 +4342,8 @@ class Attribute extends AppModel
'deleted' => array('function' => 'set_filter_deleted'),
'timestamp' => array('function' => 'set_filter_timestamp'),
'attribute_timestamp' => array('function' => 'set_filter_timestamp'),
'first_seen' => array('function' => 'set_filter_seen'),
'last_seen' => array('function' => 'set_filter_seen'),
'to_ids' => array('function' => 'set_filter_to_ids'),
'comment' => array('function' => 'set_filter_comment')
),

View File

@ -2064,9 +2064,9 @@ 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', 'Event.extends_uuid');
$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');
$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', 'Attribute.first_seen', 'Attribute.last_seen');
$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');
$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', 'ShadowAttribute.first_seen', 'ShadowAttribute.last_seen');
$fieldsOrg = array('id', 'name', 'uuid', 'local');
$fieldsServer = array('id', 'url', 'name');
if (!$options['includeAllTags']) {
@ -2702,6 +2702,13 @@ class Event extends AppModel
return $conditions;
}
public function set_filter_seen(&$params, $conditions, $options)
{
$f = $options['scope'] . '.' . $options['filter'];
$conditions = $this->Attribute->setTimestampSeenConditions($params[$options['filter']], $conditions, $f);
return $conditions;
}
public function set_filter_timestamp(&$params, $conditions, $options)
{
if ($options['filter'] == 'from') {

View File

@ -46,6 +46,8 @@ class MispObject extends AppModel
),
);
public $shortDist = array(0 => 'Organisation', 1 => 'Community', 2 => 'Connected', 3 => 'All', 4 => ' Sharing Group', 5 => 'Inherit');
public $validate = array(
'uuid' => array(
'uuid' => array(
@ -60,6 +62,18 @@ class MispObject extends AppModel
)
);
public function afterFind($results, $primary = false)
{
foreach ($results as $k => $v) {
$results[$k] = $this->Attribute->UTCToISODatetime($results[$k], $this->alias);
}
return $results;
}
public function beforeSave($options = array()) {
$this->data = $this->Attribute->ISODatetimeToUTC($this->data, $this->alias);
}
public function beforeValidate($options = array())
{
parent::beforeValidate();
@ -75,6 +89,14 @@ class MispObject extends AppModel
$date = new DateTime();
$this->data[$this->alias]['timestamp'] = $date->getTimestamp();
}
// parse first_seen different formats
if (isset($this->data[$this->alias]['first_seen'])) {
$this->data[$this->alias]['first_seen'] = $this->data[$this->alias]['first_seen'] === '' ? null : $this->data[$this->alias]['first_seen'];
}
// parse last_seen different formats
if (isset($this->data[$this->alias]['last_seen'])) {
$this->data[$this->alias]['last_seen'] = $this->data[$this->alias]['last_seen'] === '' ? null : $this->data[$this->alias]['last_seen'];
}
if (empty($this->data[$this->alias]['template_version'])) {
$this->data[$this->alias]['template_version'] = 1;
}
@ -184,6 +206,18 @@ class MispObject extends AppModel
$result = $this->id;
foreach ($object['Attribute'] as $k => $attribute) {
$object['Attribute'][$k]['object_id'] = $this->id;
if (
(!array_key_exists('first_seen', $object['Attribute'][$k]) || is_null($object['Attribute'][$k]['first_seen'])) &&
(array_key_exists('first_seen', $object['Object']) && !is_null($object['Object']['first_seen']))
) {
$object['Attribute'][$k]['first_seen'] = $object['Object']['first_seen'];
}
if (
(!array_key_exists('last_seen', $object['Attribute'][$k]) || is_null($object['Attribute'][$k]['last_seen'])) &&
(array_key_exists('last_seen', $object['Object']) && !is_null($object['Object']['last_seen']))
) {
$object['Attribute'][$k]['last_seen'] = $object['Object']['last_seen'];
}
}
$this->Attribute->saveAttributes($object['Attribute']);
} else {
@ -254,6 +288,28 @@ class MispObject extends AppModel
return $conditions;
}
public function fetchObjectSimple($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'],
'contain' => array('Event' => array('distribution', 'id', 'user_id', 'orgc_id', 'org_id')),
'sort' => false
));
return $results;
}
// Method that fetches all objects
// very flexible, it's basically a replacement for find, with the addition that it restricts access based on user
@ -489,12 +545,57 @@ class MispObject extends AppModel
$tmpfile->delete();
$tmpfile->close();
}
if (!isset($attributes['Attribute'][$k]['first_seen'])) {
$attributes['Attribute'][$k]['first_seen'] = null;
}
if (!isset($attributes['Attribute'][$k]['last_seen'])) {
$attributes['Attribute'][$k]['last_seen'] = null;
}
unset($attributes['Attribute'][$k]['save']);
}
return $attributes;
}
public function deltaMerge($object, $objectToSave)
// Set Object's *-seen (and ObjectAttribute's *-seen and ObjectAttribute's value if requested) to the provided *-seen value
// Therefore, synchronizing the 3 values
public function syncObjectAndAttributeSeen($object, $forcedSeenOnElements, $applyOnAttribute=True) {
if (empty($forcedSeenOnElements)) {
return $object;
}
if (isset($forcedSeenOnElements['first_seen'])) {
$object['Object']['first_seen'] = $forcedSeenOnElements['first_seen'];
}
if (isset($forcedSeenOnElements['last_seen'])) {
$object['Object']['last_seen'] = $forcedSeenOnElements['last_seen'];
}
if ($applyOnAttribute) {
if (isset($object['Attribute'])) {
$attributes = $object['Attribute'];
} else {
$attributes = $this->find('first', array(
'conditions' => array('id' => $object['Object']['id']),
'contain' => array('Attribute')
))['Attribute'];
}
foreach($attributes as $i => $attribute) {
if (isset($forcedSeenOnElements['first_seen'])) {
$attributes[$i]['first_seen'] = $forcedSeenOnElements['first_seen'];
if ($attribute['object_relation'] == 'first-seen') {
$attributes[$i]['value'] = $forcedSeenOnElements['first_seen'];
}
} elseif (isset($forcedSeenOnElements['last_seen'])) {
$attributes[$i]['last_seen'] = $forcedSeenOnElements['last_seen'];
if ($attribute['object_relation'] == 'last-seen') {
$attributes[$i]['value'] = $forcedSeenOnElements['last_seen'];
}
}
}
$object['Attribute'] = $attributes;
}
return $object;
}
public function deltaMerge($object, $objectToSave, $onlyAddNewAttribute=false)
{
if (!isset($objectToSave['Object'])) {
$dataToBackup = array('ObjectReferences', 'Attribute', 'ShadowAttribute');
@ -513,69 +614,141 @@ class MispObject extends AppModel
}
unset($dataToBackup);
}
$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'];
if (isset($objectToSave['Object']['comment'])) {
$object['Object']['comment'] = $objectToSave['Object']['comment'];
}
if (isset($objectToSave['Object']['distribution'])) {
$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();
$forcedSeenOnElements = array();
if (isset($objectToSave['Object']['first_seen'])) {
$forcedSeenOnElements['first_seen'] = $objectToSave['Object']['first_seen'];
}
if (isset($objectToSave['Object']['last_seen'])) {
$forcedSeenOnElements['last_seen'] = $objectToSave['Object']['last_seen'];
}
$object = $this->syncObjectAndAttributeSeen($object, $forcedSeenOnElements, false);
$this->save($object);
$checkFields = array('category', 'value', 'to_ids', 'distribution', 'sharing_group_id', 'comment', 'disable_correlation');
if (!empty($objectToSave['Attribute'])) {
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 (!$onlyAddNewAttribute) {
$checkFields = array('category', 'value', 'to_ids', 'distribution', 'sharing_group_id', 'comment', 'disable_correlation', 'first_seen', 'last_seen');
if (!empty($objectToSave['Attribute'])) {
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;
}
// Set seen of object at attribute level
if (isset($forcedSeenOnElements['first_seen'])) {
$newAttribute['first_seen'] = $forcedSeenOnElements['first_seen'];
if ($newAttribute['object_relation'] == 'first-seen') {
// $newAttribute['value'] = $forcedSeenOnElements['first_seen'];
}
$different = true;
}
if (isset($forcedSeenOnElements['last_seen'])) {
$newAttribute['last_seen'] = $forcedSeenOnElements['last_seen'];
if ($newAttribute['object_relation'] == 'last-seen') {
// $newAttribute['value'] = $forcedSeenOnElements['last_seen'];
}
$different = true;
}
}
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',
'disable_correlation',
'first_seen',
'last_seen'
));
}
unset($object['Attribute'][$origKey]);
continue 2;
}
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',
'disable_correlation'
));
}
unset($object['Attribute'][$origKey]);
continue 2;
}
}
}
$this->Event->Attribute->create();
$newAttribute['event_id'] = $object['Object']['event_id'];
$newAttribute['object_id'] = $object['Object']['id'];
if (!isset($newAttribute['timestamp'])) {
$newAttribute['distribution'] = Configure::read('MISP.default_attribute_distribution');
if ($newAttribute['distribution'] == 'event') {
$newAttribute['distribution'] = 5;
$this->Event->Attribute->create();
$newAttribute['event_id'] = $object['Object']['event_id'];
$newAttribute['object_id'] = $object['Object']['id'];
// Set seen of object at attribute level
if (isset($forcedSeenOnElements['first_seen'])) {
$newAttribute['first_seen'] = $forcedSeenOnElements['first_seen'];
if ($newAttribute['object_relation'] == 'first-seen') {
$newAttribute['value'] = $forcedSeenOnElements['first_seen'];
}
}
if (isset($forcedSeenOnElements['last_seen'])) {
$newAttribute['last_seen'] = $forcedSeenOnElements['last_seen'];
if ($newAttribute['object_relation'] == 'last-seen') {
$newAttribute['value'] = $forcedSeenOnElements['last_seen'];
}
}
if (!isset($newAttribute['timestamp'])) {
$newAttribute['distribution'] = Configure::read('MISP.default_attribute_distribution');
if ($newAttribute['distribution'] == 'event') {
$newAttribute['distribution'] = 5;
}
}
$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);
}
$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);
} else { // we only add the new attribute
$newAttribute = $objectToSave['Attribute'][0];
$this->Event->Attribute->create();
$newAttribute['event_id'] = $object['Object']['event_id'];
$newAttribute['object_id'] = $object['Object']['id'];
// Set seen of object at attribute level
if (
(!array_key_exists('first_seen', $newAttribute) || is_null($newAttribute['first_seen'])) &&
(!array_key_exists('first_seen', $object['Object']) && !is_null($object['Object']['first_seen']))
) {
$newAttribute['first_seen'] = $object['Object']['first_seen'];
}
if (
(!array_key_exists('last_seen', $newAttribute) || is_null($newAttribute['last_seen'])) &&
(!array_key_exists('last_seen', $object['Object']) && !is_null($object['Object']['last_seen']))
) {
$newAttribute['last_seen'] = $object['Object']['last_seen'];
$different = true;
}
if (!isset($newAttribute['timestamp'])) {
$newAttribute['distribution'] = Configure::read('MISP.default_attribute_distribution');
if ($newAttribute['distribution'] == 'event') {
$newAttribute['distribution'] = 5;
}
}
$saveAttributeResult = $this->Attribute->saveAttributes(array($newAttribute));
return $saveAttributeResult ? $this->id : $this->validationErrors;
}
return $this->id;
}

View File

@ -133,6 +133,16 @@ class ShadowAttribute extends AppModel
'rule' => array('boolean'),
),
),
'first_seen' => array(
'rule' => array('datetimeOrNull'),
'required' => false,
'message' => array('Invalid ISO 8601 format')
),
'last_seen' => array(
'rule' => array('datetimeOrNull'),
'required' => false,
'message' => array('Invalid ISO 8601 format')
)
);
public function __construct($id = false, $table = null, $ds = null)
@ -177,6 +187,9 @@ class ShadowAttribute extends AppModel
if ($this->data['ShadowAttribute']['deleted']) {
$this->__beforeDeleteCorrelation($this->data['ShadowAttribute']);
}
// convert into utc and micro sec
$this->data = $this->Attribute->ISODatetimeToUTC($this->data, $this->alias);
return true;
}
@ -358,6 +371,14 @@ class ShadowAttribute extends AppModel
return true;
}
public function afterFind($results, $primary = false)
{
foreach ($results as $k => $v) {
$results[$k] = $this->Attribute->UTCToISODatetime($results[$k], $this->alias);
}
return $results;
}
public function validateTypeValue($fields)
{
$category = $this->data['ShadowAttribute']['category'];
@ -474,6 +495,20 @@ class ShadowAttribute extends AppModel
return $fails;
}
// check whether the variable is null or datetime
public function datetimeOrNull($fields)
{
$k = array_keys($fields)[0];
$seen = $fields[$k];
try {
new DateTime($seen);
$returnValue = true;
} catch (Exception $e) {
$returnValue = false;
}
return $returnValue || is_null($seen);
}
public function setDeleted($id)
{
$this->Behaviors->detach('SysLogLogable.SysLogLogable');

View File

@ -65,6 +65,16 @@
'field' => 'disable_correlation',
'type' => 'checkbox'
),
array(
'field' => 'first_seen',
'type' => 'text',
'hidden' => true
),
array(
'field' => 'last_seen',
'type' => 'text',
'hidden' => true
),
'<div id="extended_event_preview" style="width:446px;"></div>'
),
'submit' => array(
@ -74,6 +84,9 @@
"'" . ($action == 'add' ? h($event_id) : h($attribute['Attribute']['id'])) . "'",
"'" . h($action) . "'"
)
),
'metaFields' => array(
'<div id="bothSeenSliderContainer" style="height: 170px;"></div>'
)
)
));
@ -130,4 +143,5 @@
checkSharingGroup('Attribute');
});
</script>
<?php echo $this->element('form_seen_input'); ?>
<?php echo $this->Js->writeBuffer(); // Write cached scripts

View File

@ -0,0 +1,17 @@
<?php
echo $this->Form->create('Attribute', array(
'id' => 'Attribute' . '_' . $object['id'] . '_first_seen_form',
'url' => '/attributes/editField/' . $object['id']
));
?>
<?php
echo $this->Form->input('first_seen', array(
'label' => false,
'type' => 'text',
'value' => 0,
'id' => 'Attribute' . '_' . $object['id'] . '_first_seen_field',
'div' => false
));
echo $this->Form->end();
?>
</div>

View File

@ -0,0 +1,15 @@
<?php
echo $this->Form->create('Attribute', array(
'id' => 'Attribute' . '_' . $object['id'] . '_last_seen_form',
'url' => '/attributes/editField/' . $object['id']
));
echo $this->Form->input('last_seen', array(
'label' => false,
'type' => 'text',
'value' => 0,
'id' => 'Attribute' . '_' . $object['id'] . '_last_seen_field',
'div' => false
));
echo $this->Form->end();
?>
</div>

View File

@ -32,13 +32,27 @@
'type' => 'checkbox',
'label' => __('Alternate Search Result (Events)')
));
echo $this->Form->input('first_seen', array(
'type' => 'text',
'div' => 'input hidden',
'required' => false,
));
echo $this->Form->input('last_seen', array(
'type' => 'text',
'div' => 'input hidden',
'required' => false,
));
?>
<div class="clear">
<h3><?php echo __('First seen and Last seen.'); ?></h3>
<p><?php echo __('Attributes not having first seen or last seen set might not appear in the search'); ?></p>
</div>
</fieldset>
<?php
echo $this->Form->button(__('Search'), array('class' => 'btn btn-primary'));
echo $this->Form->end();
?>
<?php echo $this->Form->end(); ?>
<div id="bothSeenSliderContainer"></div>
<button onclick="$('#AttributeSearchForm').submit();" class="btn btn-primary">Submit</button>
</div>
<?php echo $this->element('form_seen_input'); ?>
<script type="text/javascript">
//
// Generate Category / Type filtering array

View File

@ -54,6 +54,9 @@
<td class="short context hidden">
<?php echo h($object['uuid']); ?>
</td>
<td class="short context hidden">
<?php echo $this->element('/Events/View/seen_field', array('object' => $object)); ?>
</td>
<td class="short">
<?php echo date('Y-m-d', $object['timestamp']); ?>
</td>

View File

@ -1,7 +1,7 @@
<?php
$tr_class = '';
$linkClass = 'white';
$currentType = 'denyForm';
$currentType = 'Object';
$tr_class = 'tableHighlightBorderTop borderBlue';
if ($event['Event']['id'] != $object['event_id']) {
if (!$isSiteAdmin && $event['extensionEvents'][$object['event_id']]['Orgc']['id'] != $me['org_id']) {
@ -36,6 +36,9 @@
<td class="short context hidden">
<?php echo h($object['uuid']); ?>
</td>
<td class="short context hidden">
<?php echo $this->element('/Events/View/seen_field', array('object' => $object)); ?>
</td>
<td class="short">
<?php echo date('Y-m-d', $object['timestamp']); ?>
</td>
@ -85,18 +88,21 @@
}
?>
</td>
<td class="shortish">
<?php echo h($object['comment']); ?>
<td class="showspaces bitwider" onmouseenter="quickEditHover(this, 'Object', '<?php echo $object['id']; ?>', 'comment', <?php echo $event['Event']['id'];?>);">
<div id = "Object_<?php echo $object['id']; ?>_comment_placeholder" class = "inline-field-placeholder"></div>
<div id = "Object_<?php echo $object['id']; ?>_comment_solid" class="inline-field-solid">
<?php echo nl2br(h($object['comment'])); ?>&nbsp;
</div>
</td>
<td colspan="4">&nbsp;
</td>
<td class="shortish">
<td class="shortish" onmouseenter="quickEditHover(this, 'Object', '<?php echo $object['id']; ?>', 'distribution', <?php echo $event['Event']['id'];?>);">
<?php
$turnRed = '';
if ($object['objectType'] == 0 && $object['distribution'] == 0) $turnRed = 'style="color:red"';
?>
<div id = "<?php echo $currentType . '_' . $object['id'] . '_distribution_placeholder'; ?>" class = "inline-field-placeholder"></div>
<div id = "<?php echo $currentType . '_' . $object['id'] . '_distribution_solid'; ?>" <?php echo $turnRed; ?> class="inline-field-solid" ondblclick="activateField('<?php echo $currentType; ?>', '<?php echo $object['id']; ?>', 'distribution', <?php echo $event['Event']['id'];?>);">
<div id = "<?php echo $currentType . '_' . $object['id'] . '_distribution_solid'; ?>" <?php echo $turnRed; ?> class="inline-field-solid">
<?php
if ($object['objectType'] == 0) {
if ($object['distribution'] == 4):
@ -155,5 +161,6 @@
'child' => $attrKey == $lastElement ? 'last' : true
));
}
echo '<tr class="objectAddFieldTr"><td><span class="fa fa-plus-circle objectAddField" title="' . __('Add an Object Attribute') .'" onclick="popoverPopup(this, ' . h($object['id']) . ', \'objects\', \'quickFetchTemplateWithValidObjectAttributes\')"></span></td></tr>';
}
?>

View File

@ -49,6 +49,9 @@
<td class="short context hidden">
<?php echo $object['objectType'] == 0 ? h($object['uuid']) : '&nbsp;'; ?>
</td>
<td class="short context hidden">
<?php echo $this->element('/Events/View/seen_field', array('object' => $object)); ?>
</td>
<td class="short">
<div id = "<?php echo $currentType . '_' . $object['id'] . '_timestamp_solid'; ?>">
<?php

View File

@ -50,6 +50,9 @@
<td class="short context hidden">
<?php echo h($object['uuid']); ?>
</td>
<td class="short context hidden">
<?php echo $this->element('/Events/View/seen_field', array('object' => $object)); ?>
</td>
<td style="font-weight:bold;text-align:left;">DELETE</td>
<?php
if ($extended):

View File

@ -0,0 +1,9 @@
<?php if ($object['first_seen'] != null || $object['last_seen'] != null): ?>
<div>
<div><?php echo $object['first_seen'] != null ? h($object['first_seen']) : '<span style="display: block; text-align:center;">_</span>'; ?></div>
<i style="display: block; text-align: center;" class="fas fa-arrow-down"></i>
<div><?php echo $object['last_seen'] != null ? h($object['last_seen']) : '<span style="display: block; text-align:center;">_</span>'; ?></div>
</div>
<?php else: ?>
<div></div>
<?php endif; ?>

View File

@ -22,6 +22,9 @@
<td class="short">
<?php echo date('Y-m-d', $object['timestamp']); ?>
</td>
<td class="short">
<?php echo $this->element('/Servers/View/seen_field', array('object' => $object)); ?>
</td>
<td class="short">
<div id="Attribute_<?php echo $object['uuid']; ?>_category_solid" class="inline-field-solid">
<?php echo h($object['category']); ?>

View File

@ -12,6 +12,9 @@
<td class="short">
<?php echo date('Y-m-d', $object['timestamp']); ?>
</td>
<td class="short">
<?php echo $this->element('/Servers/View/seen_field', array('object' => $object)); ?>
</td>
<td colspan="<?php echo $fieldCount -2;?>">
<span class="bold"><?php echo __('Name: ');?></span><?php echo h($object['name']);?>
<span class="fa fa-expand useCursorPointer" title="<?php echo __('Expand or Collapse');?>" role="button" tabindex="0" aria-label="<?php echo __('Expand or Collapse');?>" data-toggle="collapse" data-target="#Object_<?php echo h($object['uuid']); ?>_collapsible"></span>

View File

@ -0,0 +1,9 @@
<?php if (array_key_exists('first_seen', $object) && ($object['first_seen'] != null || $object['last_seen'] != null)): ?>
<div>
<div><?php echo $object['first_seen'] != null ? h($object['first_seen']) : '<span style="display: block; text-align:center;">_</span>'; ?></div>
<i style="display: block; text-align: center;" class="fas fa-arrow-down"></i>
<div><?php echo $object['last_seen'] != null ? h($object['last_seen']) : '<span style="display: block; text-align:center;">_</span>'; ?></div>
</div>
<?php else: ?>
<div></div>
<?php endif; ?>

View File

@ -39,6 +39,7 @@
<table class="table table-striped table-condensed">
<tr>
<th><?php echo $this->Paginator->sort('timestamp', __('Date'));?></th>
<th><?php echo __('First seen') ?> <i class="fas fa-arrow-right"></i> <?php echo __('Last seen') ?></th>
<th><?php echo $this->Paginator->sort('category');?></th>
<th><?php echo $this->Paginator->sort('type');?></th>
<th><?php echo $this->Paginator->sort('value');?></th>

View File

@ -22,6 +22,9 @@
<td class="short">
<?php echo date('Y-m-d', $object['timestamp']); ?>
</td>
<td class="short">
<?php echo $this->element('/Servers/View/seen_field', array('object' => $object)); ?>
</td>
<td class="short">
<div id = "Attribute_<?php echo $object['id']; ?>_category_placeholder" class = "inline-field-placeholder"></div>
<div id = "Attribute_<?php echo $object['id']; ?>_category_solid" class="inline-field-solid" ondblclick="activateField('Attribute', '<?php echo $object['id']; ?>', 'category', <?php echo $event['Event']['id'];?>);">

View File

@ -12,6 +12,9 @@
<td class="short">
<?php echo date('Y-m-d', $object['timestamp']); ?>
</td>
<td class="short">
<?php echo $this->element('/Servers/View/seen_field', array('object' => $object)); ?>
</td>
<td colspan="<?php echo $fieldCount -2;?>">
<span class="bold"><?php echo __('Name');?>: </span><?php echo h($object['name']);?>
<span class="fa fa-expand useCursorPointer" title="<?php echo __('Expand or Collapse');?>" role="button" tabindex="0" aria-label="<?php echo __('Expand or Collapse');?>" data-toggle="collapse" data-target="#Object_<?php echo h($object['id']); ?>_collapsible"></span>

View File

@ -0,0 +1,9 @@
<?php if (array_key_exists('first_seen', $object) && ($object['first_seen'] != null || $object['last_seen'] != null)): ?>
<div>
<div><?php echo $object['first_seen'] != null ? h($object['first_seen']) : '<span style="display: block; text-align:center;">_</span>'; ?></div>
<i style="display: block; text-align: center;" class="fas fa-arrow-down"></i>
<div><?php echo $object['last_seen'] != null ? h($object['last_seen']) : '<span style="display: block; text-align:center;">_</span>'; ?></div>
</div>
<?php else: ?>
<div></div>
<?php endif; ?>

View File

@ -39,6 +39,7 @@
<table class="table table-striped table-condensed">
<tr>
<th><?php echo $this->Paginator->sort('timestamp', __('Date'));?></th>
<th><?php echo __('First seen') ?> <i class="fas fa-arrow-right"></i> <?php echo __('Last seen') ?></th>
<th><?php echo $this->Paginator->sort('category');?></th>
<th><?php echo $this->Paginator->sort('type');?></th>
<th><?php echo $this->Paginator->sort('value');?></th>

View File

@ -135,6 +135,7 @@
?>
<th class="context hidden"><?php echo $this->Paginator->sort('id');?></th>
<th class="context hidden">UUID</th>
<th class="context hidden"><?php echo __('First seen') ?> <i class="fas fa-arrow-right"></i> <?php echo __('Last seen') ?></th>
<th><?php echo $this->Paginator->sort('timestamp', __('Date'), array('direction' => 'desc'));?></th>
<?php
if ($extended):

View File

@ -0,0 +1,78 @@
<?php echo $this->Html->script('moment-with-locales'); ?>
<script>
<?php
$temp = explode('_', $this->params->controller);
if (count($temp) > 1) {
$temp = array_map(function($i, $str) {
return $i > 0 ? substr(ucfirst($str), 0, -1) : ucfirst($str);
}, array_keys($temp), $temp);
$temp = implode('', $temp);
} else {
$temp = substr(ucfirst($this->params->controller), 0, -1);
}
?>
var controller = "<?php echo $temp; ?>"; // get current controller name so that we can access all form fields
function reflect_change_on_form() {
var first_seen = $('#date_fs').val() + 'T' + $("#time_fs").val();
var last_seen = $('#date_ls').val() + 'T' + $("#time_ls").val();
$('#'+controller+'FirstSeen').val(first_seen);
$('#'+controller+'LastSeen').val(last_seen);
}
function extractDatetimePart(text) {
try {
var split = text.split('T')
return {
date: split[0],
time: split[1]
}
} catch (error) {
return { date: '', time: ''}
}
}
$(document).ready(function() {
var sliders_container = "#bothSeenSliderContainer"
var inputs_container = $('<div class="input-group input-daterange"></div>');
// create separate date and time input
var date_div_fs = $('<div class="input clear larger-input-field" style="margin-left: 10px;"></div>').append(
$('<label><?php echo __('First seen date') . '<span class="fas fa-calendar label-icon"></span>'; ?><input id="date_fs" type="text" style="width: 240px;"></input></label>')
);
$(inputs_container).append(date_div_fs);
var date_div_ls = $('<div class="input text larger-input-field"></div>').append(
$('<label><?php echo __('Last seen date') . '<span class="fas fa-calendar label-icon"></span>'; ?><input id="date_ls" type="text" style="width: 240px;"></input></label>')
);
$(inputs_container).append(date_div_ls);
$(sliders_container).append(inputs_container);
var time_div_fs = $('<div class="input clear larger-input-field" style="margin-left: 10px;"></div>').append(
$('<label><?php echo __('First seen time') . '<span class="fas fa-clock label-icon"></span>'; ?><input id="time_fs" type="text" style="width: 240px; text-align: center; margin-bottom: 0px" placeholder="HH:MM:SS.ssssss+TT:TT"></input></label>'),
$('<span class="apply_css_arrow"></span>').text('<?php echo __('Expected format: HH:MM:SS.ssssss+TT:TT') ?>')
);
$(sliders_container).append(time_div_fs);
var time_div_ls = $('<div class="input larger-input-field"></div>').append(
$('<label><?php echo __('Last seen time') . '<span class="fas fa-clock label-icon"></span>'; ?><input id="time_ls" type="text" style="width: 240px; text-align: center; margin-bottom: 0px" placeholder="HH:MM:SS.ssssss+TT:TT"></input></label>'),
$('<span class="apply_css_arrow"></span>').text('<?php echo __('Expected format: HH:MM:SS.ssssss+TT:TT') ?>')
);
$(sliders_container).append(time_div_ls);
$('#'+controller+'FirstSeen').closest('form').submit(function( event ) {
reflect_change_on_form();
});
var d1 = extractDatetimePart($('#'+controller+'FirstSeen').val());
var d2 = extractDatetimePart($('#'+controller+'LastSeen').val());
$('#date_fs').val(d1.date);
$('#time_fs').val(d1.time);
$('#date_ls').val(d2.date);
$('#time_ls').val(d2.time);
$('.input-daterange').datepicker({
preventMultipleSet: true,
format: 'yyyy-mm-dd',
todayHighlight: true
})
});
</script>

View File

@ -66,7 +66,7 @@
</span><br />
<pre class="hidden green bold" id="gitResult"></pre>
<button title="<?php echo __('Pull the latest MISP version from github');?>" class="btn btn-inverse" style="padding-top:1px;padding-bottom:1px;" onClick = "updateMISP();"><?php echo __('Update MISP');?></button>
<a title="<?php echo __('Click the following button to go to the update progress page. This page lists all updates that are currently queued and executed.'); ?>" class="btn btn-inverse" style="padding-top:1px;padding-bottom:1px;" href="<?php echo $baseurl; ?>/servers/updateProgress/"><?php echo __('Update Progress');?></a>
<a title="<?php echo __('Click the following button to go to the update progress page. This page lists all updates that are currently queued and executed.'); ?>" style="margin-left: 5px;" href="<?php echo $baseurl; ?>/servers/updateProgress/"><i class="fas fa-tasks"></i> <?php echo __('View Update Progress');?></a>
</div>
<h3><?php echo __('Submodules version');?>
<it id="refreshSubmoduleStatus" class="fas fa-sync useCursorPointer" style="font-size: small; margin-left: 5px;" title="<?php echo __('Refresh submodules version.'); ?>"></it>
@ -159,7 +159,7 @@
<p><span class="bold"><?php echo __('PHP ini path');?></span>:… <span class="green"><?php echo h($php_ini); ?></span><br />
<span class="bold"><?php echo __('PHP Version');?> (><?php echo $phprec; ?> <?php echo __('recommended');?>): </span><span class="<?php echo $phpversions['web']['phpcolour']; ?>"><?php echo h($phpversions['web']['phpversion']) . ' (' . $phpversions['web']['phptext'] . ')';?></span><br />
<span class="bold"><?php echo __('PHP CLI Version');?> (><?php echo $phprec; ?> <?php echo __('recommended');?>): </span><span class="<?php echo $phpversions['cli']['phpcolour']; ?>"><?php echo h($phpversions['cli']['phpversion']) . ' (' . $phpversions['cli']['phptext'] . ')';?></span></p>
<p class="red bold"><?php echo __('Please note that the we will be dropping support for Python 2.7 and PHP 7.1 as of 2020-01-01 and are henceforth considered deprecated (but supported until the end of 2019). Both of these versions will by then reached End of Life and will become a liability. Furthermore, by dropping support for these outdated versions of the languages, we\'ll be able to phase out support for legacy code that exists solely to support them. Make sure that you plan ahead accordingly. More info: ');?><a href="https://secure.php.net/supported-versions.php">PHP</a>, <a href="https://www.python.org/dev/peps/pep-0373">Python</a>.</p>
<p class="red bold"><?php echo __('Please note that the support for Python versions below 3.6 and below PHP 7.2 has been dropped as of 2020-01-01 and are henceforth considered unsupported. More info: ');?><a href="https://secure.php.net/supported-versions.php">PHP</a>, <a href="https://www.python.org/dev/peps/pep-0373">Python</a>.</p>
<p><?php echo __('The following settings might have a negative impact on certain functionalities of MISP with their current and recommended minimum settings. You can adjust these in your php.ini. Keep in mind that the recommendations are not requirements, just recommendations. Depending on usage you might want to go beyond the recommended values.');?></p>
<?php
foreach ($phpSettings as $settingName => &$phpSetting):

View File

@ -0,0 +1,33 @@
<?php
$mayModify = (($isAclModify && $event['Event']['user_id'] == $me['id'] && $event['Orgc']['id'] == $me['org_id']) || ($isAclModifyOrg && $event['Orgc']['id'] == $me['org_id']));
$mayPublish = ($isAclPublish && $event['Orgc']['id'] == $me['org_id']);
?>
<div>
<div id="timeline-header" class="eventgraph_header">
<label id="timeline-scope" class="btn center-in-network-header network-control-btn">
<span class="useCursorPointer fa fa-object-group" style="margin-right: 3px;"></span><?php echo __('Time scope')?>
</label>
<label id="timeline-display" class="btn center-in-network-header network-control-btn">
<span class="useCursorPointer fa fa-list-alt" style="margin-right: 3px;"></span><?php echo __('Display')?>
<span id="timeline-display-badge" class="badge"></span>
</label>
<select id="timeline-typeahead" class="center-in-network-header network-typeahead flushright position-absolute max-width-400" style="display:none" data-provide="typeahead" size="20" placeholder="Search for an item">
</select>
</div>
<div id="event_timeline" style="min-height: 100px;" data-user-manipulation="<?php echo $mayModify || $isSiteAdmin ? 'true' : 'false'; ?>" data-extended="<?php echo $extended; ?>">
<div class="loadingTimeline">
<div class="spinner"></div>
<div class="loadingText"><?php echo __('Loading');?></div>
</div>
</div>
<span id="fullscreen-btn-timeline" class="fullscreen-btn-timeline btn btn-xs btn-primary" data-toggle="tooltip" data-placement="top" data-title="<?php echo __('Toggle fullscreen');?>"><span class="fa fa-desktop"></span></span>
</div>
<?php
echo $this->Html->script('moment-with-locales');
echo $this->Html->script('event-timeline');
echo $this->Html->css('event-timeline');
?>

View File

@ -508,6 +508,9 @@
<button class="btn btn-inverse toggle qet galaxy-toggle-button" id="eventgraph_toggle" data-toggle-type="eventgraph" onclick="enable_interactive_graph();">
<span class="icon-plus icon-white" title="<?php echo __('Toggle Event graph');?>" role="button" tabindex="0" aria-label="<?php echo __('Toggle Event graph');?>" style="vertical-align:top;"></span><?php echo __('Event graph');?>
</button>
<button class="btn btn-inverse toggle qet galaxy-toggle-button" id="eventtimeline_toggle" data-toggle-type="eventtimeline" onclick="enable_timeline();">
<span class="icon-plus icon-white" title="<?php echo __('Toggle Event timeline');?>" role="button" tabindex="0" aria-label="<?php echo __('Toggle Event timeline');?>" style="vertical-align:top;"></span><?php echo __('Event timeline');?>
</button>
<button class="btn btn-inverse toggle qet galaxy-toggle-button" id="correlationgraph_toggle" data-toggle-type="correlationgraph" onclick="enable_correlation_graph();">
<span class="icon-plus icon-white" title="<?php echo __('Toggle Correlation graph');?>" role="button" tabindex="0" aria-label="<?php echo __('Toggle Correlation graph');?>" style="vertical-align:top;"></span><?php echo __('Correlation graph');?>
</button>
@ -533,6 +536,9 @@
<div id="eventgraph_div" class="info_container_eventgraph_network" style="display: none;" data-fullscreen="false">
<?php echo $this->element('view_event_graph'); ?>
</div>
<div id="eventtimeline_div" class="info_container_eventtimeline" style="display: none;" data-fullscreen="false">
<?php echo $this->element('view_timeline'); ?>
</div>
<div id="correlationgraph_div" class="info_container_eventgraph_network" style="display: none;" data-fullscreen="false">
</div>
<div id="attackmatrix_div" class="info_container_eventgraph_network" style="display: none;" data-fullscreen="false" data-mitre-attack-galaxy-id="<?php echo h($mitreAttackGalaxyId)?>">

View File

@ -5,7 +5,7 @@
echo $this->Form->create('Object', array('id', 'url' => $url, 'enctype' => 'multipart/form-data'));
?>
<h3><?php echo ucfirst($action) . ' ' . Inflector::humanize(h($template['ObjectTemplate']['name'])) . __(' Object'); ?></h3>
<div class="row-fluid" style="margin-bottom:10px;">
<div id="meta-div" class="row-fluid" style="margin-bottom:10px;">
<dl class="span8">
<dt><?php echo __('Object Template');?></dt>
<dd>
@ -77,6 +77,19 @@
));
?>
</dd>
<?php
echo $this->Form->input('first_seen', array(
'type' => 'text',
'div' => 'input hidden',
'required' => false,
));
echo $this->Form->input('last_seen', array(
'type' => 'text',
'div' => 'input hidden',
'required' => false,
));
?>
<div id="bothSeenSliderContainer"></div>
</dl>
</div>
<?php
@ -309,6 +322,7 @@
<?php
echo $this->element('form_seen_input');
if (!$ajax) {
echo $this->element('/genericElements/SideMenu/side_menu', array('menuList' => 'event', 'menuItem' => 'addObject', 'event' => $event));
}

View File

@ -0,0 +1,22 @@
<?php
echo $this->Form->create('Object', array('class' => 'inline-form inline-field-form', 'id' => 'Object_' . $object['id'] . '_comment_form', 'url' => '/objects/editField/' . $object['id']));
?>
<div class='inline-input inline-input-container'>
<div class="inline-input-accept inline-input-button inline-input-passive"><span class = "icon-ok" role="button" tabindex="0" aria-label="<?php echo __('Accept change'); ?>"></span></div>
<div class="inline-input-decline inline-input-button inline-input-passive"><span class = "icon-remove" role="button" tabindex="0" aria-label="<?php echo __('Discard change'); ?>"></span></div>
<?php
echo $this->Form->input('comment', array(
'type' => 'textarea',
'label' => false,
'value' => $object['comment'],
'error' => array('escape' => false),
'class' => 'inline-input',
'id' => 'Object' . '_' . $object['id'] . '_comment_field',
'div' => false
));
echo $this->Form->end();
?>
</div>
<?php
echo $this->Form->end();
?>

View File

@ -0,0 +1,19 @@
<?php
echo $this->Form->create('Object', array('class' => 'inline-form inline-field-form', 'id' => 'Object_' . $object['id'] . '_distribution_form', 'url' => '/objects/editField/' . $object['id']));
?>
<div class='inline-input inline-input-container'>
<div class="inline-input-accept inline-input-button inline-input-passive"><span class = "icon-ok" role="button" tabindex="0" aria-label="<?php echo __('Accept change'); ?>"></span></div>
<div class="inline-input-decline inline-input-button inline-input-passive"><span class = "icon-remove" role="button" tabindex="0" aria-label="<?php echo __('Discard change'); ?>"></span></div>
<?php
echo $this->Form->input('distribution', array(
'options' => array($distributionLevels),
'label' => false,
'selected' => $object['distribution'],
'error' => array('escape' => false),
'class' => 'inline-input',
'id' => 'Object_' . $object['id'] . '_distribution_field',
'div' => false
));
echo $this->Form->end();
?>
</div>

View File

@ -0,0 +1,14 @@
<?php
echo $this->Form->create('Object', array('id' => 'Object' . '_' . $object['id'] . '_first_seen_form', 'url' => '/objects/editField/' . $object['id']));
?>
<?php
echo $this->Form->input('first_seen', array(
'label' => false,
'type' => 'text',
'value' => 0,
'id' => 'Object' . '_' . $object['id'] . '_first_seen_field',
'div' => false
));
echo $this->Form->end();
?>
</div>

View File

@ -0,0 +1,14 @@
<?php
echo $this->Form->create('Object', array('id' => 'Object' . '_' . $object['id'] . '_last_seen_form', 'url' => '/objects/editField/' . $object['id']));
?>
<?php
echo $this->Form->input('last_seen', array(
'label' => false,
'type' => 'text',
'value' => 0,
'id' => 'Object' . '_' . $object['id'] . '_last_seen_field',
'div' => false
));
echo $this->Form->end();
?>
</div>

View File

@ -0,0 +1,2 @@
<?php
echo nl2br(h($value)) . '&nbsp;';

View File

@ -0,0 +1,152 @@
<div class="generic-picker-embeded-block">
<?php
$url = '/objects/quickAddAttributeForm/' . $object['id'];
$element = $template_element;
$k = 0;
$action = 'add';
echo $this->Form->create('Object', array(
'id' => 'Object_' . $object['id'] . '_quick_add_attribute_form',
'url' => $url,
'class' => 'allDiv'
));
?>
<fieldset>
<legend><?php echo __('Add Object attribute'); ?></legend>
<div class="add_attribute_fields">
<?php
$formSettings = array(
'type' => 'hidden',
'value' => $element['object_relation'],
'label' => false,
'div' => false
);
echo $this->Form->input('Attribute.' . $k . '.object_relation', $formSettings);
$formSettings['value'] = $object['id'];
echo $this->Form->input('Object.id', $formSettings);
$formSettings['value'] = $object['event_id'];
echo $this->Form->input('Object.event_id', $formSettings);
$formSettings['value'] = $element['type'];
echo '<div style="margin-bottom: 5px; font-size: 14px;">';
echo $this->Form->input('Attribute.' . $k . '.type', $formSettings);
echo '<span class="bold">' . Inflector::humanize(h($element['object_relation'])) . '</span>';
echo ' :: ' . h($element['type']) . '';
echo '<br>';
echo '<span class="immutableAttributeDescription">' . h($element['description']) . '</span>';
echo '</div>';
?>
<?php
$formSettings = array(
'options' => array_combine($element['categories'], $element['categories']),
'default' => $element['default_category'],
'div' => true
);
echo $this->Form->input('Attribute.' . $k . '.category', $formSettings);
?>
<div class='input'>
<?php
echo $this->Form->input('Attribute.' . $k . '.distribution', array(
'class' => 'Attribute_distribution_select',
'options' => $distributionData['levels'],
'default' => !empty($element['distribution']) ? $element['distribution'] : $distributionData['initial'],
'div' => false,
'label' => __('Distribution ') . $this->element('formInfo', array('type' => 'distribution')),
));
?>
</div>
<div class='input'>
<div id="SGContainer" style="display:none;">
<?php
if (!empty($distributionData['sgs'])) {
echo $this->Form->input('Attribute.' . $k . '.sharing_group_id', array(
'options' => $distributionData['sgs'],
'label' => __('Sharing Group')
));
}
?>
</div>
</div>
<div class="clear">
<?php
echo '<label for="Attribute' . $k . '.value">' . __('Value') . '</label>';
echo $this->element(
'Objects/object_value_field',
array(
'element' => $element,
'k' => $k,
'action' => $action
)
);
?>
</div>
<?php
echo $this->Form->input('Attribute.' . $k . '.to_ids', array(
'type' => 'checkbox',
'checked' => $element['to_ids'],
));
?>
<?php
echo $this->Form->input('Attribute.' . $k . '.disable_correlation', array(
'type' => 'checkbox',
'checked' => $element['disable_correlation'],
));
?>
<?php
echo $this->Form->input('Attribute.' . $k . '.comment', array(
'type' => 'textarea',
'required' => false,
'allowEmpty' => true,
'div' => 'input clear',
'class' => 'input-xxlarge'
));
?>
</div>
</fieldset>
<div class="overlay_spacing">
<?php if ($ajax): ?>
<span id="submitButton" class="btn btn-primary" style="margin-bottom:5px;float:left;" title="<?php echo __('Submit'); ?>" role="button" tabindex="0" aria-label="<?php echo __('Submit'); ?>" onClick="submitPopoverForm('<?php echo h($object['id']); ?>', 'quickAddAttributeForm', <?php echo h($object['event_id']); ?>, 0, $(this).closest('div.popover').attr('data-dismissid'))"><?php echo __('Submit'); ?></span>
<?php else:
echo $this->Form->button('Submit', array('class' => 'btn btn-primary'));
endif;
?>
</div>
<?php
echo $this->Form->end();
?>
</div>
<script>
<?php
$formInfoTypes = array('distribution' => 'Distribution');
echo 'var formInfoFields = ' . json_encode($formInfoTypes) . PHP_EOL;
foreach ($formInfoTypes as $formInfoType => $humanisedName) {
echo 'var ' . $formInfoType . 'FormInfoValues = {' . PHP_EOL;
foreach ($info[$formInfoType] as $key => $formInfoData) {
echo '"' . $key . '": "<span class=\"blue bold\">' . h($formInfoData['key']) . '</span>: ' . h($formInfoData['desc']) . '<br />",' . PHP_EOL;
}
echo '}' . PHP_EOL;
}
?>
$(document).ready(function() {
initPopoverContent('Attribute0');
$('#Attribute0Distribution').change(function() {
initPopoverContent('Attribute0');
if ($('#Attribute0Distribution').val() == 4) $('#SGContainer').show();
else $('#SGContainer').hide();
});
});
</script>

View File

@ -49,6 +49,13 @@
<td class="bold"><?php echo __('Comment');?></td>
<td><?php echo h($data['Object']['comment']); ?></td>
</tr>
<td class="bold"><?php echo __('First seen');?></td>
<td><?php echo h($data['Object']['first_seen']); ?></td>
</tr>
<tr>
<td class="bold"><?php echo __('Last seen');?></td>
<td><?php echo h($data['Object']['last_seen']); ?></td>
</tr>
<tr>
<table id="attribute_table" class="table table-condensed table-striped">
<thead>

View File

@ -40,7 +40,18 @@
echo $this->Form->input('batch_import', array(
'type' => 'checkbox',
));
echo $this->Form->input('first_seen', array(
'type' => 'text',
'div' => 'input hidden',
'required' => false,
));
echo $this->Form->input('last_seen', array(
'type' => 'text',
'div' => 'input hidden',
'required' => false,
));
?>
<div id="bothSeenSliderContainer"></div>
</div>
</fieldset>
<p style="color:red;font-weight:bold;display:none;<?php if ($ajax) echo 'text-align:center;'; ?>" id="warning-message"><?php echo __('Warning: You are about to share data that is of a classified nature (Attribution / targeting data). Make sure that you are authorised to share this.');?></p>
@ -72,6 +83,8 @@
if (!$ajax) {
echo $this->element('/genericElements/SideMenu/side_menu', array('menuList' => 'event', 'menuItem' => 'proposeAttribute', 'event' => $event));
}
echo $this->element('form_seen_input');
?>
<script type="text/javascript">
<?php

View File

@ -40,7 +40,18 @@
echo $this->Form->input('to_ids', array(
'label' => __('IDS Signature?'),
));
echo $this->Form->input('first_seen', array(
'type' => 'text',
'div' => 'input hidden',
'required' => false,
));
echo $this->Form->input('last_seen', array(
'type' => 'text',
'div' => 'input hidden',
'required' => false,
));
?>
<div id="bothSeenSliderContainer"></div>
</fieldset>
<p style="color:red;font-weight:bold;display:none;<?php if (isset($ajax) && $ajax) echo "text-align:center;"?>" id="warning-message"><?php echo __('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.');?></p>
<?php if (isset($ajax) && $ajax): ?>
@ -69,6 +80,8 @@
<?php
$event['Event']['id'] = $this->request->data['ShadowAttribute']['event_id'];
echo $this->element('/genericElements/SideMenu/side_menu', array('menuList' => 'event', 'menuItem' => 'proposeAttribute', 'event' => $event));
echo $this->element('form_seen_input');
?>
<script type="text/javascript">

@ -1 +1 @@
Subproject commit 5da0c7bd545ee93cf40786c1c535b9d4897943b1
Subproject commit dbaab413b6b4680ae458a7d7a8ac6e1917fcc357

@ -1 +1 @@
Subproject commit bce101832597b5f62679ac0299b2b4ef4681a145
Subproject commit fa634803911d211f993049242d41eebaf342a9c4

View File

@ -26,6 +26,7 @@
.contextual-menu > label {
font-weight: bold;
margin-right: 3px;
user-select: none;
}
.contextual-menu > div > label {

View File

@ -0,0 +1,151 @@
.info_container_eventtimeline {
position: relative;
margin-top:10px;
border: 1px solid #0088cc;
border-radius: 7px;
box-shadow: 0px 0px 6px #B2B2B2;
width: 100%;
min-height: 100px;
padding: 1px;
}
.vis-timeline {
position: relative;
border: none;
overflow: hidden;
padding: 0;
margin: 0;
box-sizing: border-box;
}
.fullscreen-btn-timeline {
float: right;
display: inline-block;
bottom: 33px;
position: relative;
right: 3px;
opacity: 0.7;
z-index: 1;
cursor: nesw-resize;
}
.loadingTimeline {
display: none;
position: absolute;
margin-top: 10px;
width: 100%;
text-align: center;
}
.loadingTimeline > div.loadingText {
left: 0px;
}
.timestamp-obj {
box-shadow: #ff0000 0px 0px 0px 5px;
}
.timestamp-attr {
box-shadow: #ff0000 0px 0px 0px 5px;
padding-top: 4px;
padding-bottom: 4px;
}
.vis-item.attribute {
background-color: orange;
border-color: black;
}
.vis-item.attribute.vis-selected {
box-shadow: 0 0 20px rgba(82, 168, 236, 1);`
}
.vis-item.object {
background-color: #3465a4;
border-color: black;
color: white;
}
.vis-item.object.vis-selected {
box-shadow: 0 0 20px rgba(82, 168, 236, 1);`
}
.vis-item.object_attribute {
background-color: greenyellow;
border-color: green;
}
.timeline-objectName {
text-align:left;
border-bottom: white solid 1px;
}
.timeline-objectAttrType {
text-align:left;
line-height: 14px;
font-style: italic;
}
.timeline-objectAttrVal {
text-align:left;
line-height: 14px;
padding-left: 5px;
}
.vis-delete {
top: -8px !important;
padding: 3px !important;
height: 22px !important;
width: 20px !important;
}
.vis-expand-action {
position: absolute;
padding: 3px 2px !important;
height: 22px !important;
width: 22px !important;
top: 15px;
right: -25px;
box-sizing: border-box;
cursor: pointer;
-webkit-transition: background 0.2s linear;
-moz-transition: background 0.2s linear;
-ms-transition: background 0.2s linear;
-o-transition: background 0.2s linear;
transition: background 0.2s linear;
}
.vis-item .vis-expand-action:after {
color: green;
font-family: arial, sans-serif;
font-size: 22px;
font-weight: bold;
-webkit-transition: color 0.2s linear;
-moz-transition: color 0.2s linear;
-ms-transition: color 0.2s linear;
-o-transition: color 0.2s linear;
transition: color 0.2s linear;
}
.expand-btn:after {
content: "\21d4";
}
.collapse-btn:after {
content: "\21b5";
}
.vis-item .vis-expand-action:hover {
background: green;
}
.vis-item .vis-expand-action:hover:after {
color: white;
}
.vis-onUpdateTime-tooltip {
background: #7b7b7b !important;
padding: 0px !important;
border-radius: 5px !important;
border: solid 1px #000 !important;
}
#timeline-typeahead {
width: 400px;
}

View File

@ -25,6 +25,10 @@ button .full-width {
margin-bottom:10px;
}
form button.btn[disabled] {
cursor: not-allowed;
}
/*.close{
float:none;
}*/
@ -301,6 +305,39 @@ td.highlight3 {
}
table.table-striped > tbody > tr.objectAddFieldTr > td {
position: absolute;
background-color: #ffffff00;
}
table.table-striped > tbody > tr > td > span.objectAddField {
padding: 0px;
line-height: normal;
display: inline-block;
position: relative;
top: -19px;
left: -10px;
color: white;
background-color: #1369a0;
border: 0px;
border-radius: 15px;
height: 16px;
width: 16px;
text-align: center;
}
table.table-striped > tbody > tr > td > span.objectAddField:before {
left: 2px;
height: 12px;
width: 12px;
line-height: 16px;
}
.table-striped tbody > tr > td > span.objectAddField:hover {
transform: scale(1.7);
cursor: pointer;
}
tr.highlightBlueSides {
border-left:2px solid #0088cc;
border-right:2px solid #0088cc;
@ -880,6 +917,20 @@ a.proposal_link_red:hover {
border: 0;
}
.immutableAttributeDescription:before {
background: rgba(255, 255, 255, 0);
content: '';
margin-right: 5px;
margin-left: 2px;
border-left: 1px solid;
border-bottom: 1px solid;
height: 10px;
width: 5px;
position: relative;
display: block;
float: left;
}
.modal-body-long {
max-height: 600px;
}
@ -1043,6 +1094,16 @@ a.pill-pre-picker {
position: absolute;
}
div.generic-picker-wrapper > div.generic-picker-embeded-block {
margin-left: 15px;
margin-top: -12px;
display: inline-block;
}
div.generic-picker-embeded-block legend {
margin-bottom: 0px;
}
.generic-picker-item-element-check {
float: right;
border: 1px solid #999;
@ -2307,6 +2368,23 @@ table tr:hover .down-expand-button {
position: relative;
}
.nanosec-div {
font-weight:bold;
position: relative;
width: 100%;
text-align: center;
top: -5px;
}
.larger-input-field {
width: 270px !important;
}
.precision-tool {
margin-left: 10px;
display: inline-block;
}
.fa-as-icon {
font-size: 20px;
padding-left: 2px;

View File

@ -1461,6 +1461,7 @@
return i.jquery ? i[0] : i;
});
delete options.inputs;
this.preventMultipleSet = options.preventMultipleSet;
datepickerPlugin.call($(this.inputs), options)
.on('changeDate', $.proxy(this.dateUpdated, this));
@ -1507,10 +1508,12 @@
if (i === -1)
return;
$.each(this.pickers, function(i, p){
if (!p.getUTCDate())
p.setUTCDate(new_date);
});
if (!this.preventMultipleSet) {
$.each(this.pickers, function(i, p){
if (!p.getUTCDate())
p.setUTCDate(new_date);
});
}
if (new_date < this.dates[j]){
// Date being moved earlier/left

View File

@ -143,6 +143,7 @@ class ContextualMenu {
__create_menu_div_bootstrap_popover() {
var div = document.createElement('div');
div.classList.add("contextual-menu");
div.style.display = 'none';
this.container.appendChild(div);
var that = this;
this.trigger_container.tabIndex = 0; // required for the popover focus feature
@ -151,19 +152,20 @@ class ContextualMenu {
container: 'body',
html: true,
placement: "bottom",
content: function () {return $(that.menu); }, // return contextual menu html
content: function () { var html=$(that.menu); html.css('display', 'inline-block'); return html;}, // return contextual menu html
trigger: "manual",
template: '<div class="popover" id="popover-contextual-menu-'+this.trigger_container.id+'" role="tooltip" style="'+additional_styling+'"><div class="arrow"></div></h3><div class="popover-content"></div></div>'
})
// Overwrite the default popover behavior: hidding cause the popover to be detached from the DOM, making impossible to fetch input values in the form
$(this.trigger_container).click (function(e) {
if (that.has_been_shown_once) {
$('#popover-contextual-menu-'+this.id).toggle();
} else {
that.has_been_shown_once = true;
$(this).popover('show');
}
$(this).popover('toggle');
// if (that.has_been_shown_once) {
// $('#popover-contextual-menu-'+this.id).toggle();
// } else {
// that.has_been_shown_once = true;
// $(this).popover('show');
// }
});
return div;
}
@ -371,7 +373,7 @@ class ContextualMenu {
this.menu.appendChild(label);
this.menu.appendChild(div);
if(options.event !== undefined) {
button.addEventListener("click", function(evt) {
button.addEventListener("click", function(evt) {
var corresponding_select_id = evt.target.dataset.correspondingId;
var selected_value = $('#'+corresponding_select_id).val();
options.event(selected_value);

View File

@ -0,0 +1,661 @@
var max_displayed_char_timeline = 64;
var eventTimeline;
var items_timeline;
var items_backup;
var use_local_timezone = true;
var mapping_text_to_id = new Map();
var user_manipulation = $('#event_timeline').data('user-manipulation');
var extended_text = $('#event_timeline').data('extended') == 1 ? "extended:1/" : "";
var container_timeline = document.getElementById('event_timeline');
var hardThreshold = 50;
var softThreshold = 30;
var timeline_disabled = false;
var default_editable = {
add: false, // add new items by double tapping
updateTime: true, // drag items horizontally
remove: true
};
var relationship_type_mapping = {
'followed-by': 'after',
'preceding-by': 'before',
}
var options = {
template: function (item, element, data) {
switch(item.group) {
case "attribute":
return build_attr_template(item);
case "object":
return build_object_template(item);
case "object_attribute":
console.log('Error');
break;
default:
break;
}
},
moment: function(date) {
if (use_local_timezone) {
return vis.moment(date);
} else {
return vis.moment(date).utc();
}
},
verticalScroll: true,
zoomKey: 'altKey',
maxHeight: 400,
minHeight: 400,
multiselect: true,
editable: user_manipulation ? default_editable : false,
tooltipOnItemUpdateTime: true,
onRemove: function(item, callback) { // clear timestamps
update_seen(item, 'first', null, false, function() {
update_seen(item, 'last', null, false, function() { reflect_change(true); });
});
eventTimeline.setSelection([]);
$('.timelineSelectionTooltip').remove()
},
onMove: function(item, callback) {
var newStart = moment(item.start.toISOString());
var newEnd = (item.end !== undefined && item.end !== null) ? moment(item.end.toISOString()) : null;
var c1 = item.first_seen !== null ? !item.first_seen.isSame(newStart) : true;
var c2 = item.last_seen !== null ? !item.last_seen.isSame(newEnd) && item.seen_enabled : true;
if (c1) {
if (item.first_seen === null) {
if (!c2) {
update_seen(item, 'first', newStart, true, undefined);
} else {
update_seen(
item,
'first',
newStart,
false,
function() {
update_seen(
item,
'last',
newEnd,
true,
function() {
reflect_change(true);
}
);
}
);
}
} else {
update_seen(item, 'first', newStart, false, function() {
if (c2) {
update_seen(item, 'last', newEnd, true, undefined);
}
});
}
}
if (c2 && !c1) {
update_seen(item, 'last', newEnd, true, undefined);
}
}
};
function isDefined(element) {
return element !== undefined && element !== null;
}
function generate_timeline_tooltip(itemID, target) {
var item = items_timeline.get(itemID);
if (
item.first_seen === undefined
|| item.first_seen === null
|| item.first_seen_overwrite
) { // do not generate if first_seen not set
return;
}
var closest = $(target.closest(".vis-selected.vis-editable"));
var btn_type = item.last_seen !== null ? 'collapse-btn' : 'expand-btn';
var fct_type = item.last_seen !== null ? 'collapseItem' : 'expandItem';
var btn = $('<div class="timelineSelectionTooltip vis-expand-action '+btn_type+'" data-itemid="'+item.id+'"></div>')
if (item.last_seen !== null) {
btn.click(collapseItem);
} else {
btn.click(expandItem);
}
closest.append(btn);
}
/* UTIL */
function collapseItem() {
var itemID = $(this).data('itemid');
var item = items_timeline.get(itemID);
update_seen(item, 'last', null, true, undefined);
}
function expandItem() {
var itemID = $(this).data('itemid');
var item = items_timeline.get(itemID);
var newEnd = get_next_step(item.first_seen);
update_seen(item, 'last', newEnd, true, undefined);
}
function get_next_step(mom) {
var scale = adapt_scale(eventTimeline.timeAxis.step.scale);
var momAhead = mom.clone();
momAhead.add(1, scale);
return momAhead;
}
function adapt_scale(scale) {
first_letter = scale.charAt(0);
if (first_letter !== 'm' && first_letter !== 'w') {
return first_letter;
} else {
switch (scale) {
case 'millisecond':
return 'ms';
case 'minute':
return 'm';
case 'month':
return 'M';
case 'week':
return 'w';
case 'weekday':
return 'd';
default:
return scale;
}
}
}
function build_attr_template(attr) {
var span = $('<span data-itemID="'+attr.id+'">');
if (!attr.seen_enabled) {
span.addClass('timestamp-attr');
}
span.text(attr.content);
span.data('seen_enabled', attr.seen_enabled);
var html = span[0].outerHTML;
return html;
}
function build_object_template(obj) {
var table = $('<table>');
table.data('seen_enabled', obj.seen_enabled);
if (!obj.seen_enabled) {
table.addClass('timestamp-obj');
}
var bolt_html = obj.overwrite_enabled ? " <i class=\"fa fa-bolt\" style=\"color: yellow; font-size: large;\" title=\"The Object is overwritten by its attributes\">" : "";
table.append($('<tr class="timeline-objectName"><th>'+obj.content+bolt_html+'</th><th></th></tr>'));
for (var attr of obj.Attribute) {
var overwritten = obj.overwrite_enabled && (attr.contentType == "first-seen" || attr.contentType == "last-seen") ? " <i class=\"fa fa-bolt\" style=\"color: yellow;\" title=\"Overwrite object "+attr.contentType+"\"></i>" : "";
table.append(
$('<tr>').append(
$('<td class="timeline-objectAttrType">' + attr.contentType + '</td>'
+'<td class="timeline-objectAttrVal">' + attr.content+overwritten + '</td>'
)
)
)
}
var html = table[0].outerHTML;
return html;
}
function contain_seen_attribute(obj) {
if (obj['Attribute'] === undefined) {
return false;
}
for (var i = 0; i < obj['Attribute'].length; i++) {
var attribute = obj['Attribute'][i];
if (attribute['contentType'] == 'first-seen' || attribute['contentType'] == 'last-seen') {
return true;
}
}
return false;
}
function reflect_change(onIndex, itemType, itemId, item) {
if (onIndex) {
updateIndex(scope_id, 'event'); // MISP function
} else { // reflect change on item only
quick_fetch_seens(itemType, item.orig_id, function(firstSeen, lastSeen) {
var updatedItem = items_timeline.get(itemId);
updatedItem.first_seen = firstSeen;
updatedItem.last_seen = lastSeen;
updatedItem.first_seen_overwrite = false;
updatedItem.last_seen_overwrite = false;
if (user_manipulation) {
var e = $.extend({}, default_editable);
e.remove = true;
updatedItem.editable = e;
}
set_spanned_time(updatedItem);
items_timeline.remove(updatedItem.id);
items_timeline.add(updatedItem);
});
}
}
function quick_fetch_seens(itemType, itemId, callback) {
var url = "/" + itemType + "/" + "fetchViewValue" + "/" + itemId + "/";
var dfs = $.ajax({
dataType: "html",
cache: false,
success: function(data, textStatus) {
return data;
},
url: url+"first_seen"
});
var dls = $.ajax({
dataType: "html",
cache: false,
success: function(data, textStatus) {
return data;
},
url: url+"last_seen"
});
$.when( dfs, dls).done(function(a1, a2) {
firstSeen = a1[0].replace('&nbsp;', '');
firstSeen = firstSeen == '' ? null : firstSeen;
lastSeen = a2[0].replace('&nbsp;', '');
lastSeen = lastSeen == '' ? null : lastSeen;
callback(firstSeen, lastSeen);
});
}
function update_seen(item, seenType, value, reflect, callback) {
var itemType = item.group + 's';
var momentISO = value !== null ? value.toISOString() : null;
fetch_form_and_submit(itemType, item, seenType, momentISO, reflect, callback);
}
function fetch_form_and_submit(itemType, item, seenType, value, reflect, callback) {
var url = "/" + itemType + "/fetchEditForm/" + item.orig_id + "/" + seenType+"_seen";
$.ajax({
beforeSend: function (XMLHttpRequest) {
$(".loadingTimeline").show();
},
dataType:"html",
cache: false,
success: function (data, textStatus) {
var form = $(data);
$(container_timeline).append(form);
form.css({display: 'none'});
var field = form.find('input[name*="' + seenType + '_seen"]');
field.val(value);
// submit the form
$.ajax({
data: form.serialize(),
cache: false,
success:function (data, textStatus) {
if (reflect) {
if (contain_seen_attribute(item)) {
reflect_change(true, itemType, item.id, item);
} else {
reflect_change(false, itemType, item.id, item);
}
}
form.remove()
},
error:function() {
console.log('fail', 'Request failed for an unknown reason.');
},
complete: function () {
$(".loadingTimeline").hide();
if (callback !== undefined) {
callback();
}
},
type:"post",
url: form.attr('action')
});
},
error: function() {
console.log('Feature not supported.');
},
url: url,
});
}
function timestampToMoment(timestamp) {
var factor = 1000;
var d = moment(timestamp*factor);
return d;
}
function set_spanned_time(item) {
var timestamp = item.timestamp;
var fs = item.first_seen == null ? null : moment(item.first_seen);
var ls = item.last_seen == null ? null : moment(item.last_seen);
item.first_seen = fs;
item.last_seen = ls;
item.seen_enabled = false;
item.overwrite_enabled = false;
if (fs===null && ls===null) {
item.start = timestampToMoment(timestamp);
item.type = 'box';
} else if (fs===null && ls!==null) {
item.start = timestampToMoment(timestamp);
item.type = 'box';
} else if (ls===null && fs!==null) {
item.start = fs;
item.seen_enabled = true;
delete item.end;
item.type = 'box';
} else { // fs and ls are defined
item.start = fs;
item.end = ls;
item.seen_enabled = true;
if (fs == ls) {
item.type = 'box';
} else {
item.type = 'range';
}
}
if (item.first_seen_overwrite === true || item.last_seen_overwrite === true) {
if (user_manipulation) {
var e = $.extend({}, default_editable);
e.remove = false;
item.editable = e;
}
item.overwrite_enabled = true;
}
}
function map_scope(val) {
switch(val) {
case 'First seen/Last seen':
return 'seen';
case 'Object relationship':
return 'relationship';
default:
return 'seen';
}
}
function timelinePopupCallback(state) {
if (eventTimeline === undefined) {
return;
}
reload_timeline();
}
function adjust_text_length(elem) {
var maxChar = $('#slider_timeline_display_max_char_num').val();
maxChar = maxChar === undefined ? 64 : maxChar;
elem.content = elem.content.substring(0, maxChar) + (elem.content.length < maxChar ? "" : "[...]");
}
function update_badge() {
if (use_local_timezone) {
$("#timeline-display-badge").text("Timezone: " + ": " + moment().format('Z'));
} else {
$("#timeline-display-badge").text("Timezone: " + ": " + moment().utc().format('Z (z)'));
}
}
function reload_timeline() {
update_badge();
var payload = {scope: map_scope($('#select_timeline_scope').val())};
$.ajax({
url: "/events/"+"getEventTimeline"+"/"+scope_id+"/"+extended_text+"event.json",
dataType: 'json',
type: 'post',
contentType: 'application/json',
data: JSON.stringify( payload ),
processData: false,
beforeSend: function (XMLHttpRequest) {
$(".loadingTimeline").show();
},
success: function( data, textStatus, jQxhr ){
items_timeline.clear();
for (var item of data.items) {
item.className = item.group;
item.orig_id = item.id;
item.id = item.uuid;
set_spanned_time(item);
if (item.group == 'object') {
for (var attr of item.Attribute) {
mapping_text_to_id.set(attr.contentType+': '+attr.content+' ('+item.orig_id+')', item.id);
adjust_text_length(attr);
}
} else {
mapping_text_to_id.set(item.content+' ('+item.orig_id+')', item.id);
adjust_text_length(item);
}
}
items_timeline.add(data.items);
handle_not_seen_enabled($('#checkbox_timeline_display_hide_not_seen_enabled').prop('checked'), false)
},
error: function( jqXhr, textStatus, errorThrown ){
console.log( errorThrown );
},
complete: function() {
$(".loadingTimeline").hide();
}
});
}
function enable_timeline() {
if (eventTimeline !== undefined) {
return;
}
init_popover();
var chosen_options_timeline = {
max_shown_results: 20,
inherit_select_classes: true
};
var payload = {scope: map_scope($('#select_timeline_scope').val())};
$.ajax({
url: "/events/"+"getEventTimeline"+"/"+scope_id+"/"+extended_text+"event.json",
dataType: 'json',
type: 'post',
contentType: 'application/json',
data: JSON.stringify( payload ),
processData: false,
beforeSend: function (XMLHttpRequest) {
$(".loadingTimeline").show();
},
success: function( data, textStatus, jQxhr ){
if (data.items.length > hardThreshold) {
$('#eventtimeline_div').html('<div class="alert alert-danger" style="margin: 10px;">Timeline: To much data to show</div>');
timeline_disabled = true;
return;
} else if (data.items.length > softThreshold) {
var res = confirm('You are about to draw a lot ('+data.items.length+') of items in the timeline. Do you wish to continue?');
if (!res) {
$('#eventtimeline_div').html('<div class="alert alert-danger" style="margin: 10px;">Timeline: To much data to show</div>');
timeline_disabled = true;
return;
}
}
for (var item of data.items) {
item.className = item.group;
item.orig_id = item.id;
item.id = item.uuid;
set_spanned_time(item);
if (item.group == 'object') {
for (var attr of item.Attribute) {
mapping_text_to_id.set(attr.contentType+': '+attr.content+' ('+item.orig_id+')', item.id);
adjust_text_length(attr);
}
} else {
mapping_text_to_id.set(item.content+' ('+item.orig_id+')', item.id);
adjust_text_length(item);
}
}
items_timeline = new vis.DataSet(data.items);
eventTimeline = new vis.Timeline(container_timeline, items_timeline, options);
update_badge();
eventTimeline.on('select', handle_selection);
eventTimeline.on('doubleClick', handle_doubleClick);
items_timeline.on('update', function(eventname, data) {
handle_selection({
event: { target: $('span[data-itemID="'+data.items[0]+'"]')},
items: data.items
});
});
var $selectTypeahead = $('#timeline-typeahead');
Array.from(mapping_text_to_id.keys()).forEach(function(element) {
var value = mapping_text_to_id[element];
var $option = $('<option></option>');
$option.text(element);
$option.attr('value', value);
$selectTypeahead.append($option);
});
$selectTypeahead.css('display', '').chosen(chosen_options_timeline).on('change', function(evt, params) {
var value = params.selected;
var id = mapping_text_to_id.get(value);
eventTimeline.focus(id);
$("#timeline-typeahead").blur();
});
},
error: function( jqXhr, textStatus, errorThrown ){
console.log( errorThrown );
},
complete: function() {
$(".loadingTimeline").hide();
}
});
}
function handle_selection(data) {
var event = data.event;
var target = event.target;
var items = data.items;
if (items.length == 0) {
$('.timelineSelectionTooltip').remove()
} else {
for (var itemID of items) {
generate_timeline_tooltip(itemID, target);
}
}
}
function edit_item(id, callback) {
var item = items_timeline.get(id);
var group = item.group;
if (group == 'attribute') {
simplePopup('/attributes/edit/'+item.orig_id);
} else if (group == 'object') {
window.location = '/objects/edit/'+item.orig_id;
}
}
function handle_doubleClick(data) {
// should be replaced by keyboard shortcut: SHIFT+E ?
//edit_item(data.item);
}
function handle_not_seen_enabled(hide, include_hidden) {
include_hidden = include_hidden !== undefined ? include_hidden : true;
if (hide) {
var hidden = items_timeline.get({
filter: function(item) {
return !item.seen_enabled;
}
});
var hidden_ids = [];
items_timeline.forEach(function(item) {
hidden_ids.push(item.id);
});
items_timeline.remove(hidden)
items_backup = hidden;
} else if (include_hidden) {
if (items_backup !== undefined) {
items_timeline.add(items_backup);
}
}
}
$('#fullscreen-btn-timeline').click(function() {
var timeline_div = $('#eventtimeline_div');
var fullscreen_enabled = !timeline_div.data('fullscreen');
timeline_div.data('fullscreen', fullscreen_enabled);
var height_val = fullscreen_enabled == true ? "calc(100vh - 42px - 42px - 10px)" : "400px";
timeline_div.css("max-height", height_val);
setTimeout(function() { // timeline takes time to be drawn
timeline_div[0].scrollIntoView({
behavior: "smooth",
});
}, 1);
eventTimeline.setOptions({maxHeight: height_val});
});
// init_scope_menu
var menu_scope_timeline, menu_display_timeline;
function init_popover() {
if (timeline_disabled) return;
menu_scope_timeline = new ContextualMenu({
trigger_container: document.getElementById("timeline-scope"),
bootstrap_popover: true,
style: "z-index: 1",
container: document.getElementById("eventtimeline_div")
});
menu_scope_timeline.add_select({
id: "select_timeline_scope",
label: "Scope",
tooltip: "The time scope represented by the timeline",
event: function(value) {
if (value == "First seen/Last seen") {
reload_timeline();
}
},
options: ["First seen/Last seen"],
default: "First seen/Last seen"
});
menu_display_timeline = new ContextualMenu({
trigger_container: document.getElementById("timeline-display"),
bootstrap_popover: true,
style: "z-index: 1",
container: document.getElementById("eventtimeline_div")
});
menu_display_timeline.add_slider({
id: 'slider_timeline_display_max_char_num',
label: "Charater to show",
title: "Maximum number of charater to display in the label",
min: 8,
max: 2048,
value: max_displayed_char_timeline,
step: 8,
applyButton: true,
event: function(value) {
$("#slider_timeline__display_max_char_num").parent().find("span").text(value);
},
eventApply: function(value) {
reload_timeline();
}
});
menu_display_timeline.add_checkbox({
id: 'checkbox_timeline_display_hide_not_seen_enabled',
label: "Hide first seen not set",
title: "Hide items that does not have first seen sets",
event: function(value) {
handle_not_seen_enabled(value)
}
});
menu_display_timeline.add_checkbox({
id: 'checkbox_timeline_display_gmt',
label: "Display with current timezone",
title: "Set the dates relative to the browser timezone. Otherwise, keep dates in GMT",
event: function(value) {
use_local_timezone = value;
reload_timeline()
},
checked: true
});
}

View File

@ -419,6 +419,11 @@ function updateIndex(id, context, newPage) {
} else {
console.log("genericPopupCallback function not defined");
}
if (typeof timelinePopupCallback !== "undefined") {
timelinePopupCallback("success");
} else {
console.log("timelinepopupcallback function not defined");
}
},
url: url,
});
@ -447,15 +452,42 @@ function updateAttributeFieldOnSuccess(name, type, id, field, event) {
});
}
function updateObjectFieldOnSuccess(name, type, id, field, event) {
$.ajax({
beforeSend: function (XMLHttpRequest) {
if (field != 'timestamp') {
$(".loading").show();
}
},
dataType:"html",
cache: false,
success:function (data, textStatus) {
if (field != 'timestamp') {
$(".loading").hide();
$(name + '_solid').html(data);
$(name + '_placeholder').empty();
$(name + '_solid').show();
} else {
$('#' + type + '_' + id + '_' + 'timestamp_solid').html(data);
}
},
url:"/objects/fetchViewValue/" + id + "/" + field,
});
}
function activateField(type, id, field, event) {
resetForms();
if (type == 'denyForm') return;
var objectType = 'attributes';
var containerName = 'Attribute';
if (type == 'ShadowAttribute') {
objectType = 'shadow_attributes';
} else if (type == 'Object') {
objectType = 'objects';
containerName = 'Object';
}
var name = '#' + type + '_' + id + '_' + field;
var container_name = '#Attribute_' + id + '_' + field;
var container_name = '#' + containerName + '_' + id + '_' + field;
$.ajax({
beforeSend: function (XMLHttpRequest) {
$(".loading").show();
@ -593,6 +625,8 @@ function submitForm(type, id, field, context) {
var name = '#' + type + '_' + id + '_' + field;
if (type == 'ShadowAttribute') {
object_type = 'shadow_attributes';
} else if (type == 'Object') {
object_type = 'objects';
}
$.ajax({
data: $(name + '_field').closest("form").serialize(),
@ -748,7 +782,8 @@ function handleAjaxEditResponse(data, name, type, id, field, event) {
responseArray = data;
if (type == 'Attribute') {
if (responseArray.saved) {
showMessage('success', responseArray.success);
var msg = responseArray.success !== undefined ? responseArray.success : responseArray.message;
showMessage('success', msg);
updateAttributeFieldOnSuccess(name, type, id, field, event);
updateAttributeFieldOnSuccess(name, type, id, 'timestamp', event);
eventUnpublish();
@ -759,6 +794,17 @@ function handleAjaxEditResponse(data, name, type, id, field, event) {
}
if (type == 'ShadowAttribute') {
updateIndex(event, 'event');
} else if (type == 'Object') {
if (responseArray.saved) {
var msg = responseArray.success !== undefined ? responseArray.success : responseArray.message;
showMessage('success', msg);
updateObjectFieldOnSuccess(name, type, id, field, event);
updateObjectFieldOnSuccess(name, type, id, 'timestamp', event);
eventUnpublish();
} else {
showMessage('fail', 'Validation failed: ' + responseArray.errors.value);
updateObjectFieldOnSuccess(name, type, id, field, event);
}
}
if (responseArray.hasOwnProperty('check_publish')) {
checkAndSetPublishedInfo();
@ -1139,7 +1185,7 @@ function openGenericModal(url) {
});
}
function submitPopoverForm(context_id, referer, update_context_id, modal) {
function submitPopoverForm(context_id, referer, update_context_id, modal, popover_dissmis_id_to_close) {
var url = null;
var context = 'event';
var contextNamingConvention = 'Attribute';
@ -1175,6 +1221,12 @@ function submitPopoverForm(context_id, referer, update_context_id, modal) {
case 'addSighting':
closePopover = false;
break;
case 'addObjectReference':
url = "/objectReferences/add/" + context_id;
break;
case 'quickAddAttributeForm':
url = "/objects/quickAddAttributeForm/" + context_id;
break;
}
if ($("#submitButton").parent().hasClass('modal-footer')) {
var $form = $("#submitButton").parent().parent().find('.modal-body form');
@ -1193,6 +1245,9 @@ function submitPopoverForm(context_id, referer, update_context_id, modal) {
if (closePopover) {
$("#gray_out").fadeOut();
$("#popover_form").fadeOut();
if (popover_dissmis_id_to_close !== undefined) {
$('[data-dismissid="' + popover_dissmis_id_to_close + '"]').popover('destroy');
}
$(".loading").show();
}
}
@ -1217,7 +1272,14 @@ function submitPopoverForm(context_id, referer, update_context_id, modal) {
$('#sightingsListAllToggle').removeClass('btn-inverse');
$('#sightingsListAllToggle').addClass('btn-primary');
}
if (context == 'event' && (referer == 'add' || referer == 'massEdit' || referer == 'replaceAttributes' || referer == 'addObjectReference')) eventUnpublish();
if (
(
context == 'event' &&
(referer == 'add' || referer == 'massEdit' || referer == 'replaceAttributes' || referer == 'addObjectReference' || referer == 'quickAddAttributeForm')
)
){
eventUnpublish();
}
},
error: function (jqXHR, textStatus, errorThrown) {
showMessage('fail', textStatus + ": " + errorThrown);
@ -1260,6 +1322,11 @@ function handleAjaxModalResponse(response, context_id, url, referer, context, co
result = "fail";
}
recoverValuesFromPersistance(savedArray);
},
error: function (jqXHR, textStatus, errorThrown) {
showMessage('fail', textStatus + ": " + errorThrown);
},
complete: function () {
$(".loading").hide();
},
url:url
@ -1718,8 +1785,22 @@ function popoverPopup(clicked, id, context, target, admin) {
$clicked.popover('show');
}
},
error:function() {
popover.options.content = '<div class="alert alert-error" style="margin-bottom: 0px;">Something went wrong - the queried function returned an exception. Contact your administrator for further details (the exception has been logged).</div>';
error:function(jqXHR, textStatus, errorThrown ) {
var errorJSON = '';
try {
errorJSON = JSON.parse(jqXHR.responseText);
errorJSON = errorJSON['errors'];
if (errorJSON === undefined) {
errorJSON = '';
}
} catch (SyntaxError) {
// no error provided
}
var errorText = '<div class="alert alert-error" style="margin-bottom: 3px;">Something went wrong - the queried function returned an exception. Contact your administrator for further details (the exception has been logged).</div>';
if (errorJSON !== '') {
errorText += '<div class="well"><strong>Returned error:</strong>' + $('<span/>').text(errorJSON).html() + '</div>';
}
popover.options.content = errorText;
$clicked.popover('show');
},
url: url

File diff suppressed because one or more lines are too long