new: [timeline/*-seen] Initial import of the timeline code from the

zoidberg branch
pull/4743/head
mokaddem 2019-06-13 09:16:34 +02:00
parent 3bcd7c57a3
commit e7f3d0d9df
34 changed files with 2554 additions and 100 deletions

View File

@ -919,7 +919,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];
@ -958,6 +958,10 @@ class AttributesController extends AppController
} else {
return new CakeResponse(array('body'=> json_encode(array('saved' => false, 'errors' => 'Could not update attribute, reason: ' . json_encode($this->Attribute->validationErrors))),'status' => 200, 'type' => 'json'));
}
} else {
if (!$result) {
return new CakeResponse(array('body'=> json_encode(array('saved' => false, 'errors' => 'Could not update attribute, reason: ' . json_encode($this->Attribute->validationErrors))),'status' => 200, 'type' => 'json'));
}
}
}
if ($result) {
@ -975,10 +979,11 @@ class AttributesController extends AppController
}
}
if ($this->_isRest() || $this->response->type() === 'application/json') {
$searchFields = array('id', 'type', 'to_ids', 'category', 'uuid', 'event_id', 'distribution', 'timestamp', 'comment', 'value', 'disable_correlation', 'first_seen', 'last_seen');
$saved_attribute = $this->Attribute->find('first', array(
'conditions' => array('id' => $this->Attribute->id),
'recursive' => -1,
'fields' => array('id', 'type', 'to_ids', 'category', 'uuid', 'event_id', 'distribution', 'timestamp', 'comment', 'value', 'disable_correlation'),
'fields' => $searchFields,
));
$response = array('response' => array('Attribute' => $saved_attribute['Attribute']));
$this->set('response', $response);
@ -1113,7 +1118,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);
@ -1721,7 +1726,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'],
@ -1934,7 +1939,7 @@ class AttributesController extends AppController
'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',
'includeProposals', 'returnFormat', 'published', 'limit', 'page', 'requested_attributes', 'includeContext', 'headerless',
'includeWarninglistHits', 'attackGalaxy', 'object_relation'
'includeWarninglistHits', 'attackGalaxy', 'object_relation', 'first_seen', 'last_seen'
);
$filterData = array(
'request' => $this->request,
@ -2389,7 +2394,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.'));
}

View File

@ -117,6 +117,7 @@ class ACLComponent extends Component
'getEventGraphReferences' => array('*'),
'getEventGraphTags' => array('*'),
'getEventGraphGeneric' => array('*'),
'getEventTimeline' => array('*'),
'genDistributionGraph' => array('*'),
'getDistributionGraph' => array('*'),
'getReferenceData' => array('*'),
@ -250,6 +251,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

@ -20,13 +20,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'),
'optional' => array('category', 'to_ids', 'uuid', 'distribution', 'sharing_group_id', 'timestamp', 'comment', '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'),
'optional' => array('value', 'type', 'category', 'to_ids', 'uuid', 'distribution', 'sharing_group_id', 'timestamp', 'comment', 'first_seen', 'last_seen'),
'params' => array('attribute_id')
),
'deleteSelected' => array(
@ -382,6 +382,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);
@ -395,6 +396,7 @@ class RestResponseComponent extends Component
if (!$message) {
$message = Inflector::singularize($controller) . ' ' . $action['action'] . ((substr($action['action'], -1) == 'e') ? 'd' : 'ed');
}
$response['saved'] = true;
$response['name'] = $message;
$response['message'] = $response['name'];
$response['url'] = $this->__generateURL($action, $controller, $id);

View File

@ -4752,6 +4752,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

@ -353,7 +353,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)
{
if (Validation::uuid($id)) {
$conditions = array('Object.uuid' => $id);
@ -532,7 +532,7 @@ class ObjectsController extends AppController
$this->request->data = array('Attribute' => $this->request->data);
}
$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
@ -595,6 +595,303 @@ 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->MispObject->id = $id;
if (!$this->MispObject->exists()) {
return $this->RestResponse->saveFailResponse('Objects', 'edit', false, 'Invalid object', $this->response->type());
}
$this->MispObject->recursive = -1;
$this->MispObject->contain('Event');
$object = $this->MispObject->read();
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', $this->response->type());
}
}
$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.');
}
}
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, $this->response->type(), 'nochange');
}
$object['Object'][$changedKey] = $changedField;
$changed = true;
}
if (!$changed) {
return $this->RestResponse->saveSuccessResponse('Objects', 'edit', $id, $this->response->type(), 'nochange');
}
$date = new DateTime();
$object['Object']['timestamp'] = $date->getTimestamp();
$this->MispObject->setObjectSeenMetaFromAttribute($object, true);
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;
$this->MispObject->Event->save($event, array('fieldList' => array('published', 'timestamp', 'info')));
$this->autoRender = false;
return $this->RestResponse->saveSuccessResponse('Objects', 'edit', $id, $this->response->type(), 'Field updated');
} else {
$this->autoRender = false;
return $this->RestResponse->saveFailResponse('Objects', 'edit', false, $this->MispObject->validationErrors, $this->response->type());
}
}
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.');
}
$this->MispObject->id = $id;
if (!$this->MispObject->exists()) {
throw new NotFoundException(__('Invalid object'));
}
$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.');
}
$this->MispObject->id = $id;
if (!$this->MispObject->exists()) {
throw new NotFoundException(__('Invalid object'));
}
$fields = array('id', 'distribution', 'event_id');
$fields[] = $field;
$params = array(
'conditions' => array('Object.id' => $id),
'fields' => $fields,
'flatten' => 1,
'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']);
$this->render('ajax/templateWithValidObjectAttributes');
} 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']);
$this->set('template', $template);
$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'], true);
}
}
public function delete($id, $hard = false)
{
if (!$this->userRole['perm_modify']) {

View File

@ -40,7 +40,7 @@
private function __get_event($id)
{
$this->__json['available_rotation_key'] = $this->__authorized_JSON_key;
$this->__json['available_pivot_key'] = $this->__authorized_JSON_key;
$fullevent = $this->__eventModel->fetchEvent($this->__user, array('eventid' => $id, 'flatten' => 0, 'includeTagRelations' => 1, 'extended' => $this->__extended_view));
$event = array();
@ -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'],
);
array_push($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'],
);
array_push($toPush_obj['Attribute'], $toPush_attr);
}
array_push($this->__json['items'], $toPush_obj);
}
return $this->__json;
}
}

View File

@ -79,6 +79,26 @@ class AppModel extends Model
);
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' => true, # 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
),
'testUpdate' => array(
'title' => 'Test Update',
'description' => 'Runs a test update',
'liveOff' => true,
'recommendBackup' => true,
'exitOnError' => true,
'preUpdate' => 'failingPreUpdate', # Function to execute before the update. If it returns false, cancel the update
'url' => '/servers/updateDatabase/testUpdate/'
)
);
public $actions_description = array(
'verifyGnuPGkeys' => array(
@ -1210,6 +1230,55 @@ 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,
DROP INDEX deleted,
;";
$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
;";
$sqlArray[] = "
ALTER TABLE `attributes`
ADD INDEX `uuid` (`uuid`),
ADD INDEX `event_id` (`event_id`),
ADD INDEX `sharing_group_id` (`sharing_group_id`),
ADD INDEX `type` (`type`),
ADD INDEX `category` (`category`),
ADD INDEX `value1` (`value1`(255)),
ADD INDEX `value2` (`value2`(255)),
ADD INDEX `object_id` (`object_id`),
ADD INDEX `object_relation` (`object_relation`),
ADD INDEX `deleted` (`deleted`),
ADD INDEX `first_seen` (`first_seen`),
ADD INDEX `last_seen` (`last_seen`),
ADD INDEX `comment` (`comment`(767))
;";
$sqlArray[] = "
ALTER TABLE `objects`
ADD `first_seen` BIGINT(20) NULL DEFAULT NULL,
ADD `last_seen` BIGINT(20) NULL DEFAULT NULL
;";
$indexArray[] = array('objects', 'first_seen');
$indexArray[] = array('objects', 'last_seen');
break;
case 'testUpdate':
$sqlArray[] = "SELECT SLEEP(10);";
$sqlArray[] = "SELECT SLEEPsdcfsac(4);";
$sqlArray[] = "SELECT SLEEP(12);";
break;
default:
return false;
break;
@ -1481,6 +1550,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)
{
$this->AdminSetting = ClassRegistry::init('AdminSetting');

View File

@ -360,7 +360,9 @@ class Attribute extends AppModel
'deleted',
'disable_correlation',
'object_id',
'object_relation'
'object_relation',
'first_seen',
'last_seen'
);
public $searchResponseTypes = array(
@ -508,6 +510,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')
)
);
@ -586,6 +598,22 @@ class Attribute extends AppModel
if (isset($v['Attribute']['object_relation']) && $v['Attribute']['object_relation'] === null) {
$results[$k]['Attribute']['object_relation'] = '';
}
if (!empty($v['Attribute']['first_seen'])) {
$fs = $results[$k]['Attribute']['first_seen'];
$fs_sec = $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;
$results[$k]['Attribute']['first_seen'] = DateTime::createFromFormat('U.u', $fs)->format('Y-m-d\TH:i:s.uP');
}
if (!empty($v['Attribute']['last_seen'])) {
$ls = $results[$k]['Attribute']['last_seen'];
$ls_sec = $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;
$results[$k]['Attribute']['last_seen'] = DateTime::createFromFormat('U.u', $ls)->format('Y-m-d\TH:i:s.uP');
}
}
return $results;
}
@ -610,6 +638,27 @@ class Attribute extends AppModel
}
}
// convert into utc and micro sec
if (!empty($this->data['Attribute']['first_seen'])) {
$d = new DateTime($this->data['Attribute']['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;
$this->data['Attribute']['first_seen'] = $fs;
}
if (!empty($this->data['Attribute']['last_seen'])) {
$d = new DateTime($this->data['Attribute']['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;
$this->data['Attribute']['last_seen'] = $ls;
}
// update correlation... (only needed here if there's an update)
if ($this->id || !empty($this->data['Attribute']['id'])) {
$this->__beforeSaveCorrelation($this->data['Attribute']);
@ -807,6 +856,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']);
@ -939,6 +998,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,
@ -3787,7 +3860,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];
@ -3857,7 +3930,9 @@ class Attribute extends AppModel
'comment',
'sharing_group_id',
'deleted',
'disable_correlation'
'disable_correlation',
'first_seen',
'last_seen'
);
if ($objectId) {
$fieldList[] = 'object_id';

View File

@ -1993,7 +1993,7 @@ 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');
$fieldsOrg = array('id', 'name', 'uuid');

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,51 @@ class MispObject extends AppModel
)
);
public function afterFind($results, $primary = false)
{
foreach ($results as $k => $v) {
if (!empty($v[$this->alias]['first_seen'])) {
$fs = $results[$k][$this->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;
$results[$k][$this->alias]['first_seen'] = DateTime::createFromFormat('U.u', $fs)->format('Y-m-d\TH:i:s.uP');
}
if (!empty($v[$this->alias]['last_seen'])) {
$ls = $results[$k][$this->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;
$results[$k][$this->alias]['last_seen'] = DateTime::createFromFormat('U.u', $ls)->format('Y-m-d\TH:i:s.uP');
}
}
return $results;
}
public function beforeSave($options = array()) {
// convert into utc and micro sec
if (!empty($this->data[$this->alias]['first_seen'])) {
$d = new DateTime($this->data[$this->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;
$this->data[$this->alias]['first_seen'] = $fs;
}
if (!empty($this->data[$this->alias]['last_seen'])) {
$d = new DateTime($this->data[$this->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;
$this->data[$this->alias]['last_seen'] = $ls;
}
}
public function beforeValidate($options = array())
{
parent::beforeValidate();
@ -75,6 +122,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;
}
@ -254,6 +309,27 @@ 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'],
'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
@ -493,7 +569,42 @@ class MispObject extends AppModel
return $attributes;
}
public function deltaMerge($object, $objectToSave)
// EUH????? No sure we should do that. Maybe the other way around??? #FIXME
// delete first/last-seen object attribute and set object's meta accordingly
// set object's meta (fs/ls) and potentially delete objectAttributes
public function setObjectSeenMetaFromAttribute($object, $delete=false) {
if ( !$delete && isset($object['Object']['first-seen']) && isset($object['Object']['last-seen'])) {
return;
}
if (isset($object['Attribute'])) {
foreach($object['Attribute'] as $i => $attribute) {
if ($attribute['object_relation'] == 'first-seen') {
// set object meta if unset
if (!isset($object['Object']['first-seen'])) {
$object['Object']['first-seen'] = $attribute['value'];
}
if ($delete) {
$object['Attribute'][$i]['deleted'] = 1;
$this->Event->Attribute->save($object['Attribute'][$i]);
}
continue;
}
if ($attribute['object_relation'] == 'last-seen') {
// set object meta if unset
if (!isset($object['Object']['last-seen'])) {
$object['Object']['last-seen'] = $attribute['value'];
}
if ($delete) {
$object['Attribute'][$i]['deleted'] = 1;
$this->Event->Attribute->save($object['Attribute'][$i]);
}
continue;
}
}
}
}
public function deltaMerge($object, $objectToSave, $onlyAddNewAttribute=false)
{
if (!isset($objectToSave['Object'])) {
$dataToBackup = array('ObjectReferences', 'Attribute', 'ShadowAttribute');
@ -512,67 +623,95 @@ 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();
if (isset($objectToSave['Object']['first_seen'])) {
$object['Object']['first_seen'] = $objectToSave['Object']['first_seen'];
}
if (isset($objectToSave['Object']['last_seen'])) {
$object['Object']['last_seen'] = $objectToSave['Object']['last_seen'];
}
$this->setObjectSeenMetaFromAttribute($object, true);
$this->save($object);
$checkFields = array('category', 'value', 'to_ids', 'distribution', 'sharing_group_id', 'comment', 'disable_correlation');
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');
foreach ($objectToSave['Attribute'] as $newKey => $newAttribute) {
foreach ($object['Attribute'] as $origKey => $originalAttribute) {
if (!empty($newAttribute['uuid'])) {
if ($newAttribute['uuid'] == $originalAttribute['uuid']) {
$different = false;
foreach ($checkFields as $f) {
if ($f == 'sharing_group_id' && empty($newAttribute[$f])) {
$newAttribute[$f] = 0;
}
if ($newAttribute[$f] != $originalAttribute[$f]) {
$different = true;
}
}
if ($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'
));
}
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'];
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);
}
} else { // we only add the new attribute
$newAttribute = $objectToSave['Attribute'][0];
if ($newAttribute != 'last-seen' && $newAttribute != 'first-seen') { // fs/ls should be added in the object's meta
$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->Attribute->saveAttributes(array($newAttribute));
}
$this->Event->Attribute->save($newAttribute);
$attributeArrays['add'][] = $newAttribute;
unset($objectToSave['Attribute'][$newKey]);
}
foreach ($object['Attribute'] as $origKey => $originalAttribute) {
$originalAttribute['deleted'] = 1;
$this->Event->Attribute->save($originalAttribute);
}
return $this->id;
}

View File

@ -1,7 +1,7 @@
<div class="attributes <?php if (!isset($ajax) || !$ajax) echo 'form';?>">
<?php
$url_params = $action == 'add' ? 'add/' . $event_id : 'edit/' . $attribute['Attribute']['id'];
echo $this->Form->create('Attribute', array('id', 'url' => '/attributes/' . $url_params));
echo $this->Form->create('Attribute', array('id'=>'AttributeForm', 'url' => '/attributes/' . $url_params));
?>
<fieldset>
<legend><?php echo $action == 'add' ? __('Add Attribute') : __('Edit Attribute'); ?></legend>
@ -78,6 +78,7 @@
echo $this->Form->input('batch_import', array(
'type' => 'checkbox'
));
echo $this->element('form_seen_input');
echo '<div class="input clear"></div>';
echo $this->Form->input('disable_correlation', array(
'type' => 'checkbox'

View File

@ -0,0 +1,14 @@
<?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,14 @@
<?php
echo $this->Form->create('Attribute', array('id' => 'Attribute' . '_' . $object['id'] . '_last_seen_form', 'url' => '/attributes/editField/' . $object['id']));
?>
<?php
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

@ -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']) {
@ -85,8 +85,11 @@
}
?>
</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>
@ -147,5 +150,6 @@
'child' => $attrKey == $lastElement ? 'last' : true
));
}
echo '<tr><td class="fa fa-plus-circle objectAddField" title="' . __('Add an Object Attribute') .'" onclick="quickFetchValidObjectAttribute(' . h($object['id']) . ')"></td></tr>';
}
?>

View File

@ -0,0 +1,444 @@
<?php echo $this->Html->script('moment-with-locales'); ?>
<div class="input-group">
<?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>
<div class="input clear"></div>
<script>
var controller = "<?php echo(substr(ucfirst($this->params->controller), 0, -1)); ?>"; // get current controller name fo that we can access all form fields
var time_vals = [
['Hour', 23, 1000*1000*60*60],
['Minute', 59, 1000*1000*60],
['Second', 59, 1000*60],
['ms', 999, 1000],
['us', 999, 1],
];
class MicroDatetime {
constructor(value) {
this.isoDatetimeMicroRegex = /(\d{4})-(\d{2})-(\d{2})T(\d{2})\:(\d{2})\:(\d{2})\.(\d{3})(\d*)(\D\S*)/g;
this.isotimeMicroRegex = /(\d{2})\:(\d{2})\:(\d{2})\.(\d{3})(\d*)(\D\S*)/g;
this.numberRegex = /^(\-|\+)?([0-9]+|Infinity)$/g;
if (value === undefined || value === "") {
this.moment = undefined;
this.micro = 0;
} else {
// check if timestamp UNIX timestamp (in sec)
if (this.numberRegex.test(value)) {
var timestamp = parseInt(value);
var timestamp_str = String(timestamp);
if (timestamp === '' || timestamp === undefined || timestamp === null || isNaN(timestamp)) {
this.moment = moment(0);
this.micro = 0;
} else {
var all_milli = parseInt(timestamp)*1000;
all_milli = isNaN(all_milli) || all_milli === undefined ? 0 : all_milli;
this.moment = moment(all_milli);
this.micro = 0;
}
// check if only a time
} else { // let moment parse the date
try {
value = String(value);
this.moment = new moment(value);
if (this.moment.isValid()) {
var res = this.isoDatetimeMicroRegex.exec(value);
var micro_str = res !== null ? res[8] : 0;
this.micro = parseInt(micro_str);
this.micro = isNaN(this.micro) ? 0 : this.micro;
} else {
this.moment = undefined;
this.micro = 0;
showMessage('fail', 'Failed to parse the date: <strong>' + value + '</strong>');
}
} catch (err) {
if (this.isotimeMicroRegex.test(value)) {
this.moment = moment(value, "HH:mm:ss.SSSSSSZ");
if (this.moment.isValid()) {
var res = this.isotimeMicroRegex.exec(value);
var micro_str = res !== null ? res[8] : 0;
this.micro = parseInt(micro_str);
this.micro = isNaN(this.micro) ? 0 : this.micro;
} else {
this.moment = undefined;
this.micro = 0;
showMessage('fail', 'Failed to parse the date: ' + value);
}
}
}
}
}
}
get_microISO() {
if (this.moment === undefined || this.moment === null) {
return "";
}
var tz = this.moment.format('Z');
var str = this.moment.toISOString(true);
str = str.replace(tz, pad_zero(this.micro, 3)+tz);
return str;
}
get_date() {
if (this.moment === undefined || this.moment === null) {
return "";
}
return this.moment.format('YYYY/MM/DD');
}
get_time() {
if (this.moment === undefined || this.moment === null) {
return "";
}
return this.moment.format('HH:mm:ss.SSS')
+ String(pad_zero(this.micro, 3))
+ this.moment.format('Z');
}
has_date() {
return this.moment !== undefined;
}
has_time() {
if (this.moment === undefined) {
return false;
} else {
return this.moment.get('hour') != 0
|| this.moment.get('minute') != 0
|| this.moment.get('second') != 0
|| this.moment.get('millisecond') != 0
|| this.micro != 0;
}
}
}
function pad_zero(val, pad) {
var ret = '';
for (var i=0; i<(pad-String(val).length); i++) {
ret += '0';
}
ret += String(val);
return ret;
}
function get_slider_and_input(type, scale, factor, max) {
var row = $('<tr></tr>');
var td1 = $('<td></td>');
var td2 = $('<td></td>');
var td3 = $('<td></td>');
var label = $('<span>'+scale+'</span>').css({fontWeight: 'bold'});
var input = $('<input id="input-time-'+type+'-'+scale+'" type="number" min=0 max='+max+' step=1 value=0 factor='+factor+' scale='+scale+'></input>').css({width: '50px', margin: '5px'});
var slider_width = '200px';
var slider = $('<input id="slider-time-'+type+'-'+scale+'" type="range" min=0 max='+max+' step=1 value=0 factor='+factor+' scale='+scale+'></input>').css({width: slider_width, margin: '5px'});
row.append(td1.append(label))
.append(td2.append(input))
.append(td3.append(slider))
return row;
}
function reflect_change_on_sliders(seen, skip_input_update, overwrite) {
if (seen == 'both' || seen == 'first') {
var f_val = overwrite === undefined ? $('#'+controller+'FirstSeen').val() : overwrite;
var f_microdatetime = new MicroDatetime(f_val);
var hours = f_microdatetime.moment !== undefined ? f_microdatetime.moment.hours() : 0;
var minutes = f_microdatetime.moment !== undefined ? f_microdatetime.moment.minutes() : 0;
var seconds = f_microdatetime.moment !== undefined ? f_microdatetime.moment.seconds() : 0;
var milli = f_microdatetime.moment !== undefined ? f_microdatetime.moment.milliseconds() : 0;
var d = f_microdatetime.moment !== undefined ? f_microdatetime.get_date() : undefined;
// mirror slider and input field
$('#input-time-first-'+time_vals[0]).val(hours);
$('#slider-time-first-'+time_vals[0]).val(hours);
$('#input-time-first-'+time_vals[1]).val(minutes);
$('#slider-time-first-'+time_vals[1]).val(minutes);
$('#input-time-first-'+time_vals[2]).val(seconds);
$('#slider-time-first-'+time_vals[2]).val(seconds);
$('#input-time-first-'+time_vals[3]).val(milli);
$('#slider-time-first-'+time_vals[3]).val(milli);
$('#input-time-first-'+time_vals[4]).val(f_microdatetime.micro);
$('#slider-time-first-'+time_vals[4]).val(f_microdatetime.micro);
$('#date_fs').datepicker('setDate', d);
$('#time_fs').val(f_microdatetime.get_time());
}
if (seen == 'both' || seen == 'last') {
var l_val = overwrite === undefined ? $('#'+controller+'LastSeen').val() : overwrite;
var l_microdatetime = new MicroDatetime(l_val);
var hours = l_microdatetime.moment !== undefined ? l_microdatetime.moment.hours() : 0;
var minutes = l_microdatetime.moment !== undefined ? l_microdatetime.moment.minutes() : 0;
var seconds = l_microdatetime.moment !== undefined ? l_microdatetime.moment.seconds() : 0;
var milli = l_microdatetime.moment !== undefined ? l_microdatetime.moment.milliseconds() : 0;
var d = l_microdatetime.moment !== undefined ? l_microdatetime.get_date() : undefined;
// mirror slider and input field
$('#input-time-last-'+time_vals[0]).val(hours);
$('#slider-time-last-'+time_vals[0]).val(hours);
$('#input-time-last-'+time_vals[1]).val(minutes);
$('#slider-time-last-'+time_vals[1]).val(minutes);
$('#input-time-last-'+time_vals[2]).val(seconds);
$('#slider-time-last-'+time_vals[2]).val(seconds);
$('#input-time-last-'+time_vals[3]).val(milli);
$('#slider-time-last-'+time_vals[3]).val(milli);
$('#input-time-last-'+time_vals[4]).val(l_microdatetime.micro);
$('#slider-time-last-'+time_vals[4]).val(l_microdatetime.micro);
$('#date_ls').datepicker('setDate', d);
$('#time_ls').val(l_microdatetime.get_time());
}
if (!skip_input_update) {
reflect_change_on_input(seen);
}
}
// get data stored in sliders
function get_time_from_slider(which) {
var micro = 0;
var mom;
var dp = which == 'first' ? $('#date_fs') : $('#date_ls');
var timej = which == 'first' ? $('#time_fs') : $('#time_ls');
if (dp.val() != "") { // no time without a date
mom = dp.datepicker('getDate');
mom = mom === null ? moment(0) : moment(mom.toISOString());
if (timej.val() != "") {
$('#precision_tool_'+which).find('input[type="number"]').each(function() {
switch($(this).attr('scale')) {
case 'us':
micro = parseInt($(this).val());
break;
case 'ms':
mom.set('ms', parseInt($(this).val()));
break;
case 'Second':
mom.set('s', parseInt($(this).val()));
break;
case 'Minute':
mom.set('m', parseInt($(this).val()));
break;
case 'Hour':
mom.set('h', parseInt($(this).val()));
break;
}
});
} else { // no time, setting it UTC noon
micro = 0;
mom.set('h', 0);
}
}
microdatetime = new MicroDatetime();
microdatetime.moment = mom;
microdatetime.micro = micro;
return microdatetime;
}
function reflect_change_on_input(seen, full) {
if ($('#seen_precision_tool').prop('checked')) {
if (seen == 'both' || seen == 'first') {
var microdatetime = get_time_from_slider('first');
if($('#date_fs').val() !== '') {
$("#time_fs").val(microdatetime.get_time());
}
}
if (seen == 'both' || seen == 'last') {
var microdatetime = get_time_from_slider('last');
if($('#date_ls').val() !== '') {
$("#time_ls").val(microdatetime.get_time());
}
}
}
}
function reflect_change_on_form() {
var microdatetime = get_time_from_slider('first');
if (microdatetime.moment !== undefined) {
$('#'+controller+'FirstSeen').val(microdatetime.get_microISO());
} else {
$('#'+controller+'FirstSeen').val("");
}
var microdatetime = get_time_from_slider('last');
if (microdatetime.moment !== undefined) {
$('#'+controller+'LastSeen').val(microdatetime.get_microISO());
} else {
$('#'+controller+'LastSeen').val("");
}
}
$(document).ready(function() {
var sliders_container = "<?php if ($this->params->controller === 'attributes') { echo 'fieldset'; } else { echo '#meta-div'; } ?>";
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="fa 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="fa 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="fa fa-clock-o label-icon"></span>'; ?><input id="time_fs" type="text" style="width: 240px; text-align: center;"></input></label>')
);
$(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="fa fa-clock-o label-icon"></span>'; ?><input id="time_ls" type="text" style="width: 240px; text-align: center;"></input></label>')
);
$(sliders_container).append(time_div_ls);
// create checkbox
var div_checkbox_prec_tool = $('<div class="clear checkbox" style="margin-left: 10px;"></div>').append(
$('<label style="display: inline-block"><input id="seen_precision_tool" type="checkbox" style="margin-top: 0px;"></input><?php echo(__('Enable precision tool'))?><span class="fa fa-bullseye label-icon"</span></label>')
);
$(sliders_container).append(div_checkbox_prec_tool);
// create sliders
var div = $('<div id="precision_tool" class="precision-tool clear" style="display: none"></div>');
var content = $('<table id="precision_tool_first" style="float: left;"></table>');
for (var i=0; i<time_vals.length; i++) {
var type = time_vals[i][0];
var max = time_vals[i][1];
var factor = time_vals[i][2];
var row = get_slider_and_input('first', type, factor, max)
content.append(row);
}
div.append(content);
var content = $('<table id="precision_tool_last" style="float:left; margin-left: 15px;"></table>');
for (var i=0; i<time_vals.length; i++) {
var type = time_vals[i][0];
var max = time_vals[i][1];
var factor = time_vals[i][2];
var row = get_slider_and_input('last', type, factor, max)
content.append(row);
}
div.append(content);
$(sliders_container).append(div);
time_vals.forEach(function(elem) {
$('#input-time-first-'+elem[0]).on('input', function(e) {
$('#slider-time-first-'+elem[0]).val($(this).val());
reflect_change_on_input('first');
});
$('#slider-time-first-'+elem[0]).on('input', function(e) {
$('#input-time-first-'+elem[0]).val($(this).val());
reflect_change_on_input('first');
});
$('#input-time-last-'+elem[0]).on('input', function(e) {
$('#slider-time-last-'+elem[0]).val($(this).val());
reflect_change_on_input('last');
});
$('#slider-time-last-'+elem[0]).on('input', function(e) {
$('#input-time-last-'+elem[0]).val($(this).val());
reflect_change_on_input('last');
});
});
$('#seen_precision_tool').change(function(e) {
if(e.target.checked) {
$('#precision_tool').show();
} else {
$('#precision_tool').hide();
}
});
$('#time_fs').on("focus", function(event) {
$('#seen_precision_tool').prop('checked', true);
$('#precision_tool').show();
});
$('#time_ls').on("focus", function(event) {
$('#seen_precision_tool').prop('checked', true);
$('#precision_tool').show();
});
$('#time_fs').on("input", function(event) {
reflect_change_on_sliders('first', false, $(this).val());
});
$('#time_ls').on("input", function(event) {
reflect_change_on_sliders('last', false, $(this).val());
});
$('#time_fs').on("paste", function(event) {
// prefetch clipboard text and apply change
var datetimeString;
if (event.originalEvent.clipboardData && event.originalEvent.clipboardData.types
&& $.inArray('text/plain', event.originalEvent.clipboardData.types) !== -1) {
datetimeString = event.originalEvent.clipboardData.getData('text/plain');
}
else if (window.clipboardData) {
datetimeString = window.clipboardData.getData('Text');
}
$('#'+controller+'FirstSeen').val(datetimeString);
reflect_change_on_sliders('first', false);
event.preventDefault();
});
$('#time_ls').on("paste", function(event) {
// prefetch clipboard text and apply change
var datetimeString;
if (event.originalEvent.clipboardData && event.originalEvent.clipboardData.types
&& $.inArray('text/plain', event.originalEvent.clipboardData.types) !== -1) {
datetimeString = event.originalEvent.clipboardData.getData('text/plain');
}
else if (window.clipboardData) {
datetimeString = window.clipboardData.getData('Text');
}
$('#'+controller+'LastSeen').val(datetimeString);
reflect_change_on_sliders('last', false);
event.preventDefault();
});
$('#date_fs').closest('form').submit(function( event ) {
reflect_change_on_form();
});
var d1 = new MicroDatetime($('#'+controller+'FirstSeen').val());
var d2 = new MicroDatetime($('#'+controller+'LastSeen').val());
if (d1.has_date() || d2.has_date()) {
$('#date_fs').val(d1.get_date());
$('#date_ls').val(d2.get_date());
}
if (d1.has_time() || d2.has_time()) {
$('#seen_precision_tool').prop('checked', true);
$('#precision_tool').show();
$('#time_fs').val(d1.get_time());
$('#time_ls').val(d2.get_time());
}
$('.input-daterange').datepicker({
preventMultipleSet: true,
format: 'yyyy/mm/dd',
todayHighlight: true
})
reflect_change_on_sliders('both', true);
});
</script>

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>
<input type="text" id="timeline-typeahead" class="center-in-network-header network-typeahead flushright" data-provide="typeahead" size="20" placeholder="Search for an item">
</div>
<div id="event_timeline" 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

@ -467,6 +467,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>
@ -492,6 +495,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,9 @@
));
?>
</dd>
<?php
echo $this->element('form_seen_input');
?>
</dl>
</div>
<?php

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,156 @@
<?php
$url = '/objects/quickAddAttributeForm/' . $object['id'];
$element = $template['ObjectTemplateElement'][0];
$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="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'],
));
?>
<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>
<?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']); ?>)"><?php echo __('Submit'); ?></span>
<?php else:
echo $this->Form->button('Submit', array('class' => 'btn btn-primary'));
endif;
?>
<span class="btn btn-inverse" style="float:right;" title="<?php echo __('Cancel'); ?>" role="button" tabindex="0" aria-label="<?php echo __('Cancel'); ?>" id="cancel_attribute_add"><?php echo __('Cancel'); ?></span>
</div>
<?php
echo $this->Form->end();
?>
<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();
});
$('#cancel_attribute_add').click(function() {
cancelPopoverForm();
});
});
</script>

View File

@ -0,0 +1,22 @@
<div class="popover_choice">
<legend><?php echo __('Select Object Attribute To Add');?></legend>
<div class="popover_choice_main" id ="popover_choice_main">
<table style="width:100%;">
<?php foreach ($template['ObjectTemplateElement'] as $objectAttribute): ?>
<tr style="border-bottom:1px solid black;" class="templateChoiceButton">
<td role="button" tabindex="0" aria-label="<?php echo h($objectAttribute['object_relation']); ?>" title="<?php echo h($objectAttribute['object_relation']); ?>" style="padding-left:10px;padding-right:10px; text-align:center;width:100%;" onClick="fetchAddObjectAttributeForm(<?php echo h($objectId) . ', \'' . h($objectAttribute['object_relation']) . '\''; ?>)"><?php echo '<strong>' . h($objectAttribute['object_relation']) . '</strong>' . ' :: ' . h($objectAttribute['type']) . '<br>' . h($objectAttribute['description']); ?></td>
</tr>
<?php endforeach; ?>
</table>
</div>
<div role="button" tabindex="0" aria-label="<?php echo __('Cancel');?>" title="<?php echo __('Cancel');?>" class="templateChoiceButton templateChoiceButtonLast" onClick="cancelPopoverForm();"><?php echo __('Cancel');?></div>
</div>
<script type="text/javascript">
$(document).ready(function() {
resizePopoverBody();
});
$(window).resize(function() {
resizePopoverBody();
});
</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

@ -0,0 +1,150 @@
.info_container_eventtimeline {
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

@ -21,6 +21,10 @@ button .full-width {
margin-bottom:10px;
}
form button.btn[disabled] {
cursor: not-allowed;
}
/*.close{
float:none;
}*/
@ -289,6 +293,26 @@ td.highlight3 {
}
table-striped tbody > tr > td.objectAddField {
padding: 2px 3px;
line-height: normal;
display: inline-block;
position: relative;
top: -13px;
left: -4px;
color: white;
background-color: #1369a0;
border: 0px;
border-radius: 15px;
height: 12px;
width: 10px;
}
.table-striped tbody > tr > td.objectAddField:hover {
transform: scale(1.7);
cursor: pointer;
}
tr.highlightBlueSides {
border-left:2px solid #0088cc;
border-right:2px solid #0088cc;
@ -836,6 +860,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;
}
.ajax_popover_form {
display:none;
width: 700px;
@ -2219,6 +2257,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

@ -155,7 +155,7 @@ class EventGraph {
$("#select_graph_scope").val(value);
}
if (value == "Rotation key") {
if (value == "Pivot key") {
$("#network-scope-badge").text(value + ": " + eventGraph.scope_keyType);
} else {
$("#network-scope-badge").text(value);
@ -176,30 +176,30 @@ class EventGraph {
label: "Scope",
tooltip: "The scope represented by the network",
event: function(value) {
if (value == "Rotation key" && $('#input_graph_scope_jsonkey').val() == "") { // no key selected for Rotation key scope
if (value == "Pivot key" && $('#input_graph_scope_jsonkey').val() == "") { // no key selected for Pivot key scope
return;
} else {
eventGraph.update_scope(value);
dataHandler.fetch_data_and_update();
}
},
options: ["Reference", "Tag", "Rotation key"],
options: ["Reference", "Tag", "Pivot key"],
default: "Reference"
});
menu_scope.add_select({
id: "input_graph_scope_jsonkey",
label: "Rotation key",
label: "Pivot key",
tooltip: "The key around which the network will be constructed",
event: function(value) {
if (value == "Rotation key" && $('#input_graph_scope_jsonkey').val() == "") { // no key selected for Rotation key scope
if (value == "Pivot key" && $('#input_graph_scope_jsonkey').val() == "") { // no key selected for Pivot key scope
return;
} else {
eventGraph.scope_keyType = value;
eventGraph.update_scope("Rotation key");
eventGraph.update_scope("Pivot key");
dataHandler.fetch_data_and_update();
}
},
options: dataHandler.available_rotation_key ? dataHandler.available_rotation_key : [],
options: dataHandler.available_pivot_key ? dataHandler.available_pivot_key : [],
default: ""
});
return menu_scope;
@ -298,7 +298,8 @@ class EventGraph {
for(var nodeId of objectIds) {
eventGraph.expand_node(nodeId);
}
}
},
title: "Expanding all nodes may takes some time"
});
menu_display.add_button({
label: "Collapse all nodes",
@ -310,7 +311,8 @@ class EventGraph {
for(var nodeId of objectIds) {
eventGraph.collapse_node(nodeId);
}
}
},
title: "Collapsing all nodes may takes some time"
});
menu_display.add_slider({
id: 'slider_display_max_char_num',
@ -1427,8 +1429,8 @@ class DataHandler {
return [[index, value]];
});
dataHandler.update_filtering_selectors(available_object_references, available_tags);
dataHandler.available_rotation_key = data.available_rotation_key;
eventGraph.menu_scope.add_options("input_graph_scope_jsonkey", dataHandler.available_rotation_key);
dataHandler.available_pivot_key = data.available_pivot_key;
eventGraph.menu_scope.add_options("input_graph_scope_jsonkey", dataHandler.available_pivot_key);
if (data.items.length < nodes_ask_threshold) {
eventGraph.update_graph(data);
} else if (data.items.length > nodes_ask_threshold && confirm("The network contains a lot of nodes, displaying it may slow down your browser. Continue?")) {

View File

@ -0,0 +1,586 @@
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, undefined);
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() { reflect_change(true); });
}
} else {
update_seen(item, 'first', newStart, !c2, undefined);
}
}
if (c2) {
update_seen(item, 'last', newEnd, true, undefined);
}
}
};
var timeline_typeaheadDataSearch;
var timeline_typeaheadOption = {
source: function (query, process) {
if (timeline_typeaheadDataSearch === undefined) { // caching
timeline_typeaheadDataSearch = Array.from(mapping_text_to_id.keys());
}
process(timeline_typeaheadDataSearch);
},
updater: function(value) {
var id = mapping_text_to_id.get(value);
eventTimeline.focus(id);
$("#timeline-typeahead").blur();
},
autoSelect: true
}
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 = eventTimeline.timeAxis.step.scale;
var momAhead = mom.clone();
momAhead.add(1, scale);
return momAhead;
}
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=\"Object is (or can be) 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 reflect_change(onIndex, itemType, itemId) {
if (onIndex) {
updateIndex(scope_id, 'event'); // MISP function
} else { // reflect change on item only
quick_fetch_seens(itemType, itemId, 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;
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.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) {
reflect_change(false, itemType, item.id);
}
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) {
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();
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;
set_spanned_time(item);
if (item.group == 'object') {
for (var attr of item.Attribute) {
mapping_text_to_id.set(attr.contentType+': '+attr.content+' ('+item.id+')', item.id);
adjust_text_length(attr);
}
} else {
mapping_text_to_id.set(item.content+' ('+item.id+')', item.id);
adjust_text_length(item);
}
}
items_timeline.add(data.items);
handle_not_seen_enabled($('#checkbox_timeline_display_hide_not_seen_enabled').val())
},
error: function( jqXhr, textStatus, errorThrown ){
console.log( errorThrown );
},
complete: function() {
$(".loadingTimeline").hide();
}
});
}
function enable_timeline() {
if (eventTimeline !== undefined) {
return;
}
init_popover();
$('#timeline-typeahead').typeahead(timeline_typeaheadOption);
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;
set_spanned_time(item);
if (item.group == 'object') {
for (var attr of item.Attribute) {
mapping_text_to_id.set(attr.contentType+': '+attr.content+' ('+item.id+')', item.id);
adjust_text_length(attr);
}
} else {
mapping_text_to_id.set(item.content+' ('+item.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
});
});
},
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 group = items_timeline.get(id).group;
if (group == 'attribute') {
simplePopup('/attributes/edit/'+id);
} else if (group == 'object') {
window.location = '/objects/edit/'+id;
}
}
function handle_doubleClick(data) {
// should be replaced by keyboard shortcut: SHIFT+E ?
//edit_item(data.item);
}
function handle_not_seen_enabled(hide) {
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 {
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

@ -409,6 +409,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,
});
@ -437,15 +442,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();
@ -583,6 +615,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(),
@ -723,7 +757,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();
@ -734,12 +769,75 @@ 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();
}
}
function quickFetchValidObjectAttribute(objectId) {
var itemType = "objects";
var formUrl= "quickFetchTemplateWithValidObjectAttributes";
var compiledUrlForm = "/" + itemType + "/" + formUrl + "/" + objectId;
$.ajax({
beforeSend: function (XMLHttpRequest) {
$(".loading").show();
},
success:function (data, textStatus) {
if (data.fail !== undefined && data.fail) {
showMessage('fail', data.errors);
} else {
$('#popover_form').html(data);
openPopup('#popover_form');
}
},
error:function() {
showMessage('fail', 'Could not fetch allowed attribute type.');
},
complete:function() {
$(".loading").hide();
},
type: "get",
url: compiledUrlForm
});
return false;
}
function fetchAddObjectAttributeForm(objectId, fieldName) {
var itemType = "objects";
var formUrl= "quickAddAttributeForm";
var compiledUrlForm = "/" + itemType + "/" + formUrl + "/" + objectId + "/" + fieldName;
$.ajax({
beforeSend: function (XMLHttpRequest) {
$(".loading").show();
},
success:function (data, textStatus) {
$('#popover_form').html(data);
openPopup('#popover_form');
},
error:function() {
showMessage('fail', 'Could not fetch allowed attribute type.');
},
complete:function() {
$(".loading").hide();
},
type: "get",
url: compiledUrlForm
});
return false;
}
function handleGenericAjaxResponse(data, skip_reload) {
if (typeof skip_reload === "undefined") {
skip_reload = false;
@ -1144,6 +1242,9 @@ function submitPopoverForm(context_id, referer, update_context_id) {
case 'addObjectReference':
url = "/objectReferences/add/" + context_id;
break;
case 'quickAddAttributeForm':
url = "/objects/quickAddAttributeForm/" + context_id;
break;
}
if (url !== null) {
$.ajax({