new: First revision of the new sightings system

pull/1967/head
iglocska 2017-02-16 22:41:22 +01:00
commit 94c01d5896
18 changed files with 799 additions and 106 deletions

View File

@ -1 +1 @@
{"major":2, "minor":4, "hotfix":63}
{"major":2, "minor":4, "hotfix":65}

View File

@ -47,7 +47,7 @@ class AppController extends Controller {
public $helpers = array('Utility');
private $__jsVersion = '2.4.62';
public $pyMispVersion = '2.4.63';
public $pyMispVersion = '2.4.65';
public $phpmin = '5.5.9';
public $phprec = '7.0.0';

View File

@ -96,7 +96,7 @@ class OrganisationsController extends AppController {
if ($this->_isRest()) {
if (!isset($this->request->data['Organisation']['local'])) {
$this->request->data['Organisation']['local'] = true;
}
}
}
if ($this->Organisation->save($this->request->data)) {
if ($this->_isRest()) {
@ -212,6 +212,13 @@ class OrganisationsController extends AppController {
}
public function view($id) {
if (Validation::uuid($id)) {
$temp = $this->Organisation->find('first', array('recursive' => -1, 'fields' => array('Organisation.id'), 'conditions' => array('Organisation.uuid' => $id)));
if (empty($temp)) throw new NotFoundException('Invalid organisation.');
$id = $temp['Organisation']['id'];
} else if (!is_numeric($id)) {
throw new NotFoundException('Invalid organisation.');
}
$this->Organisation->id = $id;
if (!$this->Organisation->exists()) throw new NotFoundException('Invalid organisation');
$fullAccess = false;
@ -230,7 +237,7 @@ class OrganisationsController extends AppController {
if ($fullAccess) {
$creator = $this->Organisation->User->find('first', array(
'conditions' => array('User.id' => $org['Organisation']['created_by']),
'fields' => array('email'),
'fields' => array('email'),
'recursive' => -1
)
);

View File

@ -18,46 +18,108 @@ class SightingsController extends AppController {
// takes an attribute ID or UUID
public function add($id = false) {
if (!$this->userRole['perm_add']) throw new MethodNotAllowedException('You are not authorised to add sightings data as you don\'t have write access.');
if (!$this->request->is('post')) throw new MethodNotAllowedException('This action can only be accessed via a post request.');
$now = time();
$values = false;
$timestamp = false;
$error = false;
if ($id === 'stix') {
$result = $this->Sighting->handleStixSighting(file_get_contents('php://input'));
if ($result['success']) {
$result['data'] = json_decode($result['data'], true);
$timestamp = isset($result['data']['timestamp']) ? strtotime($result['data']['timestamp']) : $now;
$type = '0';
$source = '';
if (isset($result['data']['values'])) $values = $result['data']['values'];
else $error = 'No valid values found could be extracted from the sightings document.';
} $error = $result['message'];
} else {
if (isset($this->request->data['request'])) $this->request->data = $this->request->data['request'];
if (isset($this->request->data['Sighting'])) $this->request->data = $this->request->data['Sighting'];
$timestamp = isset($this->request->data['timestamp']) ? $this->request->data['timestamp'] : $now;
if (isset($this->request->data['value'])) $this->request->data['values'] = array($this->request->data['value']);
$values = isset($this->request->data['values']) ? $this->request->data['values'] : false;
if (!$id && isset($this->request->data['id'])) $id = $this->request->data['id'];
$type = isset($this->request->data['type']) ? $this->request->data['type'] : '0';
$source = isset($this->request->data['type']) ? $this->request->data['type'] : '';
}
if (!$error) $result = $this->Sighting->saveSightings($id, $values, $timestamp, $this->Auth->user(), $type, $source);
if ($result == 0) $error = 'No valid attributes found that would match the sighting criteria.';
if ($this->request->is('ajax')) {
if ($error) {
$error_message = 'Could not add the Sighting. Reason: ' . $error;
return new CakeResponse(array('body'=> json_encode(array('saved' => false, 'errors' => $error_message)), 'status' => 200));
if ($this->request->is('post')) {
$now = time();
$values = false;
$timestamp = false;
$error = false;
if ($id === 'stix') {
$result = $this->Sighting->handleStixSighting(file_get_contents('php://input'));
if ($result['success']) {
$result['data'] = json_decode($result['data'], true);
$timestamp = isset($result['data']['timestamp']) ? strtotime($result['data']['timestamp']) : $now;
$type = '0';
$source = '';
if (isset($result['data']['values'])) $values = $result['data']['values'];
else $error = 'No valid values found could be extracted from the sightings document.';
} $error = $result['message'];
} else {
return new CakeResponse(array('body'=> json_encode(array('saved' => true, 'success' => $result . ' ' . $this->Sighting->type[$type] . (($result == 1) ? '' : 's') . ' added.')), 'status' => 200));
if (isset($this->request->data['request'])) $this->request->data = $this->request->data['request'];
if (isset($this->request->data['Sighting'])) $this->request->data = $this->request->data['Sighting'];
if (!empty($this->request->data['date']) && !empty($this->request->data['time'])) {
$timestamp = DateTime::createFromFormat('Y-m-d:H:i:s', $this->request->data['date'] . ':' . $this->request->data['time']);
$timestamp = $timestamp->getTimestamp();
} else {
$timestamp = isset($this->request->data['timestamp']) ? $this->request->data['timestamp'] : $now;
}
if (isset($this->request->data['value'])) $this->request->data['values'] = array($this->request->data['value']);
$values = isset($this->request->data['values']) ? $this->request->data['values'] : false;
if (!$id && isset($this->request->data['id'])) $id = $this->request->data['id'];
$type = isset($this->request->data['type']) ? $this->request->data['type'] : '0';
$source = isset($this->request->data['source']) ? trim($this->request->data['source']) : '';
}
if (!$error) $result = $this->Sighting->saveSightings($id, $values, $timestamp, $this->Auth->user(), $type, $source);
if ($result == 0) $error = 'No valid attributes found that would match the sighting criteria.';
if ($this->request->is('ajax')) {
if ($error) {
$error_message = 'Could not add the Sighting. Reason: ' . $error;
return new CakeResponse(array('body'=> json_encode(array('saved' => false, 'errors' => $error_message)), 'status' => 200));
} else {
return new CakeResponse(array('body'=> json_encode(array('saved' => true, 'success' => $result . ' ' . $this->Sighting->type[$type] . (($result == 1) ? '' : 's') . ' added.')), 'status' => 200));
}
} else {
if ($error) {
return $this->RestResponse->saveFailResponse('Sighting', 'add', $id, $error);
} else {
return $this->RestResponse->saveSuccessResponse('Sighting', 'add', $id, false, $result . ' ' . $this->Sighting->type[$type] . (($result == 1) ? '' : 's') . ' successfuly added.');
}
}
} else {
if ($error) {
return $this->RestResponse->saveFailResponse('Sighting', 'add', $id, $error);
if (!$this->request->is('ajax')) {
throw new MethodNotAllowedException('This method is only accessible via POST requests and ajax GET requests.');
} else {
return $this->RestResponse->saveSuccessResponse('Sighting', 'add', $id, false, $result . ' ' . $this->Sighting->type[$type] . (($result == 1) ? '' : 's') . ' successfuly added.');
$this->layout = false;
$this->loadModel('Attribute');
if (empty($this->Attribute->fetchAttributes($this->Auth->user(), array('conditions' => array('Attribute.id' => $id))))) {
throw new MethodNotAllowedExeption('Invalid Attribute.');
}
$this->set('id', $id);
$this->render('ajax/add_sighting');
}
}
}
public function advanced($id) {
if (empty($id)) {
throw new MethodNotAllowedException('Invalid attribute.');
}
$input_id = $id;
$id = $this->Sighting->explodeIdList($id);
$this->loadModel('Attribute');
$attributes = $this->Attribute->fetchAttributes($this->Auth->user(), array('conditions' => array('Attribute.id' => $id)));
if (empty($attributes)) {
throw new MethodNotAllowedException('Invalid attribute.');
}
$this->set('id', $input_id);
$this->render('/Sightings/ajax/advanced');
}
public function quickDelete($id, $rawId, $context) {
if (!$this->userRole['perm_modify_org']) throw new MethodNotAllowedException('You are not authorised to remove sightings data as you don\'t have permission to modify your organisation\'s data.');
if (!$this->request->is('post')) {
$this->set('id', $id);
$sighting = $this->Sighting->find('first', array('conditions' => array('Sighting.id' => $id), 'recursive' => -1, 'fields' => array('Sighting.attribute_id')));
$this->set('rawId', $rawId);
$this->set('context', $context);
$this->render('ajax/quickDeleteConfirmationForm');
} else {
if (!isset($id)) {
return new CakeResponse(array('body'=> json_encode(array('saved' => true, 'errors' => 'Invalid request.')), 'status' => 200));
} else {
$sighting = $this->Sighting->find('first', array('conditions' => array('Sighting.id' => $id), 'recursive' => -1));
if (empty($sighting)) {
return new CakeResponse(array('body'=> json_encode(array('saved' => true, 'errors' => 'Invalid sighting.')), 'status' => 200));
}
if (!$this->_isSiteAdmin() && $sighting['Sighting']['org_id'] != $this->Auth->user('org_id')) {
return new CakeResponse(array('body'=> json_encode(array('saved' => true, 'errors' => 'Invalid sighting.')), 'status' => 200));
}
$result = $this->Sighting->delete($id);
if ($result) {
return new CakeResponse(array('body'=> json_encode(array('saved' => true, 'success' => 'Sighting deleted.')), 'status' => 200));
} else {
return new CakeResponse(array('body'=> json_encode(array('saved' => true, 'errors' => 'Sighting could not be deleted')), 'status' => 200));
}
}
}
}
@ -107,8 +169,10 @@ class SightingsController extends AppController {
return $this->RestResponse->viewData($sightings);
}
public function viewSightings($id, $context = 'attribute') {
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)));
} else {
@ -120,25 +184,84 @@ class SightingsController extends AppController {
if (empty($object)) {
throw new MethodNotAllowedException('Invalid object.');
}
$results = array();
$csv = array();
foreach (array('0', '1') as $type) {
$raw[$type] = $this->Sighting->find('all', array(
'conditions' => array('Sighting.' . $context . '_id' => $id, 'Sighting.type' => $type),
'recursive' => -1,
'contain' => array('Organisation.name')
));
foreach ($raw[$type] as $sighting) {
$results[$type][date('Y-m-d', $sighting['Sighting']['date_sighting'])][] = $sighting;
}
$conditions = array(
'Sighting.' . $context . '_id' => $id
);
if ($org_id) {
$conditions[] = array('Sighting.org_id' => $org_id);
}
$csv = array('0' => '', '1' => '');
$sightings = $this->Sighting->find('all', array(
'conditions' => $conditions,
'recursive' => -1,
'contain' => array('Organisation.name'),
'order' => array('Sighting.date_sighting DESC')
));
$this->set('org_id', $org_id);
$this->set('rawId', $rawId);
$this->set('context', $context);
$this->set('types', array('Sighting', 'False-positive', 'Expiration'));
$this->set('sightings', $sightings);
$this->layout = false;
$this->render('ajax/list_sightings');
}
public function viewSightings($id, $context = 'attribute') {
$this->loadModel('Event');
$id = $this->Sighting->explodeIdList($id);
if ($context === 'attribute') {
$attribute_id = $id;
$object = $this->Event->Attribute->fetchAttributes($this->Auth->user(), array('conditions' => array('Attribute.id' => $id, 'Attribute.deleted' => 0)));
if (empty($object)) {
throw new MethodNotAllowedException('Invalid object.');
}
$eventIds = array();
foreach ($object as $k => $v) {
$eventIds[] = $v['Attribute']['event_id'];
}
$events = $this->Event->fetchEvent($this->Auth->user(), array('eventid' => $eventIds));
} else {
$attribute_id = false;
// 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';
$events = $this->Event->fetchEvent($this->Auth->user(), array('eventid' => $id));
}
if (empty($events)) {
throw new MethodNotAllowedException('Invalid object.');
}
$results = array();
$raw = array();
foreach ($events as $event) {
$raw = array_merge($raw, $this->Sighting->attachToEvent($event, $this->Auth->user(), $attribute_id));
}
foreach ($raw as $sighting) {
$results[$sighting['type']][date('Ymd', $sighting['date_sighting'])][] = $sighting;
}
$tsv = 'date\tSighting\tFalse-positive\n';
$dataPoints = array();
$startDate = (date('Ymd') - 3);
$details = array();
foreach ($results as $type => $data) {
foreach ($data as $date => $sighting) {
$csv[$type] .= $date . ', ' . count($sighting) . PHP_EOL;
if ($date < $startDate) {
$startDate = $date;
}
$temp = array();
foreach ($sighting as $sightingInstance) {
$temp[$sightingInstance['Organisation']['name']] = isset($temp[$sightingInstance['Organisation']['name']]) ? $temp[$sightingInstance['Organisation']['name']] + 1 : 1;
}
$dataPoints[$date][$type] = array('count' => count($sighting), 'details' => $temp);
}
}
$this->set('csv', $csv);
for ($i = $startDate; $i < date('Ymd') + 1; $i++) {
if (checkdate(substr($i, 4, 2), substr($i, 6, 2), substr($i, 0, 4))) {
$tsv .= $i . '\t' . (isset($dataPoints[$i][0]['count']) ? $dataPoints[$i][0]['count'] : 0) . '\t' . (isset($dataPoints[$i][1]['count']) ? $dataPoints[$i][1]['count'] : 0) . '\n';
$details[$i][0] = isset($dataPoints[$i][0]['details']) ? $dataPoints[$i][0]['details'] : array();
$details[$i][1] = isset($dataPoints[$i][1]['details']) ? $dataPoints[$i][1]['details'] : array();
}
}
$this->set('tsv', $tsv);
$this->set('results', $results);
$this->layout = 'ajax';
$this->render('ajax/view_sightings');

View File

@ -41,7 +41,7 @@ class AppModel extends Model {
42 => false, 44 => false, 45 => false, 49 => true, 50 => false,
51 => false, 52 => false, 55 => true, 56 => true, 57 => true,
58 => false, 59 => false, 60 => false, 61 => false, 62 => false,
63 => false, 64 => false
63 => false, 64 => false, 65 => false
)
)
);
@ -118,7 +118,6 @@ class AppModel extends Model {
public function updateDatabase($command) {
$dataSourceConfig = ConnectionManager::getDataSource('default')->config;
$dataSource = $dataSourceConfig['datasource'];
$sql = '';
$sqlArray = array();
$indexArray = array();
$this->Log = ClassRegistry::init('Log');
@ -582,6 +581,33 @@ class AppModel extends Model {
$sqlArray[] = 'ALTER TABLE event_blacklists CHANGE comment comment TEXT CHARACTER SET utf8 COLLATE utf8_unicode_ci;';
break;
case '2.4.64':
$indexArray[] = array('feeds', 'input_source');
$indexArray[] = array('attributes', 'value1', 255);
$indexArray[] = array('attributes', 'value2', 255);
$indexArray[] = array('attributes', 'type');
$indexArray[] = array('galaxy_reference', 'galaxy_cluster_id');
$indexArray[] = array('galaxy_reference', 'referenced_galaxy_cluster_id');
$indexArray[] = array('galaxy_reference', 'referenced_galaxy_cluster_value', 255);
$indexArray[] = array('galaxy_reference', 'referenced_galaxy_cluster_type', 255);
$indexArray[] = array('correlations', '1_event_id');
$indexArray[] = array('warninglist_entries', 'warninglist_id');
$indexArray[] = array('galaxy_clusters', 'value', 255);
$indexArray[] = array('galaxy_clusters', 'tag_name');
$indexArray[] = array('galaxy_clusters', 'uuid');
$indexArray[] = array('galaxy_clusters', 'type');
$indexArray[] = array('galaxies', 'name');
$indexArray[] = array('galaxies', 'uuid');
$indexArray[] = array('galaxies', 'type');
break;
case '2.4.65':
$sqlArray[] = 'ALTER TABLE feeds CHANGE `enabled` `enabled` tinyint(1) DEFAULT 0;';
$sqlArray[] = 'ALTER TABLE feeds CHANGE `default` `default` tinyint(1) DEFAULT 0;';
$sqlArray[] = 'ALTER TABLE feeds CHANGE `distribution` `distribution` tinyint(4) NOT NULL DEFAULT 0;';
$sqlArray[] = 'ALTER TABLE feeds CHANGE `sharing_group_id` `sharing_group_id` int(11) NOT NULL DEFAULT 0;';
$sqlArray[] = 'ALTER TABLE attributes CHANGE `comment` `comment` text COLLATE utf8_bin;';
break;
case '2.4.66':
$sqlArray[] = 'ALTER TABLE shadow_attributes CHANGE old_id old_id int(11) DEFAULT 0;';
$sqlArray[] = 'ALTER TABLE sightings ADD COLUMN uuid varchar(255) COLLATE utf8_bin DEFAULT "";';
$sqlArray[] = 'ALTER TABLE sightings ADD COLUMN source varchar(255) COLLATE utf8_bin DEFAULT "";';
$sqlArray[] = 'ALTER TABLE sightings ADD COLUMN type int(11) DEFAULT 0;';
@ -606,7 +632,6 @@ class AppModel extends Model {
return false;
break;
}
if (!isset($sqlArray)) $sqlArray = array($sql);
foreach ($sqlArray as $sql) {
try {
$this->query($sql);
@ -634,33 +659,12 @@ class AppModel extends Model {
'change' => 'The executed SQL query was: ' . $sql . PHP_EOL . ' The returned error is: ' . $e->getMessage()
));
}
foreach ($indexArray as $iA) {
try {
$this->__addIndex($iA[0], $iA[1]);
$this->Log->create();
$this->Log->save(array(
'org' => 'SYSTEM',
'model' => 'Server',
'model_id' => 0,
'email' => 'SYSTEM',
'action' => 'update_database',
'user_id' => 0,
'title' => 'Successfuly executed the SQL query for ' . $command,
'change' => 'New index for field ' . $iA[1] . ' added to table ' . $iA[0],
));
} catch (Exception $e) {
$this->Log->create();
$this->Log->save(array(
'org' => 'SYSTEM',
'model' => 'Server',
'model_id' => 0,
'email' => 'SYSTEM',
'action' => 'update_database',
'user_id' => 0,
'title' => 'Issues executing the SQL query for ' . $command,
'change' => 'Failed to add index for field ' . $iA[1] . ' for table ' . $iA[0],
));
}
}
foreach ($indexArray as $iA) {
if (isset($iA[2])) {
$this->__addIndex($iA[0], $iA[1], $iA[2]);
} else {
$this->__addIndex($iA[0], $iA[1]);
}
}
if ($clean) $this->cleanCacheFiles();
@ -712,16 +716,18 @@ class AppModel extends Model {
if ($dataSource == 'Database/Postgres') {
$addIndex = "CREATE INDEX idx_" . $table . "_" . $field . " ON " . $table . " (" . $field . ");";
} else {
if (isset($length)) {
if (!$length) {
$addIndex = "ALTER TABLE `" . $table . "` ADD INDEX `" . $field . "` (`" . $field . "`);";
} else {
$addIndex = "ALTER TABLE `" . $table . "` ADD INDEX `" . $field . "` (`" . $field . "`(" . $length . "));";
}
}
$result = true;
$duplicate = false;
try {
$this->query($addIndex);
} catch (Exception $e) {
$duplicate = (strpos($e->getMessage(), '1061') !== false);
$result = false;
}
$this->Log->create();
@ -732,8 +738,8 @@ class AppModel extends Model {
'email' => 'SYSTEM',
'action' => 'update_database',
'user_id' => 0,
'title' => ($result ? 'Added index ' : 'Failed to add index ') . $field . ' to ' . $table,
'change' => ($result ? 'Added index ' : 'Failed to add index ') . $field . ' to ' . $table,
'title' => ($result ? 'Added index ' : 'Failed to add index ') . $field . ' to ' . $table . ($duplicate ? ' (index already set)' : ''),
'change' => ($result ? 'Added index ' : 'Failed to add index ') . $field . ' to ' . $table . ($duplicate ? ' (index already set)' : ''),
));
}

View File

@ -46,10 +46,13 @@ class Sighting extends AppModel {
return true;
}
public function attachToEvent(&$event, $user) {
public function attachToEvent($event, $user, $attribute_id = false) {
$ownEvent = false;
if ($user['Role']['perm_site_admin'] || $event['Event']['org_id'] == $user['org_id']) $ownEvent = true;
$conditions = array('Sighting.event_id' => $event['Event']['id']);
if ($attribute_id) {
$conditions[] = array('Sighting.attribute_id' => $attribute_id);
}
if (!$ownEvent && (!Configure::read('Plugin.Sightings_policy') || Configure::read('Plugin.Sightings_policy') == 0)) {
$conditions['Sighting.org_id'] = $user['org_id'];
}
@ -109,6 +112,10 @@ class Sighting extends AppModel {
if (empty($attributes)) return 0;
$sightingsAdded = 0;
foreach ($attributes as $attribute) {
if ($type === '2') {
// remove existing expiration by the same org if it exists
$this->deleteAll(array('Sighting.org_id' => $user['org_id'], 'Sighting.type' => $type, 'Sighting.attribute_id' => $attribute['Attribute']['id']));
}
$this->create();
$sighting = array(
'attribute_id' => $attribute['Attribute']['id'],
@ -159,4 +166,17 @@ class Sighting extends AppModel {
$this->saveMany($sightings);
return true;
}
public function explodeIdList($id) {
if (strpos($id, '|')) {
$id = explode('|', $id);
foreach ($id as $k => $v) {
if (!is_numeric($v)) {
unset($id[$k]);
}
}
$id = array_values($id);
}
return $id;
}
}

View File

@ -16,6 +16,8 @@
$attributeOwnSightings = array();
$attributeSightingsPopover = array();
$sightingsData = array();
$sparklineData = array();
$startDates = array();
if (!empty($event['Sighting'])) {
foreach ($event['Sighting'] as $sighting) {
$type = $sightingTypes[$sighting['type']];
@ -27,12 +29,43 @@
if (!isset($sightingsData[$sighting['attribute_id']][$type]['orgs'][$orgName])) {
$sightingsData[$sighting['attribute_id']][$type]['orgs'][$orgName] = array('count' => 1, 'date' => $sighting['date_sighting']);
} else {
if (!isset($startDates[$sighting['attribute_id']]) || $startDates[$sighting['attribute_id']] > $sighting['date_sighting']) {
$startDates[$sighting['attribute_id']] = $sighting['date_sighting'];
}
$sightingsData[$sighting['attribute_id']][$type]['orgs'][$orgName]['count']++;
if ($sightingsData[$sighting['attribute_id']][$type]['orgs'][$orgName]['date'] < $sighting['date_sighting']) {
$sightingsData[$sighting['attribute_id']][$type]['orgs'][$orgName]['date'] = $sighting['date_sighting'];
}
}
$date = date("Ymd", $sighting['date_sighting']);
if (!isset($sparklineData[$sighting['attribute_id']][$type][$date])) {
$sparklineData[$sighting['attribute_id']][$type][$date] = 1;
} else {
$sparklineData[$sighting['attribute_id']][$type][$date]++;
}
}
$csv = array();
$to = new DateTime();
$from = new DateTime();
foreach ($sparklineData as $aid => $data) {
foreach ($data as $type => $sighting) {
$from->setTimestamp(($startDates[$aid] - 259200));
for ($date = clone $from; $date < $to; $date->modify('+1 day')) {
if (!isset($csv[$aid][$type])) {
$csv[$aid][$type] = 'Date,Close\n';
}
$currentDate = $date->format('Ymd');
if (isset($sighting[$currentDate])) {
$csv[$aid][$type] .= $currentDate . ',' . $sighting[$currentDate] . '\n';
} else {
$csv[$aid][$type] .= $currentDate . ',0\n';
}
}
}
}
unset($sparklineData);
foreach ($sightingsData as $aid => $data) {
$sightingsData[$aid]['html'] = '';
foreach ($data as $type => $typeData) {
@ -122,6 +155,9 @@
<span id="multi-delete-button" title="Delete selected Attributes" class = "icon-trash mass-select useCursorPointer" onClick="multiSelectAction(<?php echo $event['Event']['id']; ?>, 'deleteAttributes');"></span>
<span id="multi-accept-button" title="Accept selected Proposals" class="icon-ok mass-proposal-select useCursorPointer" onClick="multiSelectAction(<?php echo $event['Event']['id']; ?>, 'acceptProposals');"></span>
<span id="multi-discard-button" title="Discard selected Proposals" class = "icon-remove mass-proposal-select useCursorPointer" onClick="multiSelectAction(<?php echo $event['Event']['id']; ?>, 'discardProposals');"></span>
<?php if (Configure::read('Plugin.Sightings_enable')): ?>
<span id="multi-sighting-button" title="Sightings display for selected attributes" class = "icon-wrench mass-select useCursorPointer sightings_advanced_add" data-object-id="selected"></span>
<?php endif; ?>
</div>
<div class="tabMenu tabMenuToolsBlock noPrint">
<?php if ($mayModify): ?>
@ -450,10 +486,21 @@
echo $this->Form->end();
?>
</span>
<?php
$temp = array();
if (isset($csv[$object['id']]['sighting'])) {
$temp[0] = $csv[$object['id']]['sighting'];
}
if (isset($csv[$object['id']]['sighting'])) {
$temp[1] = $csv[$object['id']]['false-positive'];
}
echo $this->element('sparkline', array('id' => $object['id'], 'csv' => $temp));
?>
<span class="icon-thumbs-up useCursorPointer" onClick="addSighting('0', '<?php echo h($object['id']); ?>', '<?php echo h($event['Event']['id']);?>', '<?php echo h($page); ?>');">&nbsp;</span>
<span class="icon-thumbs-down useCursorPointer" onClick="addSighting('1', '<?php echo h($object['id']); ?>', '<?php echo h($event['Event']['id']);?>', '<?php echo h($page); ?>');">&nbsp;</span>
<span class="icon-wrench useCursorPointer" onClick="addSighting('<?php echo h($object['id']); ?>', '<?php echo h($event['Event']['id']);?>', '<?php echo h($page); ?>');">&nbsp;</span>
<span id="sightingCount_<?php echo h($object['id']); ?>" class="bold sightingsCounter_<?php echo h($object['id']); ?>" data-placement="top" data-toggle="popover" data-trigger="hover" data-content="<?php echo isset($sightingsData[$object['id']]['html']) ? $sightingsData[$object['id']]['html'] : ''; ?>">
<span class="icon-wrench useCursorPointer sightings_advanced_add" data-object-id="<?php echo h($object['id']); ?>">&nbsp;</span>
<span id="sightingCount_<?php echo h($object['id']); ?>" class="bold sightingsCounter_<?php echo h($object['id']); ?>" data-placement="top" data-toggle="popover" data-trigger="hover" data-content="<?php echo isset($sightingsData[$object['id']]['html']) ? $sightingsData[$object['id']]['html'] : ''; ?>">
<?php
$s = (!empty($sightingsData[$object['id']]['sighting']['count']) ? $sightingsData[$object['id']]['sighting']['count'] : 0);
$f = (!empty($sightingsData[$object['id']]['false-positive']['count']) ? $sightingsData[$object['id']]['false-positive']['count'] : 0);
@ -616,6 +663,20 @@ attributes or the appropriate distribution level. If you think there is a mistak
$('.screenshot').click(function() {
screenshotPopup($(this).attr('src'), $(this).attr('title'));
});
$('.sightings_advanced_add').click(function() {
var selected = [];
var object_id = $(this).data('object-id');
if (object_id == 'selected') {
$(".select_attribute").each(function() {
if ($(this).is(":checked")) {
selected.push($(this).data("id"));
}
});
object_id = selected.join('|');
}
url = "<?php echo $baseurl; ?>" + "/sightings/advanced/" + object_id;
genericPopup(url, '#screenshot_box');
});
});
</script>
<?php

View File

@ -0,0 +1,48 @@
<!--
Modified version of http://www.tnoda.com/blog/2013-12-19
-->
<?php
echo $this->Html->script('d3');
//echo $this->Html->css('sightingstyle');
?>
<div id="spark_<?php echo h($id); ?>"></div>
<script>
var width = 100;
var height = 25;
var x = d3.scale.linear().range([0, width - 2]);
var y = d3.scale.linear().range([height - 4, 0]);
var parseDate = d3.time.format("%Y%m%d").parse;
var line = d3.svg.line()
.interpolate("linear")
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.close); });
function sparkline(elemId, data) {
data.forEach(function(d) {
d.date = parseDate(d.Date);
d.close = +d.Close;
});
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain(d3.extent(data, function(d) { return d.close; }));
var svg = d3.select(elemId)
.append('svg')
.attr('width', width)
.attr('height', height)
.append('g')
.attr('transform', 'translate(0, 2)');
svg.append('path')
.datum(data)
.attr('class', 'sparkline')
.attr('d', line);
svg.append('circle')
.attr('class', 'sparkcircle')
.attr('cx', x(data[0].close))
.attr('cy', y(data[0].date))
.attr('r', 1.5);
}
var myData = "<?php echo isset($csv[0]) ? $csv[0] : ''; ?>";
if (myData != '') {
var data = d3.csv.parse(myData);
sparkline('#spark_<?php echo h($id); ?>', data);
}
</script>

View File

@ -67,21 +67,21 @@ echo $this->Form->end();
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 '"' . $key . '": "<span class=\"blue bold\">' . h($formInfoData['key']) . '</span>: ' . h($formInfoData['desc']) . '<br />",' . PHP_EOL;
}
echo '}' . PHP_EOL;
}
?>
$('#EventDistribution').change(function() {
if ($('#EventDistribution').val() == 4) $('#SGContainer').show();
else $('#SGContainer').hide();
});
$("#EventDistribution, #EventAnalysis, #EventThreatLevelId").change(function() {
initPopoverContent('Event');
});
$(document).ready(function() {
if ($('#EventDistribution').val() == 4) $('#SGContainer').show();
else $('#SGContainer').hide();

View File

@ -34,8 +34,8 @@
</head>
<body>
<div id="popover_form" class="ajax_popover_form"></div>
<div id="confirmation_box" class="confirmation_box"></div>
<div id="screenshot_box" class="screenshot_box"></div>
<div id="confirmation_box" class="confirmation_box"></div>
<div id="gray_out" class="gray_out"></div>
<div id="container">
<?php echo $this->element('global_menu');

View File

@ -0,0 +1,36 @@
<h3>Add Sighting</h3>
<?php
echo $this->Form->create('Sighting', array('id', 'url' => '/sightings/add/' . $id, 'style' => 'margin-bottom:0px;'));
echo $this->Form->input('type', array(
'options' => array('Sighting', 'Fase-positive', 'Expiration'),
'default' => 2,
'style' => 'width:230px;margin-right:0px;'
));
echo $this->Form->input('source', array(
'placeholder' => 'honeypot, IDS sensor id, SIEM,...',
'style' => 'width:447px;',
'div' => array('style' => 'width:457px !important;')
));
echo $this->Form->label('Sighting date');
echo $this->Form->input('date', array(
'type' => 'text',
'id' => 'datepicker',
'default' => date('Y-m-d'),
'style' => 'width:110px;',
'div' => array('style' => 'width:120px !important;'),
'label' => false
));
echo $this->Form->input('time', array(
'class' => 'input-mini',
'default' => date('H:i:s'),
'id' => 'timepicker',
'style' => 'width:120px;',
'div' => array('style' => 'width:120px !important;'),
'label' => false
));
?>
<span id="submitButton" class="btn btn-primary" onClick="submitPopoverForm('<?php echo h($id);?>', 'addSighting')">Add</span>
<div class="input clear"></div>
<?php
echo $this->Form->end();
?>

View File

@ -0,0 +1,59 @@
<div class="sightings_advanced">
<div class="popover-legend"><p><?php echo __('Sighting details'); ?></p></div>
<div style="margin:10px;">
<span id="sightingsGraphToggle" class="btn btn-primary qet toggle-left sightingsToggle" data-type="graph">Graph</span>
<span id="sightingsListAllToggle" class="btn btn-inverse qet toggle sightingsToggle" data-type="all">All</span>
<span id="sightingsListMyToggle" class="btn btn-inverse qet toggle sightingsToggle" data-type="org">My org</span>
<span id="sightingsAddToggle" class="btn btn-inverse qet toggle-right sightingsToggle" data-type="add">Add sighting</span>
</div>
<div id="mainContents" style="margin-top:40px;padding:10px;">
<div id="sightingsData" class="sightingTab"></div>
<span style="float:right;margin-bottom:10px;" class="btn btn-inverse" id="cancel">Cancel</span>
</div>
</div>
</div>
<script type="text/javascript">
$(document).ready(function() {
id = "<?php echo h($id); ?>";
$('#cancel').click(function() {
cancelPopoverForm();
});
$('#datepicker').datepicker({
startDate: '-180d',
endDate: '+1d',
orientation: 'bottom',
autoclose: true,
format: 'yyyy-mm-dd'
});
$('#timepicker').timepicker({
minuteStep: 1,
showMeridian: false,
showSeconds: true,
maxHours: 24
});
loadSightingGraph(id, "attribute");
});
$('.sightingsToggle').click(function() {
$('.sightingsToggle').removeClass('btn-primary');
$('.sightingsToggle').addClass('btn-inverse');
$(this).removeClass('btn-inverse');
$(this).addClass('btn-primary');
var type = $(this).data('type');
$('.sightingTab').empty();
if (type == 'graph') {
loadSightingGraph(id, "attribute");
} else if (type == 'add') {
$.get( "/sightings/add/" + id, function(data) {
$("#sightingsData").html(data);
});
} else {
var org = "";
if (type == 'org') org = "/<?php echo h($me['org_id']);?>"
$.get( "/sightings/listSightings/" + id + "/attribute" + org, function(data) {
$("#sightingsData").html(data);
});
}
});
</script>
<?php echo $this->Js->writeBuffer(); // Write cached scripts

View File

@ -0,0 +1,52 @@
<div>
<div id="org_id" class="hidden"><?php echo h($org_id); ?></div>
<h2>Galaxies</h2>
<table class="table table-striped table-hover table-condensed" style="display:block; overflow-y:auto;max-height:500px;">
<tr>
<th>Date</th>
<th>Organisation</th>
<th>Type</th>
<th>Source</th>
<th>Event ID</th>
<th>Attribute ID</th>
<th class="actions">Actions</th>
</tr>
<?php
foreach ($sightings as $item):
?>
<tr>
<td class="short"><?php echo date('Y-m-d H:i:s', $item['Sighting']['date_sighting']);?></td>
<td class="short">
<?php
$imgAbsolutePath = APP . WEBROOT_DIR . DS . 'img' . DS . 'orgs' . DS . h($item['Organisation']['name']) . '.png';
if (file_exists($imgAbsolutePath)):
echo $this->Html->image('orgs/' . h($item['Organisation']['name']) . '.png', array('alt' => h($item['Organisation']['name']), 'title' => h($item['Organisation']['name']), 'style' => 'width:24px; height:24px'));
else:
echo h($item['Organisation']['name']);
endif;
?>
</td>
<td class="short">
<?php
echo $types[$item['Sighting']['type']];
?>
</td>
<td class="short"><?php echo h($item['Sighting']['source']);?></td>
<td class="short"><?php echo h($item['Sighting']['event_id']);?></td>
<td class="short"><?php echo h($item['Sighting']['attribute_id']);?></td>
<td class="short action-links">
<?php
if ($isSiteAdmin || ($item['Sighting']['org_id'] == $me['org_id'] && $perm_add)):
?>
<span class="icon-trash useCursorPointer" onClick="quickDeleteSighting('<?php echo h($item['Sighting']['id']); ?>', '<?php echo h($rawId); ?>', '<?php echo h($context); ?>');"></span>
<?php
endif;
?>
</td>
</tr>
<?php
endforeach;
?>
</table>
</div>

View File

@ -0,0 +1,24 @@
<div class="confirmation">
<?php
echo $this->Form->create('Sighting', array('style' => 'margin:0px;', 'id' => 'PromptForm', 'url' => '/sightings/quickDelete/' . $id));
?>
<legend>Remove Sighting</legend>
<div style="padding-left:5px;padding-right:5px;padding-bottom:5px;">
<p>Remove sighting (<?php echo h($id); ?>)?</p>
<table>
<tr>
<td style="vertical-align:top">
<span id="PromptYesButton" class="btn btn-primary" onClick="removeSighting('<?php echo h($id); ?>', '<?php echo h($rawId); ?>', '<?php echo h($context); ?>');">Yes</span>
</td>
<td style="width:540px;">
</td>
<td style="vertical-align:top;">
<span class="btn btn-inverse" id="PromptNoButton" onClick="cancelPrompt(1);">No</span>
</td>
</tr>
</table>
</div>
<?php
echo $this->Form->end();
?>
</div>

View File

@ -0,0 +1,150 @@
<?php
echo $this->Html->script('d3');
echo $this->Html->css('sightingstyle');
?>
<div id="graphContent" class="graphContent"></div>
<script>
var myData = "<?php echo $tsv; ?>";
var colours = {
'Sighting': 'blue',
'False-positive': 'red'
}
var margin = {
top: 20,
right: 60,
bottom: 30,
left: 25
},
width = 980 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
var parseDate = d3.time.format("%Y%m%d").parse;
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var color = d3.scale.category10();
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var line = d3.svg.line()
.interpolate("linear")
.x(function(d) {
return x(d.date);
})
.y(function(d) {
return y(d.count);
});
var svg = d3.select("#graphContent").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var data = d3.tsv.parse(myData);
color.domain(d3.keys(data[0]).filter(function(key) {
return key !== "date";
}));
data.forEach(function(d) {
d.date = parseDate(d.date);
});
var sightings = color.domain().map(function(name) {
return {
name: name,
values: data.map(function(d) {
return {
date: d.date,
count: +d[name]
};
})
};
});
x.domain(d3.extent(data, function(d) {
return d.date;
}));
y.domain([
d3.min(sightings, function(c) {
return d3.min(c.values, function(v) {
return v.count;
});
}),
d3.max(sightings, function(c) {
return d3.max(c.values, function(v) {
return v.count;
});
})
]);
var legend = svg.selectAll('g')
.data(sightings)
.enter()
.append('g')
.attr('class', 'sightingsLegend');
legend.append('rect')
.attr('x', width - 20)
.attr('y', function(d, i) {
return i * 20;
})
.attr('width', 10)
.attr('height', 10)
.style('fill', function(d) {
return colours[d.name];
});
legend.append('text')
.attr('x', width - 8)
.attr('y', function(d, i) {
return (i * 20) + 9;
})
.text(function(d) {
return d.name;
});
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Count");
var sightings = svg.selectAll(".sightings")
.data(sightings)
.enter().append("g")
.attr("class", "sightings");
sightings.append("path")
.attr("class", "line")
.attr("d", function(d) {
return line(d.values);
})
.style("stroke", function(d) {
return colours[d.name];
});
</script>

View File

@ -762,6 +762,20 @@ a.proposal_link_red:hover {
color:white;
}
.popover-legend {
border-radius: 5px 5px 0px 0px;
margin-bottom:5px;
padding-left:0px;
background-color:black;
color:white;
font-size: 21px;
line-height: 40px;
}
.popover-legend p {
padding-left:10px;
}
.legend {
display: block;
width: 100%;
@ -1672,6 +1686,21 @@ table.table.table-striped tr.deleted_row td {
transition: all 0.3s ease;
}
.sparkline {
fill: none;
stroke: #000;
stroke-width: 0.5px;
}
.sparkcircle {
fill: #f00;
stroke: none;
}
.sightings_advanced
{
width:1000px;
}
@-webkit-keyframes rotation {
from {-webkit-transform: rotate(0deg);}
to {-webkit-transform: rotate(359deg);}

View File

@ -0,0 +1,20 @@
.graphContent {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
display: none;
}
.line {
fill: none;
stroke: steelblue;
stroke-width: 1.5px;
}

View File

@ -14,6 +14,14 @@ function deleteObject(type, action, id, event) {
});
}
function quickDeleteSighting(id, rawId, context) {
url = "/sightings/quickDelete/" + id + "/" + rawId + "/" + context;
$.get(url, function(data) {
$("#confirmation_box").fadeIn();
$("#confirmation_box").html(data);
});
}
function publishPopup(id, type) {
var action = "alert";
if (type == "publish") action = "publish";
@ -37,6 +45,8 @@ function genericPopup(url, popupTarget) {
$.get(url, function(data) {
$(popupTarget).html(data);
$(popupTarget).fadeIn();
left = ($(window).width() / 2) - ($(popupTarget).width() / 2);
$(popupTarget).css({'left': left + 'px'});
$("#gray_out").fadeIn();
});
}
@ -64,9 +74,11 @@ function editTemplateElement(type, id) {
});
}
function cancelPrompt() {
$("#confirmation_box").fadeIn();
$("#gray_out").fadeOut();
function cancelPrompt(isolated) {
if (isolated == 'undefined') {
$("#gray_out").fadeOut();
}
$("#confirmation_box").fadeOut();
$("#confirmation_box").empty();
}
@ -94,6 +106,33 @@ function submitDeletion(context_id, action, type, id) {
});
}
function removeSighting(id, rawid, context) {
if (context != 'attribute') {
context = 'event';
}
var formData = $('#PromptForm').serialize();
$.ajax({
beforeSend: function (XMLHttpRequest) {
$(".loading").show();
},
data: formData,
success:function (data, textStatus) {
handleGenericAjaxResponse(data);
},
complete:function() {
$(".loading").hide();
$("#confirmation_box").fadeOut();
var org = "/" + $('#org_id').text();
$.get( "/sightings/listSightings/" + attribute_id + "/" + context + org, function(data) {
$("#sightingsData").html(data);
});
},
type:"post",
cache: false,
url:"/sightings/quickDelete/" + id,
});
}
function toggleSetting(e, setting, id) {
e.preventDefault();
e.stopPropagation();
@ -732,6 +771,7 @@ function submitPopoverForm(context_id, referer, update_context_id) {
var url = null;
var context = 'event';
var contextNamingConvention = 'Attribute';
var closePopover = true;
switch (referer) {
case 'add':
url = "/attributes/add/" + context_id;
@ -778,20 +818,28 @@ function submitPopoverForm(context_id, referer, update_context_id) {
case 'replaceAttributes':
url = "/attributes/attributeReplace/" + context_id;
break;
case 'addSighting':
url = "/sightings/add/" + context_id;
closePopover = false;
break;
}
if (url !== null) {
$.ajax({
beforeSend: function (XMLHttpRequest) {
$(".loading").show();
$("#gray_out").fadeOut();
$("#popover_form").fadeOut();
if (closePopover) {
$("#gray_out").fadeOut();
$("#popover_form").fadeOut();
}
},
data: $("#submitButton").closest("form").serialize(),
success:function (data, textStatus) {
var result = handleAjaxPopoverResponse(data, context_id, url, referer, context, contextNamingConvention);
if (closePopover) {
var result = handleAjaxPopoverResponse(data, context_id, url, referer, context, contextNamingConvention);
}
if (context == 'event' && (referer == 'add' || referer == 'massEdit' || referer == 'replaceAttributes')) eventUnpublish();
$(".loading").show();
$(".loading").hide();
},
type:"post",
url:url
@ -905,7 +953,10 @@ function showMessage(success, message, context) {
}
function cancelPopoverForm() {
$("#popover_form").empty();
$("#gray_out").fadeOut();
$("#popover_form").fadeOut();
$("#screenshot_box").fadeOut();
$("#confirmation_box").fadeOut();
$('#gray_out').fadeOut();
$('#popover_form').fadeOut();
}
@ -1108,6 +1159,7 @@ function getPopup(id, context, target, admin, popupType) {
$(".loading").show();
},
dataType:"html",
async: true,
cache: false,
success:function (data, textStatus) {
$(".loading").hide();
@ -1126,6 +1178,7 @@ function simplePopup(url) {
$(".loading").show();
},
dataType:"html",
async: true,
cache: false,
success:function (data, textStatus) {
$(".loading").hide();
@ -1926,7 +1979,6 @@ function simpleTabPageLast() {
if ($('#SharingGroupRoaming').is(":checked")) {
summaryservers = "any interconnected instances linked by an eligible organisation.";
} else {
console.log(servercounter);
if (servercounter == 0) {
summaryservers = "data marked with this sharing group will not be pushed.";
}
@ -2832,7 +2884,7 @@ function checkAndSetPublishedInfo() {
$(document).keyup(function(e){
if (e.keyCode === 27) {
$("#gray_out").fadeOut();
$("#gray_out").fadeOut();
$("#popover_form").fadeOut();
$("#screenshot_box").fadeOut();
$("#confirmation_box").fadeOut();
@ -2845,3 +2897,9 @@ function closeScreenshot() {
$("#screenshot_box").fadeOut();
$("#gray_out").fadeOut();
}
function loadSightingGraph(id, scope) {
$.get( "/sightings/viewSightings/" + id + "/" + scope, function(data) {
$("#sightingsData").html(data);
});
}