mirror of https://github.com/MISP/MISP
chg: [decaying:simulation] Support of sightings in the decaying simulation
parent
9585c9118d
commit
bbab646d01
|
@ -351,24 +351,9 @@ class DecayingModelController extends AppController
|
|||
}
|
||||
}
|
||||
|
||||
public function decayingToolChartSimulation($model_id, $attribute_id)
|
||||
{
|
||||
// $model = $this->DecayingModel->checkAuthorisation($this->Auth->user(), $model_id);
|
||||
// if ($model === false) {
|
||||
// throw new NotFoundException(_('No Decaying Model with the provided ID exists, or you are not authorised to edit it.'));
|
||||
// }
|
||||
// $attribute = $this->User->Event->Attribute->fetchAttributesSimple($this->Auth->user(), array('conditions' => array('id' => $attribute_id)));
|
||||
// if(empty($attribute)) {
|
||||
// throw new NotFoundException(__('Invalid attribute id.'));
|
||||
// } else {
|
||||
// $attribute = $attribute[0];
|
||||
// }
|
||||
// $this->render('chart_simulation');
|
||||
}
|
||||
|
||||
public function decayingToolComputeSimulation($model_id, $attribute_id)
|
||||
{
|
||||
return $this->RestResponse->viewData('date,value' . PHP_EOL . '2017-03-12,20' . PHP_EOL . '2018-03-12,10', 'csv');
|
||||
|
||||
$score_overtime = $this->RestResponse->viewData($this->DecayingModel->getScoreOvertime($this->Auth->user(), $model_id, $attribute_id), $this->response->type());
|
||||
return $score_overtime;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -312,80 +312,8 @@ class SightingsController extends AppController
|
|||
|
||||
public function listSightings($id, $context = 'attribute', $org_id = false)
|
||||
{
|
||||
$this->loadModel('Event');
|
||||
$rawId = $id;
|
||||
$id = $this->Sighting->explodeIdList($id);
|
||||
if ($context === 'attribute') {
|
||||
$object = $this->Event->Attribute->fetchAttributes($this->Auth->user(), array('conditions' => array('Attribute.id' => $id, 'Attribute.deleted' => 0), 'flatten' => 1));
|
||||
} else {
|
||||
// let's set the context to event here, since we reuse the variable later on for some additional lookups.
|
||||
// Passing $context = 'org' could have interesting results otherwise...
|
||||
$context = 'event';
|
||||
$object = $this->Event->fetchEvent($this->Auth->user(), $options = array('eventid' => $id, 'metadata' => true));
|
||||
}
|
||||
if (empty($object)) {
|
||||
throw new MethodNotAllowedException('Invalid object.');
|
||||
}
|
||||
$conditions = array(
|
||||
'Sighting.' . $context . '_id' => $id
|
||||
);
|
||||
if ($org_id) {
|
||||
$conditions[] = array('Sighting.org_id' => $org_id);
|
||||
}
|
||||
$sightings = $this->Sighting->find('all', array(
|
||||
'conditions' => $conditions,
|
||||
'recursive' => -1,
|
||||
'contain' => array('Organisation.name'),
|
||||
'order' => array('Sighting.date_sighting DESC')
|
||||
));
|
||||
if (!empty($sightings) && empty(Configure::read('Plugin.Sightings_policy')) && !$this->_isSiteAdmin()) {
|
||||
$eventOwnerOrgIdList = array();
|
||||
foreach ($sightings as $k => $sighting) {
|
||||
if (empty($eventOwnerOrgIdList[$sighting['Sighting']['event_id']])) {
|
||||
$temp_event = $this->Event->find('first', array(
|
||||
'recursive' => -1,
|
||||
'conditions' => array('Event.id' => $sighting['Sighting']['event_id']),
|
||||
'fields' => array('Event.id', 'Event.orgc_id')
|
||||
));
|
||||
$eventOwnerOrgIdList[$temp_event['Event']['id']] = $temp_event['Event']['orgc_id'];
|
||||
}
|
||||
if (empty($eventOwnerOrgIdList[$sighting['Sighting']['event_id']]) || $eventOwnerOrgIdList[$sighting['Sighting']['event_id']] !== $this->Auth->user('org_id')) {
|
||||
unset($sightings[$k]);
|
||||
}
|
||||
}
|
||||
$sightings = array_values($sightings);
|
||||
} else if (!empty($sightings) && Configure::read('Plugin.Sightings_policy') == 1 && !$this->_isSiteAdmin()) {
|
||||
$eventsWithOwnSightings = array();
|
||||
foreach ($sightings as $k => $sighting) {
|
||||
if (empty($eventsWithOwnSightings[$sighting['Sighting']['event_id']])) {
|
||||
$eventsWithOwnSightings[$sighting['Sighting']['event_id']] = false;
|
||||
$sighting_temp = $this->Sighting->find('first', array(
|
||||
'recursive' => -1,
|
||||
'conditions' => array(
|
||||
'Sighting.event_id' => $sighting['Sighting']['event_id'],
|
||||
'Sighting.org_id' => $this->Auth->user('org_id')
|
||||
)
|
||||
));
|
||||
if (empty($sighting_temp)) {
|
||||
$temp_event = $this->Event->find('first', array(
|
||||
'recursive' => -1,
|
||||
'conditions' => array(
|
||||
'Event.id' => $sighting['Sighting']['event_id'],
|
||||
'Event.orgc_id' => $this->Auth->user('org_id')
|
||||
),
|
||||
'fields' => array('Event.id', 'Event.orgc_id')
|
||||
));
|
||||
$eventsWithOwnSightings[$sighting['Sighting']['event_id']] = !empty($temp_event);
|
||||
} else {
|
||||
$eventsWithOwnSightings[$sighting['Sighting']['event_id']] = true;
|
||||
}
|
||||
}
|
||||
if (!$eventsWithOwnSightings[$sighting['Sighting']['event_id']]) {
|
||||
unset($sightings[$k]);
|
||||
}
|
||||
}
|
||||
$sightings = array_values($sightings);
|
||||
}
|
||||
$sightings = $this->Sighting->listSightings($this->Auth->user(), $id, $context, $org_id);
|
||||
$this->set('org_id', $org_id);
|
||||
$this->set('rawId', $rawId);
|
||||
$this->set('context', $context);
|
||||
|
|
|
@ -234,4 +234,105 @@ class DecayingModel extends AppModel
|
|||
);
|
||||
}
|
||||
|
||||
// compute the current score for the provided atribute with the provided model
|
||||
public function computeCurrentScore($model, $attribute)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
// compute the score at the given timestamp for the provided atribute with the provided model
|
||||
public function computeScore($model, $attribute, $timestamp, $last_sighting_timestamp)
|
||||
{
|
||||
$t = $timestamp - $last_sighting_timestamp;
|
||||
if ($t < 0) {
|
||||
return 0;
|
||||
}
|
||||
$base_score = 100;
|
||||
$delta = $model['DecayingModel']['parameters']['delta'];
|
||||
$tau = $model['DecayingModel']['parameters']['tau']*24*60*60;
|
||||
$score = $base_score * (1 - pow($t / $tau, 1 / $delta));
|
||||
// debug($timestamp);
|
||||
// debug($last_sighting_timestamp);
|
||||
// debug($tau);
|
||||
// debug($t);
|
||||
// debug($score);
|
||||
return $score < 0 ? 0 : $score;
|
||||
}
|
||||
|
||||
// returns timestamp set to the rounded hour
|
||||
public function round_timestamp_to_hour($time)
|
||||
{
|
||||
$offset = $time % 3600;
|
||||
return $time - $offset;
|
||||
}
|
||||
|
||||
public function getScoreOvertime($user, $model_id, $attribute_id)
|
||||
{
|
||||
$this->Attribute = ClassRegistry::init('Attribute');
|
||||
$attribute = $this->Attribute->fetchAttributesSimple($user, array(
|
||||
'conditions' => array('id' => $attribute_id)
|
||||
));
|
||||
if (empty($attribute)) {
|
||||
throw new NotFoundException(__('Attribute not found'));
|
||||
} else {
|
||||
$attribute = $attribute[0];
|
||||
}
|
||||
$model = $this->checkAuthorisation($user, $model_id, true);
|
||||
if ($model === false) {
|
||||
throw new NotFoundException(__('Model not found'));
|
||||
}
|
||||
$this->Sighting = ClassRegistry::init('Sighting');
|
||||
$sightings = $this->Sighting->listSightings($user, $attribute_id, 'attribute', false, 0, false);
|
||||
if (empty($sightings)) {
|
||||
$sightings = array(array('Sighting' => array('date_sighting' => $attribute['Attribute']['timestamp']))); // simulate a sighting nonetheless
|
||||
}
|
||||
// get start time
|
||||
$start_time = $attribute['Attribute']['timestamp'];
|
||||
// $start_time = $attribute['Attribute']['first_seen'] < $start_time ? $attribute['Attribute']['first_seen'] : $start_time;
|
||||
$start_time = $sightings[0]['Sighting']['date_sighting'] < $start_time ? $sightings[0]['Sighting']['date_sighting'] : $start_time;
|
||||
$start_time = intval($start_time);
|
||||
$start_time = $this->round_timestamp_to_hour($start_time);
|
||||
// get end time
|
||||
$end_time = $sightings[count($sightings)-1]['Sighting']['date_sighting'] + $model['DecayingModel']['parameters']['tau']*24*60*60;
|
||||
$end_time = $this->round_timestamp_to_hour($end_time);
|
||||
|
||||
// generate time span from oldest timestamp to last decay, resolution is hours
|
||||
$score_overtime = array();
|
||||
$rounded_sightings = array();
|
||||
$sighting_index = 0;
|
||||
for ($t=$start_time; $t < $end_time; $t+=3600) {
|
||||
// fetch closest sighting to the current time
|
||||
$sighting_index = $this->get_closest_sighting($sightings, $t, $sighting_index);
|
||||
$last_sighting = $this->round_timestamp_to_hour($sightings[$sighting_index]['Sighting']['date_sighting']);
|
||||
$sightings[$sighting_index]['Sighting']['rounded_timestamp'] = $last_sighting;
|
||||
$score_overtime[$t] = $this->computeScore($model, $attribute, $t, $last_sighting);
|
||||
}
|
||||
$csv = 'date,value' . PHP_EOL;
|
||||
foreach ($score_overtime as $t => $v) {
|
||||
$csv .= (new DateTime())->setTimestamp($t)->format('Y-m-d H:i:s') . ',' . $v . PHP_EOL;
|
||||
}
|
||||
return array(
|
||||
'csv' => $csv,
|
||||
'sightings' => $sightings
|
||||
);
|
||||
}
|
||||
|
||||
public function get_closest_sighting($sightings, $time, $previous_index)
|
||||
{
|
||||
if (count($sightings) <= $previous_index+1) {
|
||||
return $previous_index;
|
||||
}
|
||||
$max_time = $time + 3600;
|
||||
$next_index = $previous_index+1;
|
||||
$next_sighting = $sightings[$next_index]['Sighting']['date_sighting'];
|
||||
while ($next_sighting <= $max_time) {
|
||||
$next_index++;
|
||||
if ($next_index >= count($sightings)) {
|
||||
break;
|
||||
}
|
||||
$next_sighting = $sightings[$next_index]['Sighting']['date_sighting'];
|
||||
}
|
||||
return $next_index-1;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -476,6 +476,87 @@ class Sighting extends AppModel
|
|||
return $sightingsRearranged;
|
||||
}
|
||||
|
||||
public function listSightings($user, $id, $context, $org_id = false, $sightings_type = false, $order_desc = true)
|
||||
{
|
||||
$this->Event = ClassRegistry::init('Event');
|
||||
$id = $this->explodeIdList($id);
|
||||
if ($context === 'attribute') {
|
||||
$object = $this->Event->Attribute->fetchAttributes($user, array('conditions' => array('Attribute.id' => $id, 'Attribute.deleted' => 0), 'flatten' => 1));
|
||||
} else {
|
||||
// let's set the context to event here, since we reuse the variable later on for some additional lookups.
|
||||
// Passing $context = 'org' could have interesting results otherwise...
|
||||
$context = 'event';
|
||||
$object = $this->Event->fetchEvent($user, $options = array('eventid' => $id, 'metadata' => true));
|
||||
}
|
||||
if (empty($object)) {
|
||||
throw new MethodNotAllowedException('Invalid object.');
|
||||
}
|
||||
$conditions = array(
|
||||
'Sighting.' . $context . '_id' => $id
|
||||
);
|
||||
if ($org_id) {
|
||||
$conditions[] = array('Sighting.org_id' => $org_id);
|
||||
}
|
||||
if ($sightings_type !== false) {
|
||||
$conditions[] = array('Sighting.type' => $sightings_type);
|
||||
}
|
||||
$sightings = $this->find('all', array(
|
||||
'conditions' => $conditions,
|
||||
'recursive' => -1,
|
||||
'contain' => array('Organisation.name'),
|
||||
'order' => array(sprintf('Sighting.date_sighting %s', $order_desc ? 'DESC' : ''))
|
||||
));
|
||||
if (!empty($sightings) && empty(Configure::read('Plugin.Sightings_policy')) && !$user['Role']['perm_site_admin']) {
|
||||
$eventOwnerOrgIdList = array();
|
||||
foreach ($sightings as $k => $sighting) {
|
||||
if (empty($eventOwnerOrgIdList[$sighting['Sighting']['event_id']])) {
|
||||
$temp_event = $this->Event->find('first', array(
|
||||
'recursive' => -1,
|
||||
'conditions' => array('Event.id' => $sighting['Sighting']['event_id']),
|
||||
'fields' => array('Event.id', 'Event.orgc_id')
|
||||
));
|
||||
$eventOwnerOrgIdList[$temp_event['Event']['id']] = $temp_event['Event']['orgc_id'];
|
||||
}
|
||||
if (empty($eventOwnerOrgIdList[$sighting['Sighting']['event_id']]) || $eventOwnerOrgIdList[$sighting['Sighting']['event_id']] !== $user['org_id']) {
|
||||
unset($sightings[$k]);
|
||||
}
|
||||
}
|
||||
$sightings = array_values($sightings);
|
||||
} else if (!empty($sightings) && Configure::read('Plugin.Sightings_policy') == 1 && !$user['Role']['perm_site_admin']) {
|
||||
$eventsWithOwnSightings = array();
|
||||
foreach ($sightings as $k => $sighting) {
|
||||
if (empty($eventsWithOwnSightings[$sighting['Sighting']['event_id']])) {
|
||||
$eventsWithOwnSightings[$sighting['Sighting']['event_id']] = false;
|
||||
$sighting_temp = $this->find('first', array(
|
||||
'recursive' => -1,
|
||||
'conditions' => array(
|
||||
'Sighting.event_id' => $sighting['Sighting']['event_id'],
|
||||
'Sighting.org_id' => $user['org_id']
|
||||
)
|
||||
));
|
||||
if (empty($sighting_temp)) {
|
||||
$temp_event = $this->Event->find('first', array(
|
||||
'recursive' => -1,
|
||||
'conditions' => array(
|
||||
'Event.id' => $sighting['Sighting']['event_id'],
|
||||
'Event.orgc_id' => $user['org_id']
|
||||
),
|
||||
'fields' => array('Event.id', 'Event.orgc_id')
|
||||
));
|
||||
$eventsWithOwnSightings[$sighting['Sighting']['event_id']] = !empty($temp_event);
|
||||
} else {
|
||||
$eventsWithOwnSightings[$sighting['Sighting']['event_id']] = true;
|
||||
}
|
||||
}
|
||||
if (!$eventsWithOwnSightings[$sighting['Sighting']['event_id']]) {
|
||||
unset($sightings[$k]);
|
||||
}
|
||||
}
|
||||
$sightings = array_values($sightings);
|
||||
}
|
||||
return $sightings;
|
||||
}
|
||||
|
||||
public function restSearch($user, $returnFormat, $filters)
|
||||
{
|
||||
if (!isset($this->validFormats[$returnFormat][1])) {
|
||||
|
|
|
@ -3,20 +3,20 @@
|
|||
<div style="height: 40%; display: flex">
|
||||
<div style="width: 30%; display: flex; flex-direction: column;">
|
||||
<div class="panel-container" style="display: flex; flex-direction: column; flex-grow: 1">
|
||||
<select id="select_model_to_simulate" style="width: 100%;">
|
||||
<select id="select_model_to_simulate" style="width: 100%;" onchange="refreshSimulation()">
|
||||
<?php foreach ($all_models as $model): ?>
|
||||
<option value="<?php echo h($model['DecayingModel']['id']) ?>" <?php echo $decaying_model['DecayingModel']['id'] == $model['DecayingModel']['id'] ? 'selected' : '' ?>><?php echo h($model['DecayingModel']['name']); ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
|
||||
<ul class="nav nav-tabs" style="margin-right: -5px; margin-bottom: 10px;" id="simulation-tabs">
|
||||
<ul class="nav nav-tabs" style="margin-right: -5px; margin-bottom: 0px;" id="simulation-tabs">
|
||||
<li class="active"><a href="#restsearch" data-toggle="tab">RestSearch</a></li>
|
||||
<li><a href="#specificid" data-toggle="tab">Specific ID</a></li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content" style="padding: 5px;">
|
||||
<div class="tab-pane active" id="restsearch">
|
||||
<div style="display: flex; flex-direction: column;">
|
||||
<div class="tab-content" style="padding: 5px; height: 100%;">
|
||||
<div class="tab-pane active" id="restsearch" style="height: 100%;">
|
||||
<div style="display: flex; flex-direction: column; height: 100%;">
|
||||
<h3 style="">Attribute RestSearch<span style="vertical-align: top; font-size: x-small;" class="fa fa-question-circle" title="Enforced fields: returnFormat"></span></h3>
|
||||
<?php
|
||||
$registered_taxonomies = array_keys($decaying_model['DecayingModel']['parameters']['base_score_config']);
|
||||
|
@ -24,7 +24,7 @@
|
|||
$taxonomy_name = $taxonomy_name . ':%' ;
|
||||
}
|
||||
?>
|
||||
<textarea style="margin-bottom: 0px; margin-left: 4px; width: auto;height: unset !important;" rows="12">
|
||||
<textarea style="margin-bottom: 0px; margin-left: 4px; flex-grow: 3; width: auto;">
|
||||
{
|
||||
"decayingModel": <?php echo h($decaying_model['DecayingModel']['id']); ?>,
|
||||
"to_ids": 1,
|
||||
|
@ -42,7 +42,7 @@
|
|||
<div style="display: flex;">
|
||||
<div style="margin-left: 4px; margin-bottom: 0px;" class="input-prepend">
|
||||
<span class="add-on">ID</span>
|
||||
<input class="span3" type="text" placeholder="<?php echo __('Attribute ID or UUID') ?>" onkeypress="handle_input_key(event)">
|
||||
<input type="text" placeholder="<?php echo __('Attribute ID or UUID') ?>" onkeypress="handle_input_key(event)" style="width: auto;">
|
||||
</div>
|
||||
<span id="performRestSearchButton" class="btn btn-primary" style="width: fit-content; margin-left: 4px;" role="button" onclick="doSpecificSearch(this)"><?php echo __('Simulate'); ?></span>
|
||||
</div>
|
||||
|
@ -54,7 +54,7 @@
|
|||
<div style="width: 70%; display: flex;">
|
||||
<div class="panel-container" style="flex-grow: 1;">
|
||||
<div id="chart-decay-simulation-container" style="width: 100%; height: 100%; position: relative">
|
||||
<div id="simulation_chart" style="height: 100%;"></div>
|
||||
<div id="simulation_chart" style="height: 100%; overflow: hidden;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -127,7 +127,14 @@ function doSimulation(clicked, attribute_id) {
|
|||
},
|
||||
type:'get',
|
||||
cache: false,
|
||||
dataType: 'json',
|
||||
url: '/decayingModel/decayingToolComputeSimulation/' + model_id + '/' + attribute_id,
|
||||
});
|
||||
}
|
||||
|
||||
function refreshSimulation() {
|
||||
var $row = $('#attribute_div tr.success');
|
||||
var attribute_id = $row.find('td:first').text();
|
||||
doSimulation($row, attribute_id);
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
margin: {top: 10, right: 10, bottom: 20, left: 30},
|
||||
animation_duration: 1000,
|
||||
animation_short_duration: 250,
|
||||
time_format: '%Y-%m-%d'
|
||||
time_format: '%Y-%m-%d %H:%M:%S'
|
||||
};
|
||||
this.options = $.extend(true, {}, default_options, options);
|
||||
this._init();
|
||||
|
@ -51,6 +51,7 @@
|
|||
this.width = this.$container.width() - this.options.margin.left - this.options.margin.right;
|
||||
this.height = this.$container.height() - this.options.margin.top - this.options.margin.bottom;
|
||||
this.chart_data = [];
|
||||
this.sightings = [];
|
||||
this._parseDataset();
|
||||
|
||||
this.x = d3.time.scale()
|
||||
|
@ -91,10 +92,14 @@
|
|||
},
|
||||
|
||||
update: function(data) {
|
||||
var that = this;
|
||||
this.chart_data = data;
|
||||
this.chart_data = data.csv;
|
||||
this.sightings = data.sightings;
|
||||
this._parseDataset();
|
||||
this._draw();
|
||||
},
|
||||
|
||||
_draw: function() {
|
||||
var that = this;
|
||||
this.x.domain(d3.extent(this.chart_data, function(d) { return d.date; }))
|
||||
this.y.domain([0, d3.max(this.chart_data, function(d) { return d.value; })])
|
||||
|
||||
|
@ -122,7 +127,7 @@
|
|||
this.line_guides = this.svg
|
||||
.select('.d3-line-guides-group')
|
||||
.selectAll('.d3-line-guides')
|
||||
.data(this.chart_data);
|
||||
.data(this.sightings_data);
|
||||
this.line_guides
|
||||
.enter()
|
||||
.append('line')
|
||||
|
@ -142,7 +147,7 @@
|
|||
|
||||
this.points = this.svg
|
||||
.selectAll('.d3-line-circle')
|
||||
.data(this.chart_data);
|
||||
.data(this.sightings_data);
|
||||
this.points
|
||||
.enter()
|
||||
.append('circle')
|
||||
|
@ -186,13 +191,18 @@
|
|||
var that = this;
|
||||
if (typeof this.chart_data === 'string') {
|
||||
this.chart_data = d3.csv.parse(this.chart_data, function(d){
|
||||
return { date: that.timeFormatter(d.date), value : d.value }
|
||||
var parsed_date = that.timeFormatter(d.date);
|
||||
return { timestamp: Math.floor(parsed_date.getTime() / 1000), date: parsed_date, value : parseFloat(d.value) }
|
||||
});
|
||||
} else if (Array.isArray(this.chart_data)){
|
||||
this.chart_data.forEach(function(entry, i) {
|
||||
that.chart_data[i].date = that.timeFormatter(entry.date);
|
||||
})
|
||||
}
|
||||
this.sightings_data = this.sightings.map(function(d) {
|
||||
var sighting = d.Sighting;
|
||||
return { timestamp: sighting.rounded_timestamp, date: new Date(sighting.rounded_timestamp*1000), value : 100.0 };
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue