new: Various new feed features

- import feed descriptor json pastes to add a list of pre-defined feeds
- improvements to the feed pull (a single non validating attribute shouldn't break the process)
- altered the saving of the attributes to happen in chunks during a feed pull to avoid very large feeds from stalling the process
- split the feeds into 3 tabs: default, custom, all
pull/1856/head
iglocska 2016-12-31 09:04:46 +01:00
parent 7f8a81e161
commit 76e9398df9
6 changed files with 108 additions and 10 deletions

View File

@ -25,6 +25,12 @@ class FeedsController extends AppController {
}
public function index() {
$scope = isset($this->passedArgs['scope']) ? $this->passedArgs['scope'] : 'default';
if ($scope !== 'all') {
$this->paginate['conditions'][] = array(
'Feed.default' => $scope == 'custom' ? 0 : 1
);
}
$data = $this->paginate();
$this->loadModel('Event');
foreach ($data as $key => $value) {
@ -44,6 +50,7 @@ class FeedsController extends AppController {
}
return $this->RestResponse->viewData($data, $this->response->type());
}
$this->set('scope', $scope);
$this->set('feeds', $data);
$this->loadModel('Event');
$this->set('feed_types', $this->Feed->feed_types);
@ -54,6 +61,42 @@ class FeedsController extends AppController {
$feed = $this->Feed->find('first', array('conditions' => array('Feed.id' => $feedId)));
}
public function importFeeds() {
if ($this->request->is('post')) {
$feeds = json_decode($this->request->data['Feed']['json'], true);
if (empty($feeds)) throw new NotFoundException('No valid ');
$existingFeeds = $this->Feed->find('all', array());
$fail = $success = 0;
foreach ($feeds as $feed) {
$found = false;
foreach ($existingFeeds as $existingFeed) {
if ($existingFeed['Feed']['url'] == $feed['Feed']['url']) {
$found = true;
}
}
if (!$found) {
$this->Feed->create();
if (!$this->Feed->save($feed, true, array('name', 'provider', 'url', 'rules', 'source_format', 'fixed_event', 'delta_merge', 'override_ids', 'publish', 'settings'))) {
$fail++;
$this->Session->setFlash('Could not save feeds. Reason: ' . json_encode($this->Feed->validationErros));
} else {
$success++;
}
}
}
$message = $success . ' new feeds added.';
if ($fail) {
$message .= ' ' . $fail . ' feeds could not be added (possibly because they already exist)';
}
if ($this->_isRest()) {
} else {
$this->Session->setFlash($message);
$this->redirect(array('controller' => 'Feeds', 'action' => 'index', 'all'));
}
}
}
public function add() {
if ($this->request->is('post')) {
$error = false;
@ -135,7 +178,7 @@ class FeedsController extends AppController {
}
$result = $this->Feed->save($feed);
if ($result) {
$feedCache = APP . 'tmp' . DS . 'cache' . DS . 'feeds' . DS . intval($feed['Feed']['id']) . '.cache';
$feedCache = APP . 'tmp' . DS . 'cache' . DS . 'feeds' . DS . intval($feedId) . '.cache';
if (file_exists($feedCache)) {
unlink($feedCache);
}
@ -307,6 +350,10 @@ class FeedsController extends AppController {
$params = array();
// params is passed as reference here, the pagination happens in the method, which isn't ideal but considering the performance gains here it's worth it
$resultArray = $this->Feed->getFreetextFeed($feed, $HttpSocket, $feed['Feed']['source_format'], $currentPage, 60, $params);
// we want false as a valid option for the split fetch, but we don't want it for the preview
if ($resultArray == false) {
$resultArray = array();
}
$this->params->params['paging'] = array($this->modelClass => $params);
$resultArray = $this->Feed->getFreetextFeedCorrelations($resultArray);
// remove all duplicates
@ -331,6 +378,10 @@ class FeedsController extends AppController {
if ($feed['Feed']['source_format'] != 'csv') throw new MethodNotAllowedException('Invalid feed type.');
$HttpSocket = $syncTool->setupHttpSocketFeed($feed);
$resultArray = $this->Feed->getFreetextFeed($feed, $HttpSocket, $feed['Feed']['source_format'], $currentPage);
// we want false as a valid option for the split fetch, but we don't want it for the preview
if ($resultArray == false) {
$resultArray = array();
}
$resultArray = $this->Feed->getFreetextFeedCorrelations($resultArray);
// remove all duplicates
foreach ($resultArray as $k => $v) {

View File

@ -173,7 +173,7 @@ class ComplexTypeTool {
if (count($compositeParts) == 2) {
if ($this->__resolveFilename($compositeParts[0])) {
foreach ($this->__hexHashTypes as $k => $v) {
if (strlen($compositeParts[1]) == $k && preg_match("#[0-9a-f]{" . $k . "}$#i", $compositeParts[1])) return array('types' => $v['composite'], 'to_ids' => true, 'default_type' => $v['composite'][0]);
if (strlen($compositeParts[1]) == $k && preg_match("#[0-9a-f]{" . $k . "}$#i", $compositeParts[1])) return array('types' => $v['composite'], 'to_ids' => true, 'default_type' => $v['composite'][0], 'value' => $input);
}
if (preg_match('#^[0-9]+:[0-9a-zA-Z\/\+]+:[0-9a-zA-Z\/\+]+$#', $compositeParts[1]) && !preg_match('#^[0-9]{1,2}:[0-9]{1,2}:[0-9]{1,2}$#', $compositeParts[1])) {
return array('types' => array('ssdeep'), 'to_ids' => true, 'default_type' => 'filename|ssdeep', 'value' => $input);
@ -184,7 +184,7 @@ class ComplexTypeTool {
// check for hashes
foreach ($this->__hexHashTypes as $k => $v) {
if (strlen($input) == $k && preg_match("#[0-9a-f]{" . $k . "}$#i", $input)) return array('types' => $v['single'], 'to_ids' => true, 'default_type' => $v['single'][0]);
if (strlen($input) == $k && preg_match("#[0-9a-f]{" . $k . "}$#i", $input)) return array('types' => $v['single'], 'to_ids' => true, 'default_type' => $v['single'][0], 'value' => $input);
}
if (preg_match('#^[0-9]+:[0-9a-zA-Z\/\+]+:[0-9a-zA-Z\/\+]+$#', $input) && !preg_match('#^[0-9]{1,2}:[0-9]{1,2}:[0-9]{1,2}$#', $input)) return array('types' => array('ssdeep'), 'to_ids' => true, 'default_type' => 'ssdeep', 'value' => $input);
$inputRefanged = $input;

View File

@ -120,6 +120,9 @@ class Feed extends AppModel {
$params = $customPagination->createPaginationRules($data, array('page' => $page, 'limit' => $limit), 'Feed', $sort = false);
if (!empty($page) && $page != 'all') {
$start = ($page - 1) * $limit;
if ($start > count($data)) {
return false;
}
$data = array_slice($data, $start, $limit);
}
$data = implode("\n", $data);
@ -459,28 +462,33 @@ class Feed extends AppModel {
$job->id = $jobId;
$job->saveField('message', 'Fetching data.');
}
$data = $this->getFreetextFeed($this->data, $HttpSocket, $this->data['Feed']['source_format']);
foreach ($data as $key => $value) {
$data[$key] = array(
$temp = $this->getFreetextFeed($this->data, $HttpSocket, $this->data['Feed']['source_format'], 'all');
foreach ($temp as $key => $value) {
$data[] = array(
'category' => $value['category'],
'type' => $value['default_type'],
'value' => $value['value'],
'to_ids' => $value['to_ids']
);
}
if ($jobId) {
$job->saveField('progress', 50);
$job->saveField('message', 'Saving data.');
}
$result = $this->saveFreetextFeedData($this->data, $data, $user);
$message = 'Job complete.';
if ($result !== true) {
return false;
}
if ($jobId) {
$job->saveField('progress', '100');
$job->saveField('message', 'Job complete.');
}
}
return $result;
}
public function saveFreetextFeedData($feed, $data, $user) {
public function saveFreetextFeedData($feed, $data, $user, $jobId = false) {
$this->Event = ClassRegistry::init('Event');
$event = false;
if ($feed['Feed']['fixed_event'] && $feed['Feed']['event_id']) {
@ -515,7 +523,7 @@ class Feed extends AppModel {
$to_delete = array();
foreach ($data as $k => $dataPoint) {
foreach ($event['Attribute'] as $attribute_key => $attribute) {
if ($dataPoint['value'] == $attribute['value']) {
if ($dataPoint['value'] == $attribute['value'] && $dataPoint['type'] == $attribute['type'] && $attribute['category'] == $dataPoint['category']) {
unset($data[$k]);
unset($event['Attribute'][$attribute_key]);
}
@ -540,8 +548,16 @@ class Feed extends AppModel {
$data[$key]['sharing_group_id'] = $feed['Feed']['sharing_group_id'];
$data[$key]['to_ids'] = $feed['Feed']['override_ids'] ? 0 : $data[$key]['to_ids'];
}
if (!$this->Event->Attribute->saveMany($data)) {
return 'Could not save the parsed attributes.';
if ($jobId) {
$job = ClassRegistry::init('Job');
$job->id = $jobId;
}
$data = array_chunk($data, 100);
foreach ($data as $k => $chunk) {
$this->Event->Attribute->saveMany($chunk);
if ($jobId) {
$job->saveField('progress', 50 + round((50 * ((($k + 1) * 100) / count($data)))));
}
}
if ($feed['Feed']['publish']) {
$this->Event->publishRouter($event['Event']['id'], null, $user);

View File

@ -312,6 +312,7 @@
case 'feeds': ?>
<li id='liindex'><a href="<?php echo $baseurl;?>/feeds/index">List Feeds</a></li>
<li id='liadd'><a href="<?php echo $baseurl;?>/feeds/add">Add Feed</a></li>
<li id='liadd'><a href="<?php echo $baseurl;?>/feeds/importFeeds">Import Feeds from JSON</a></li>
<?php if ($menuItem === 'edit'): ?>
<li class="active"><a href="#">Edit Feed</a></li>
<?php elseif ($menuItem === 'previewIndex'): ?>

View File

@ -0,0 +1,25 @@
<div class="feed form">
<?php echo $this->Form->create('Feed');?>
<fieldset>
<legend>Paste feed data</legend>
<p>Paste a MISP feed metadata JSON below to add feeds.</p>
<div>
<?php
echo $this->Form->input('json', array(
'div' => 'input clear',
'placeholder' => 'Feed metadata JSON',
'class' => 'form-control span6',
'type' => 'textarea',
'rows' => 18
));
?>
</div>
</fieldset>
<?php
echo $this->Form->button('Add', array('class' => 'btn btn-primary'));
echo $this->Form->end();
?>
</div>
<?php
echo $this->element('side_menu', array('menuList' => 'feeds', 'menuItem' => 'import'));
?>

View File

@ -15,6 +15,11 @@
echo $this->Paginator->next(__('next') . ' &raquo;', array('tag' => 'li', 'escape' => false), null, array('tag' => 'li', 'class' => 'next disabled', 'escape' => false, 'disabledTag' => 'span'));
?>
</ul>
</div>
<div class="tabMenuFixedContainer" style="display:inline-block;">
<span class="tabMenuFixed tabMenuFixedCenter tabMenuSides useCursorPointer <?php echo $scope == 'default' ? 'tabMenuActive' : ''; ?>" onclick="window.location='/feeds/index/scope:default'">Default feeds</span>
<span class="tabMenuFixed tabMenuFixedCenter tabMenuSides useCursorPointer <?php echo $scope == 'custom' ? 'tabMenuActive' : ''; ?> " onclick="window.location='/feeds/index/scope:custom'">Custom Feeds</span>
<span class="tabMenuFixed tabMenuFixedCenter tabMenuSides useCursorPointer <?php echo $scope == 'all' ? 'tabMenuActive' : ''; ?> " onclick="window.location='/feeds/index/scope:all'">All Feeds</span>
</div>
<table class="table table-striped table-hover table-condensed">
<tr>