Merge branch 'develop' into 2.4

pull/7840/head v2.4.150
iglocska 2021-10-12 16:26:05 +02:00
commit 1e930903b2
No known key found for this signature in database
GPG Key ID: BEA224F1FEF113AC
52 changed files with 1734 additions and 1597 deletions

View File

@ -70,9 +70,8 @@ jobs:
# Repo is missing for unknown reason
LC_ALL=C.UTF-8 sudo apt-add-repository ppa:ondrej/php -y
sudo apt-get -y install curl python3 python3-zmq python3-requests python3-pip python3-nose python3-redis python3-lxml apache2 libapache2-mod-php$php_version libfuzzy-dev
sudo pip3 install virtualenv # virtualenv must be instaled from pip and not from ubuntu pacckages
sudo pip3 install --upgrade -r requirements.txt
curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python -
sudo pip3 install virtualenv # virtualenv must be instaled from pip and not from ubuntu packages
curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/install-poetry.py | python -
sudo chown $USER:www-data $HOME/.composer
pushd app
sudo -H -u $USER php composer.phar install --no-progress
@ -167,12 +166,16 @@ jobs:
- name: Update Galaxies
run: sudo -E su $USER -c 'app/Console/cake Admin updateGalaxies'
- name: Update Taxonomies
run: sudo -E su $USER -c 'app/Console/cake Admin updateTaxonomies'
- name: Update Warninglists
run: sudo -E su $USER -c 'app/Console/cake Admin updateWarningLists'
- name: Update Noticelists
run: sudo -E su $USER -c 'app/Console/cake Admin updateNoticeLists'
- name: Update Object Templates
run: sudo -E su $USER -c 'app/Console/cake Admin updateObjectTemplates 1'
@ -215,6 +218,13 @@ jobs:
cat tests/keys.py
popd
- name: Build test
run: |
. ./venv/bin/activate
pip install git+https://github.com/kbandla/pydeep.git
pushd tests
./build-test.sh
- name: Run PHP tests
run: |
./app/Vendor/bin/parallel-lint --exclude app/Lib/cakephp/ --exclude app/Vendor/ --exclude app/Lib/random_compat/ -e php,ctp app/
@ -222,7 +232,7 @@ jobs:
- name: Run tests
run: |
source $HOME/.poetry/env # enable poetry binary
export PATH=$HOME/.local/env:$PATH # enable poetry binary
# Ensure the perms of config files
sudo chown -R $USER:www-data `pwd`/app/Config

2
PyMISP

@ -1 +1 @@
Subproject commit e07321bfa90d2259e1489773cf0b8769cdcf675c
Subproject commit 506410709304cc7832b364228c52572681a2c603

View File

@ -1 +1 @@
{"major":2, "minor":4, "hotfix":149}
{"major":2, "minor":4, "hotfix":150}

View File

@ -397,20 +397,18 @@ class AdminShell extends AppShell
$cli_user = array('id' => 0, 'email' => 'SYSTEM', 'Organisation' => array('name' => 'SYSTEM'));
if (empty($setting_name) || $value === null) {
die('Usage: ' . $this->Server->command_line_functions['console_admin_tasks']['data']['Set setting'] . PHP_EOL);
}
$setting = $this->Server->getSettingData($setting_name);
if (empty($setting)) {
$message = 'Invalid setting "' . $setting_name . '". Please make sure that the setting that you are attempting to change exists and if a module parameter, the modules are running.' . PHP_EOL;
$this->error(__('Setting change rejected.'), $message);
}
$result = $this->Server->serverSettingsEditValue($cli_user, $setting, $value, $this->params['force']);
if ($result === true) {
echo 'Setting "' . $setting_name . '" changed to ' . $value . PHP_EOL;
} else {
$setting = $this->Server->getSettingData($setting_name);
if (empty($setting)) {
$message = 'Invalid setting "' . $setting_name . '". Please make sure that the setting that you are attempting to change exists and if a module parameter, the modules are running.' . PHP_EOL;
$this->error(__('Setting change rejected.'), $message);
}
$result = $this->Server->serverSettingsEditValue($cli_user, $setting, $value, $this->params['force']);
if ($result === true) {
echo 'Setting "' . $setting_name . '" changed to ' . $value . PHP_EOL;
} else {
$message = __("The setting change was rejected. MISP considers the requested setting value as invalid and would lead to the following error:\n\n\"%s\"\n\nIf you still want to force this change, please supply the --force argument.\n", $result);
$this->error(__('Setting change rejected.'), $message);
}
echo PHP_EOL;
$message = __("The setting change was rejected. MISP considers the requested setting value as invalid and would lead to the following error:\n\n\"%s\"\n\nIf you still want to force this change, please supply the --force argument.\n", $result);
$this->error(__('Setting change rejected.'), $message);
}
}

View File

@ -8,6 +8,7 @@ require_once 'AppShell.php';
* @property Event $Event
* @property Job $Job
* @property Tag $Tag
* @property Server $Server
*/
class EventShell extends AppShell
{
@ -279,8 +280,7 @@ class EventShell extends AppShell
public function contactemail()
{
$this->ConfigLoad->execute();
if (empty($this->args[0]) || empty($this->args[1]) || empty($this->args[2]) ||
if (empty($this->args[0]) || empty($this->args[1]) || !isset($this->args[2]) ||
empty($this->args[3]) || empty($this->args[4])) {
die('Usage: ' . $this->Server->command_line_functions['event_management_tasks']['data']['Contact email'] . PHP_EOL);
}
@ -289,11 +289,11 @@ class EventShell extends AppShell
$message = $this->args[1];
$all = $this->args[2];
$userId = $this->args[3];
$processId = $this->args[4];
$jobId = $this->args[4];
$user = $this->getUser($userId);
$result = $this->Event->sendContactEmail($id, $message, $all, $user);
$this->Job->saveStatus($processId, $result);
$this->Job->saveStatus($jobId, $result);
}
public function postsemail()
@ -522,17 +522,15 @@ class EventShell extends AppShell
public function processfreetext()
{
$this->ConfigLoad->execute();
if (empty($this->args[0])) {
die('Usage: ' . $this->Server->command_line_functions['event_management_tasks']['data']['Process free text'] . PHP_EOL);
}
$inputFile = $this->args[0];
$tempdir = new Folder(APP . 'tmp/cache/ingest', true, 0750);
$tempFile = new File(APP . 'tmp/cache/ingest' . DS . $inputFile);
$inputData = $tempFile->read();
$inputData = json_decode($inputData, true);
$tempFile->delete();
$inputFile = $inputFile[0] === '/' ? $inputFile : APP . 'tmp/cache/ingest' . DS . $inputFile;
$inputData = FileAccessTool::readFromFile($inputFile);
$inputData = $this->Event->jsonDecode($inputData);
FileAccessTool::deleteFile($inputFile);
Configure::write('CurrentUserId', $inputData['user']['id']);
$this->Event->processFreeTextData(
$inputData['user'],
@ -548,16 +546,15 @@ class EventShell extends AppShell
public function processmoduleresult()
{
$this->ConfigLoad->execute();
if (empty($this->args[0])) {
die('Usage: ' . $this->Server->command_line_functions['event_management_tasks']['data']['Process module result'] . PHP_EOL);
}
$inputFile = $this->args[0];
$tempDir = new Folder(APP . 'tmp/cache/ingest', true, 0750);
$tempFile = new File(APP . 'tmp/cache/ingest' . DS . $inputFile);
$inputData = json_decode($tempFile->read(), true);
$tempFile->delete();
$inputFile = $inputFile[0] === '/' ? $inputFile : APP . 'tmp/cache/ingest' . DS . $inputFile;
$inputData = FileAccessTool::readFromFile($inputFile);
$inputData = $this->Event->jsonDecode($inputData);
FileAccessTool::deleteFile($inputFile);
Configure::write('CurrentUserId', $inputData['user']['id']);
$this->Event->processModuleResultsData(
$inputData['user'],

View File

@ -33,7 +33,7 @@ class AppController extends Controller
public $helpers = array('OrgImg', 'FontAwesome', 'UserName', 'DataPathCollector');
private $__queryVersion = '130';
private $__queryVersion = '131';
public $pyMispVersion = '2.4.148';
public $phpmin = '7.2';
public $phprec = '7.4';

View File

@ -101,6 +101,7 @@ class AttributesController extends AppController
$this->set('attrDescriptions', $this->Attribute->fieldDescriptions);
$this->set('typeDefinitions', $this->Attribute->typeDefinitions);
$this->set('categoryDefinitions', $this->Attribute->categoryDefinitions);
$this->set('distributionLevels', $this->Attribute->distributionLevels);
}
public function add($eventId = false)
@ -111,9 +112,6 @@ class AttributesController extends AppController
if ($eventId === false) {
throw new MethodNotAllowedException(__('No event ID set.'));
}
if (!$this->userRole['perm_add']) {
throw new MethodNotAllowedException(__('You do not have permissions to create attributes'));
}
$event = $this->Attribute->Event->fetchSimpleEvent($this->Auth->user(), $eventId, ['contain' => ['Orgc']]);
if (!$event) {
throw new NotFoundException(__('Invalid event'));
@ -169,19 +167,19 @@ class AttributesController extends AppController
$inserted_ids = array();
foreach ($attributes as $k => $attribute) {
$validationErrors = array();
$this->Attribute->captureAttribute($attribute, $event['Event']['id'], $this->Auth->user(), false, false, false, $validationErrors, $this->params['named']);
$this->Attribute->captureAttribute($attribute, $event['Event']['id'], $this->Auth->user(), false, false, $event, $validationErrors, $this->params['named']);
if (empty($validationErrors)) {
$inserted_ids[] = $this->Attribute->id;
$successes +=1;
$successes++;
} else {
$fails["attribute_" . $k] = $validationErrors;
}
}
if (!empty($successes)) {
if ($successes !== 0) {
$this->Attribute->Event->unpublishEvent($event['Event']['id']);
}
if ($this->_isRest()) {
if (!empty($successes)) {
if ($successes !== 0) {
$attributes = $this->Attribute->find('all', array(
'recursive' => -1,
'conditions' => array('Attribute.id' => $inserted_ids),
@ -191,7 +189,7 @@ class AttributesController extends AppController
)
)
));
if (count($attributes) == 1) {
if (count($attributes) === 1) {
$attributes = $attributes[0];
} else {
$result = array('Attribute' => array());
@ -286,15 +284,9 @@ class AttributesController extends AppController
$categories = $this->_arrayToValuesIndexArray($categories);
$this->set('categories', $categories);
$this->loadModel('SharingGroup');
$sgs = $this->SharingGroup->fetchAllAuthorised($this->Auth->user(), 'name', 1);
$sgs = $this->Attribute->SharingGroup->fetchAllAuthorised($this->Auth->user(), 'name', true);
$this->set('sharingGroups', $sgs);
$initialDistribution = 5;
$configuredDistribution = Configure::check('MISP.default_attribute_distribution');
if ($configuredDistribution != null && $configuredDistribution != 'event') {
$initialDistribution = $configuredDistribution;
}
$this->set('initialDistribution', $initialDistribution);
$this->set('initialDistribution', $this->Attribute->defaultDistribution());
$fieldDesc = array();
$distributionLevels = $this->Attribute->distributionLevels;
if (empty($sgs)) {
@ -317,7 +309,7 @@ class AttributesController extends AppController
$this->set('typeDefinitions', $this->Attribute->typeDefinitions);
$this->set('categoryDefinitions', $this->Attribute->categoryDefinitions);
$this->set('event', $event);
$this->set('action', $this->action);
$this->set('action', $this->request->action);
}
public function download($id = null)
@ -435,7 +427,7 @@ class AttributesController extends AppController
'category' => $this->request->data['Attribute']['category'],
'type' => 'attachment',
'event_id' => $event['Event']['id'],
'data' => base64_encode($tmpfile->read()),
'data_raw' => $tmpfile->read(),
'comment' => $this->request->data['Attribute']['comment'],
'to_ids' => 0,
'distribution' => $this->request->data['Attribute']['distribution'],
@ -1649,6 +1641,7 @@ class AttributesController extends AppController
$this->set('isSearch', 1);
$this->set('attrDescriptions', $this->Attribute->fieldDescriptions);
$this->set('shortDist', $this->Attribute->shortDist);
$this->set('distributionLevels', $this->Attribute->distributionLevels);
$this->render('index');
}
if (isset($attributeTags)) {

View File

@ -482,12 +482,7 @@ class EventReportsController extends AppController
{
$distributionLevels = $this->EventReport->Event->Attribute->distributionLevels;
$this->set('distributionLevels', $distributionLevels);
$initialDistribution = 5;
$configuredDistribution = Configure::check('MISP.default_attribute_distribution');
if ($configuredDistribution != null && $configuredDistribution != 'event') {
$initialDistribution = $configuredDistribution;
}
$this->set('initialDistribution', $initialDistribution);
$this->set('initialDistribution', $this->EventReport->Event->Attribute->defaultDistribution());
}
private function __injectSharingGroupsDataToViewContext()

View File

@ -1959,9 +1959,6 @@ class EventsController extends AppController
public function add()
{
if (!$this->userRole['perm_add']) {
throw new MethodNotAllowedException(__('You do not have permissions to create events.'));
}
$sgs = $this->Event->SharingGroup->fetchAllAuthorised($this->Auth->user(), 'name', 1);
if ($this->request->is('post')) {
if ($this->_isRest()) {
@ -1984,13 +1981,13 @@ class EventsController extends AppController
}
if (!empty($this->data)) {
if (!isset($this->request->data['Event']['distribution'])) {
$this->request->data['Event']['distribution'] = Configure::read('MISP.default_event_distribution') ? Configure::read('MISP.default_event_distribution') : 0;
$this->request->data['Event']['distribution'] = Configure::read('MISP.default_event_distribution') ?: 0;
}
if (!isset($this->request->data['Event']['analysis'])) {
$this->request->data['Event']['analysis'] = 0;
}
if (!isset($this->request->data['Event']['threat_level_id'])) {
$this->request->data['Event']['threat_level_id'] = Configure::read('MISP.default_event_threat_level') ? Configure::read('MISP.default_event_threat_level') : 4;
$this->request->data['Event']['threat_level_id'] = Configure::read('MISP.default_event_threat_level') ?: 4;
}
if (!isset($this->request->data['Event']['date'])) {
$this->request->data['Event']['date'] = date('Y-m-d');
@ -2031,11 +2028,8 @@ class EventsController extends AppController
$validationErrors = array();
$created_id = 0;
$add = $this->Event->_add($this->request->data, $this->_isRest(), $this->Auth->user(), '', null, false, null, $created_id, $validationErrors);
if ($add === true && !is_numeric($add)) {
if ($add === true) {
if ($this->_isRest()) {
if ($add === 'blocked') {
throw new ForbiddenException(__('Event blocked by local blocklist.'));
}
// REST users want to see the newly created event
$metadata = $this->request->param('named.metadata');
$results = $this->Event->fetchEvent($this->Auth->user(), ['eventid' => $created_id, 'metadata' => $metadata]);
@ -2056,6 +2050,15 @@ class EventsController extends AppController
$this->response->send();
throw new NotFoundException(__('Event already exists, if you would like to edit it, use the url in the location header.'));
}
if ($add === 'blocked') {
throw new ForbiddenException(__('Event blocked by organisation blocklist.'));
} else if ($add === 'Blocked by blocklist') {
throw new ForbiddenException(__('Event blocked by event blocklist.'));
} else if ($add === 'Blocked by event block rules') {
throw new ForbiddenException(__('Blocked by event block rules.'));
}
// # TODO i18n?
return $this->RestResponse->saveFailResponse('Events', 'add', false, $validationErrors, $this->response->type());
} else {
@ -2098,9 +2101,9 @@ class EventsController extends AppController
}
// combobox for risks
$threat_levels = $this->Event->ThreatLevel->find('all');
$this->set('threatLevels', Set::combine($threat_levels, '{n}.ThreatLevel.id', '{n}.ThreatLevel.name'));
$fieldDesc['threat_level_id'] = Set::combine($threat_levels, '{n}.ThreatLevel.id', '{n}.ThreatLevel.description');
$threat_levels = array_column($this->Event->ThreatLevel->find('all'), 'ThreatLevel');
$this->set('threatLevels', array_column($threat_levels, 'name', 'id'));
$fieldDesc['threat_level_id'] = array_column($threat_levels, 'description', 'id');
// combobox for analysis
$this->set('sharingGroups', $sgs);
@ -2110,9 +2113,7 @@ class EventsController extends AppController
foreach ($analysisLevels as $key => $value) {
$fieldDesc['analysis'][$key] = $this->Event->analysisDescriptions[$key]['formdesc'];
}
if (!$this->_isRest()) {
$this->Flash->info(__('The event created will be visible to the organisations having an account on this platform, but not synchronised to other MISP instances until it is published.'));
}
$this->Flash->info(__('The event created will be visible to the organisations having an account on this platform, but not synchronised to other MISP instances until it is published.'));
$this->set('fieldDesc', $fieldDesc);
if (isset($this->params['named']['extends'])) {
$this->set('extends_uuid', $this->params['named']['extends']);
@ -3692,11 +3693,7 @@ class EventsController extends AppController
{
if ($distribution === false) {
if (Configure::read('MISP.default_attribute_distribution') != null) {
if (Configure::read('MISP.default_attribute_distribution') == 'event') {
$distribution = 5;
} else {
$distribution = Configure::read('MISP.default_attribute_distribution');
}
$distribution = $this->Event->Attribute->defaultDistribution();
} else {
$distribution = 0;
}

View File

@ -673,7 +673,7 @@ class FeedsController extends AppController
$passedArgs = array();
App::uses('SyncTool', 'Tools');
$syncTool = new SyncTool();
$HttpSocket = $syncTool->setupHttpSocketFeed($feed);
$HttpSocket = $syncTool->setupHttpSocketFeed();
try {
$events = $this->Feed->getManifest($feed, $HttpSocket);
} catch (Exception $e) {
@ -682,28 +682,25 @@ class FeedsController extends AppController
}
if (!empty($this->params['named']['searchall'])) {
$searchAll = trim(strtolower($this->params['named']['searchall']));
$searchAll = trim(mb_strtolower($this->params['named']['searchall']));
foreach ($events as $uuid => $event) {
$found = false;
if ($uuid === $searchAll) {
$found = true;
continue;
}
if (!$found && strpos(strtolower($event['info']), $searchAll) !== false) {
$found = true;
if (strpos(mb_strtolower($event['info']), $searchAll) !== false) {
continue;
}
if (!$found && strpos(strtolower($event['Orgc']['name']), $searchAll) !== false) {
$found = true;
if (strpos(mb_strtolower($event['Orgc']['name']), $searchAll) !== false) {
continue;
}
if (!$found && !empty($event['Tag'])) {
if (!empty($event['Tag'])) {
foreach ($event['Tag'] as $tag) {
if (strpos(strtolower($tag['name']), $searchAll) !== false) {
$found = true;
if (strpos(mb_strtolower($tag['name']), $searchAll) !== false) {
continue 2;
}
}
}
if (!$found) {
unset($events[$uuid]);
}
unset($events[$uuid]);
}
}
foreach ($filterParams as $k => $filter) {
@ -722,9 +719,7 @@ class FeedsController extends AppController
$params = $customPagination->createPaginationRules($events, $this->passedArgs, $this->alias);
$this->params->params['paging'] = array($this->modelClass => $params);
$events = $customPagination->sortArray($events, $params, true);
if (is_array($events)) {
$customPagination->truncateByPagination($events, $params);
}
$customPagination->truncateByPagination($events, $params);
if ($this->_isRest()) {
return $this->RestResponse->viewData($events, $this->response->type());
}

View File

@ -1,16 +1,16 @@
<?php
App::uses('AppController', 'Controller');
App::uses('AppController', 'Controller', 'CRUD');
class NewsController extends AppController
{
public $components = array('Session', 'RequestHandler');
public $paginate = array(
'limit' => 5,
'maxLimit' => 9999, // LATER we will bump here on a problem once we have more than 9999 events <- no we won't, this is the max a user van view/page.
'order' => array(
'News.id' => 'DESC'
),
'limit' => 5,
'maxLimit' => 9999, // LATER we will bump here on a problem once we have more than 9999 events <- no we won't, this is the max a user van view/page.
'order' => array(
'News.id' => 'DESC'
),
);
public function index()
@ -69,22 +69,15 @@ class NewsController extends AppController
$this->request->data = $this->News->read(null, $id);
$this->set('newsItem', $this->request->data);
}
$this->render('add');
}
public function delete($id)
{
if (!$this->request->is('post')) {
throw new MethodNotAllowedException();
$this->defaultModel = 'News';
$this->CRUD->delete($id);
if ($this->IndexFilter->isRest()) {
return $this->restResponsePayload;
}
$this->News->id = $id;
if (!$this->News->exists()) {
throw new NotFoundException('Invalid news item');
}
if ($this->News->delete()) {
$this->Flash->success(__('News item deleted.'));
$this->redirect(array('action' => 'index'));
}
$this->Flash->error(__('News item could not be deleted.'));
$this->redirect(array('action' => 'index'));
}
}

View File

@ -207,10 +207,7 @@ class AttachmentTool
} else {
$path = $this->attachmentDir() . DS . $path;
$file = new File($path, true);
if (!$file->write($data)) {
throw new Exception("Could not save attachment to file '$path'.");
}
FileAccessTool::writeToFile($path, $data, true);
}
return true;
@ -309,46 +306,32 @@ class AttachmentTool
{
$tempDir = $this->tempDir();
$contentsFile = new File($tempDir . DS . $md5, true);
if (!$contentsFile->write($content)) {
throw new Exception("Could not write content to file '{$contentsFile->path}'.");
}
$contentsFile->close();
FileAccessTool::writeToFile($tempDir . DS . $md5, $content);
FileAccessTool::writeToFile($tempDir . DS . $md5 . '.filename.txt', $originalFilename);
$fileNameFile = new File($tempDir . DS . $md5 . '.filename.txt', true);
if (!$fileNameFile->write($originalFilename)) {
throw new Exception("Could not write original file name to file '{$fileNameFile->path}'.");
}
$fileNameFile->close();
$zipFile = new File($tempDir . DS . $md5 . '.zip');
$zipFile = $tempDir . DS . $md5 . '.zip';
$exec = [
'zip',
'-j', // junk (don't record) directory names
'-P', // use standard encryption
self::ZIP_PASSWORD,
escapeshellarg($zipFile->path),
escapeshellarg($contentsFile->path),
escapeshellarg($fileNameFile->path),
escapeshellarg($zipFile),
escapeshellarg($tempDir . DS . $md5),
escapeshellarg($tempDir . DS . $md5 . '.filename.txt'),
];
try {
$this->execute($exec);
$zipContent = $zipFile->read();
if ($zipContent === false) {
throw new Exception("Could not read content of newly created ZIP file.");
}
return $zipContent;
return FileAccessTool::readFromFile($zipFile);
} catch (Exception $e) {
throw new Exception("Could not create encrypted ZIP file '{$zipFile->path}'.", 0, $e);
throw new Exception("Could not create encrypted ZIP file '$zipFile'.", 0, $e);
} finally {
$fileNameFile->delete();
$contentsFile->delete();
$zipFile->delete();
FileAccessTool::deleteFile($tempDir . DS . $md5);
FileAccessTool::deleteFile($tempDir . DS . $md5 . '.filename.txt');
FileAccessTool::deleteFile($zipFile);
}
}
@ -397,6 +380,56 @@ class AttachmentTool
return $this->executeAndParseJsonOutput([$pythonBin, self::ADVANCED_EXTRACTION_SCRIPT_PATH, '-c']);
}
/**
* @param string $data
* @param int $maxWidth
* @param int $maxHeight
* @return string
* @throws Exception
*/
public function resizeImage($data, $maxWidth, $maxHeight)
{
$image = imagecreatefromstring($data);
if ($image === false) {
throw new Exception("Image is not valid.");
}
$currentWidth = imagesx($image);
$currentHeight = imagesy($image);
// Compute thumbnail size with keeping ratio
if ($currentWidth > $currentHeight) {
$newWidth = min($currentWidth, $maxWidth);
$divisor = $currentWidth / $newWidth;
$newHeight = floor($currentHeight / $divisor);
} else {
$newHeight = min($currentHeight, $maxHeight);
$divisor = $currentHeight / $newHeight;
$newWidth = floor($currentWidth / $divisor);
}
$imageThumbnail = imagecreatetruecolor($newWidth, $newHeight);
// Allow transparent background
imagealphablending($imageThumbnail, false);
imagesavealpha($imageThumbnail, true);
$transparent = imagecolorallocatealpha($imageThumbnail, 255, 255, 255, 127);
imagefilledrectangle($imageThumbnail, 0, 0, $newWidth, $newHeight, $transparent);
// Resize image
imagecopyresampled($imageThumbnail, $image, 0, 0, 0, 0, $newWidth, $newHeight, $currentWidth, $currentHeight);
imagedestroy($image);
// Output image to string
ob_start();
imagepng($imageThumbnail, null, 9);
$imageData = ob_get_contents();
ob_end_clean();
imagedestroy($imageThumbnail);
return $imageData;
}
private function tempFileName()
{
$randomName = (new RandomTool())->random_str(false, 12);

View File

@ -4,13 +4,16 @@ class FileAccessTool
{
/**
* Creates temporary file, but you have to delete it after use.
* @param string $dir
* @param string|null $dir
* @param string $prefix
* @return string
* @throws Exception
*/
public static function createTempFile($dir, $prefix = 'MISP')
public static function createTempFile($dir = null, $prefix = 'MISP')
{
if ($dir === null) {
$dir = Configure::read('MISP.tmpdir') ?: sys_get_temp_dir();
}
$tempFile = tempnam($dir, $prefix);
if ($tempFile === false) {
throw new Exception("An error has occurred while attempt to create a temporary file in path `$dir`.");
@ -26,10 +29,20 @@ class FileAccessTool
*/
public static function readFromFile($file, $fileSize = -1)
{
$fileSize = $fileSize === -1 ? null : $fileSize;
$content = file_get_contents($file, false, null, 0, $fileSize);
if ($fileSize === -1) {
$content = file_get_contents($file);
} else {
$content = file_get_contents($file, false, null, 0, $fileSize);
}
if ($content === false) {
throw new Exception("An error has occurred while attempt to read file `$file`.");
if (!file_exists($file)) {
$message = "file doesn't exists";
} else if (!is_readable($file)) {
$message = "file is not readable";
} else {
$message = 'unknown error';
}
throw new Exception("An error has occurred while attempt to read file `$file`: $message.");
}
return $content;
}
@ -37,13 +50,39 @@ class FileAccessTool
/**
* @param string $file
* @param mixed $content
* @param bool $createFolder
* @throws Exception
*/
public static function writeToFile($file, $content)
public static function writeToFile($file, $content, $createFolder = false)
{
if (file_put_contents($file, $content, LOCK_EX) === false) {
throw new Exception("An error has occurred while attempt to write to file `$file`.");
$dir = dirname($file);
if ($createFolder && !is_dir($dir)) {
if (!mkdir($dir, 0766, true)) {
throw new Exception("An error has occurred while attempt to create directory `$dir`.");
}
}
if (file_put_contents($file, $content, LOCK_EX) === false) {
$freeSpace = disk_free_space($dir);
throw new Exception("An error has occurred while attempt to write to file `$file`. Maybe not enough space? ($freeSpace bytes left)");
}
}
/**
* @param mixed $content
* @param string|null $dir
* @return string Path to temp file
* @throws Exception
*/
public static function writeToTempFile($content, $dir = null)
{
$tempFile = self::createTempFile($dir);
if (file_put_contents($tempFile, $content) === false) {
self::deleteFile($tempFile);
$freeSpace = disk_free_space(dirname($tempFile));
throw new Exception("An error has occurred while attempt to write to file `$tempFile`. Maybe not enough space? ($freeSpace bytes left)");
}
return $tempFile;
}
/**

View File

@ -38,7 +38,7 @@ class SyncTool
return $this->createHttpSocket($params);
}
public function setupHttpSocketFeed($feed = null)
public function setupHttpSocketFeed()
{
return $this->createHttpSocket(['compress' => true]);
}

View File

@ -2577,9 +2577,10 @@ class AppModel extends Model
return true;
}
public function publishKafkaNotification($topicName, $data, $action = false) {
$kafkaTopic = Configure::read('Plugin.Kafka_' . $topicName . '_notifications_topic');
if (Configure::read('Plugin.Kafka_enable') && Configure::read('Plugin.Kafka_' . $topicName . '_notifications_enable') && !empty($kafkaTopic)) {
public function publishKafkaNotification($topicName, $data, $action = false)
{
$kafkaTopic = $this->kafkaTopic($topicName);
if ($kafkaTopic) {
$this->getKafkaPubTool()->publishJson($kafkaTopic, $data, $action);
}
}
@ -3275,4 +3276,23 @@ class AppModel extends Model
}
return $this->Log;
}
/**
* @param string $name
* @return string|null Null when Kafka is not enabled, topic is not enabled or topic is not defined
*/
protected function kafkaTopic($name)
{
static $kafkaEnabled;
if ($kafkaEnabled === null) {
$kafkaEnabled = (bool)Configure::read('Plugin.Kafka_enable');
}
if ($kafkaEnabled) {
if (!Configure::read("Plugin.Kafka_{$name}_notifications_enable")) {
return null;
}
return Configure::read("Plugin.Kafka_{$name}_notifications_topic") ?: null;
}
return null;
}
}

View File

@ -444,13 +444,18 @@ class Attribute extends AppModel
}
$result = true;
// if the 'data' field is set on the $this->data then save the data to the correct file
if (isset($this->data['Attribute']['type']) && $this->typeIsAttachment($this->data['Attribute']['type']) && !empty($this->data['Attribute']['data'])) {
$result = $result && $this->saveBase64EncodedAttachment($this->data['Attribute']); // TODO : is this correct?
if (isset($this->data['Attribute']['type']) && $this->typeIsAttachment($this->data['Attribute']['type'])) {
if (isset($this->data['Attribute']['data_raw'])) {
$this->data['Attribute']['data'] = $this->data['Attribute']['data_raw'];
unset($this->data['Attribute']['data_raw']);
} elseif (isset($this->data['Attribute']['data'])) {
$this->data['Attribute']['data'] = base64_decode($this->data['Attribute']['data']);
}
$result = $this->saveAttachment($this->data['Attribute']);
}
$pubToZmq = Configure::read('Plugin.ZeroMQ_enable') && Configure::read('Plugin.ZeroMQ_attribute_notifications_enable');
$kafkaTopic = Configure::read('Plugin.Kafka_attribute_notifications_topic');
$pubToKafka = Configure::read('Plugin.Kafka_enable') && Configure::read('Plugin.Kafka_attribute_notifications_enable') && !empty($kafkaTopic);
if ($pubToZmq || $pubToKafka) {
$kafkaTopic = $this->kafkaTopic('attribute');
if ($pubToZmq || $kafkaTopic) {
$attribute = $this->fetchAttribute($this->id);
if (!empty($attribute)) {
$user = array(
@ -475,7 +480,7 @@ class Attribute extends AppModel
$pubSubTool->attribute_save($attribute, $action);
unset($attribute['Attribute']['data']);
}
if ($pubToKafka) {
if ($kafkaTopic) {
if (Configure::read('Plugin.Kafka_include_attachments') && $this->typeIsAttachment($attribute['Attribute']['type'])) {
$attribute['Attribute']['data'] = $this->base64EncodeAttachment($attribute['Attribute']);
}
@ -507,8 +512,8 @@ class Attribute extends AppModel
$pubSubTool = $this->getPubSubTool();
$pubSubTool->attribute_save($this->data, 'delete');
}
$kafkaTopic = Configure::read('Plugin.Kafka_attribute_notifications_topic');
if (Configure::read('Plugin.Kafka_enable') && Configure::read('Plugin.Kafka_attribute_notifications_enable') && !empty($kafkaTopic)) {
$kafkaTopic = $this->kafkaTopic('attribute');
if ($kafkaTopic) {
$kafkaPubTool = $this->getKafkaPubTool();
$kafkaPubTool->publishJson($kafkaTopic, $this->data, 'delete');
}
@ -604,10 +609,7 @@ class Attribute extends AppModel
// Set defaults for when some of the mandatory fields don't have defaults
// These fields all have sane defaults either based on another field, or due to server settings
if (!isset($this->data['Attribute']['distribution'])) {
$this->data['Attribute']['distribution'] = Configure::read('MISP.default_attribute_distribution');
if ($this->data['Attribute']['distribution'] === 'event') {
$this->data['Attribute']['distribution'] = 5;
}
$this->data['Attribute']['distribution'] = $this->defaultDistribution();
}
// If category is not provided, assign default category by type
if (empty($this->data['Attribute']['category'])) {
@ -673,7 +675,7 @@ class Attribute extends AppModel
*/
public function valueIsUnique($fields)
{
if (isset($this->data['Attribute']['deleted']) && $this->data['Attribute']['deleted']) {
if (!empty($this->data['Attribute']['deleted'])) {
return true;
}
// We escape this rule for objects as we can have the same category/type/value combination in different objects
@ -681,14 +683,11 @@ class Attribute extends AppModel
return true;
}
$eventId = $this->data['Attribute']['event_id'];
$category = $this->data['Attribute']['category'];
$type = $this->data['Attribute']['type'];
$conditions = array(
'Attribute.event_id' => $eventId,
'Attribute.event_id' => $this->data['Attribute']['event_id'],
'Attribute.type' => $type,
'Attribute.category' => $category,
'Attribute.category' => $this->data['Attribute']['category'],
'Attribute.deleted' => 0,
'Attribute.object_id' => 0,
);
@ -706,18 +705,7 @@ class Attribute extends AppModel
$conditions['Attribute.id !='] = $this->data['Attribute']['id'];
}
$params = array(
'recursive' => -1,
'fields' => array('id'),
'conditions' => $conditions,
'order' => false,
);
if (!empty($this->find('first', $params))) {
// value isn't unique
return false;
}
// value is unique
return true;
return !$this->hasAny($conditions);
}
public function validateTypeValue($fields)
@ -1554,8 +1542,18 @@ class Attribute extends AppModel
return $this->loadAttachmentTool()->getFile($attribute['event_id'], $attribute['id'], $path_suffix);
}
public function saveAttachment($attribute, $path_suffix='')
/**
* @param array $attribute
* @param string $path_suffix
* @return bool
* @throws Exception
*/
private function saveAttachment(array $attribute, $path_suffix='')
{
if ($attribute['data'] === false) {
$this->log("Invalid attachment data provided for attribute with ID {$attribute['id']}.");
return false;
}
$result = $this->loadAttachmentTool()->save($attribute['event_id'], $attribute['id'], $attribute['data'], $path_suffix);
if ($result) {
$this->loadAttachmentScan()->backgroundScan(AttachmentScan::TYPE_ATTRIBUTE, $attribute);
@ -1579,12 +1577,6 @@ class Attribute extends AppModel
}
}
public function saveBase64EncodedAttachment($attribute)
{
$attribute['data'] = base64_decode($attribute['data']);
return $this->saveAttachment($attribute);
}
/**
* Currently, as image are considered files with JPG (JPEG), PNG or GIF extension.
* @param array $attribute
@ -1618,7 +1610,7 @@ class Attribute extends AppModel
// Thumbnail doesn't exists, we need to generate it
$imageData = $this->getAttachment($attribute['Attribute']);
$imageData = $this->resizeImage($imageData, $maxWidth, $maxHeight);
$imageData = $this->loadAttachmentTool()->resizeImage($imageData, $maxWidth, $maxHeight);
// Save just when requested default thumbnail size
if ($maxWidth == 200 && $maxHeight == 200) {
@ -1632,56 +1624,6 @@ class Attribute extends AppModel
return $imageData;
}
/**
* @param string $data
* @param int $maxWidth
* @param int $maxHeight
* @return string
* @throws Exception
*/
public function resizeImage($data, $maxWidth, $maxHeight)
{
$image = imagecreatefromstring($data);
if ($image === false) {
throw new Exception("Image is not valid.");
}
$currentWidth = imagesx($image);
$currentHeight = imagesy($image);
// Compute thumbnail size with keeping ratio
if ($currentWidth > $currentHeight) {
$newWidth = min($currentWidth, $maxWidth);
$divisor = $currentWidth / $newWidth;
$newHeight = floor($currentHeight / $divisor);
} else {
$newHeight = min($currentHeight, $maxHeight);
$divisor = $currentHeight / $newHeight;
$newWidth = floor($currentWidth / $divisor);
}
$imageThumbnail = imagecreatetruecolor($newWidth, $newHeight);
// Allow transparent background
imagealphablending($imageThumbnail, false);
imagesavealpha($imageThumbnail, true);
$transparent = imagecolorallocatealpha($imageThumbnail, 255, 255, 255, 127);
imagefilledrectangle($imageThumbnail, 0, 0, $newWidth, $newHeight, $transparent);
// Resize image
imagecopyresampled($imageThumbnail, $image, 0, 0, 0, 0, $newWidth, $newHeight, $currentWidth, $currentHeight);
imagedestroy($image);
// Output image to string
ob_start();
imagepng($imageThumbnail, null, 9);
$imageData = ob_get_contents();
ob_end_clean();
imagedestroy($imageThumbnail);
return $imageData;
}
/**
* @param array $user
* @param array $resultArray
@ -3186,6 +3128,28 @@ class Attribute extends AppModel
return $result;
}
/**
* @param string $originalFilename
* @param string $content
* @param array $hashTypes
* @return array
*/
private function handleMaliciousRaw($originalFilename, $content, array $hashTypes)
{
$attachmentTool = $this->loadAttachmentTool();
$hashes = $attachmentTool->computeHashes($content, $hashTypes);
try {
$encrypted = $attachmentTool->encrypt($originalFilename, $content, $hashes['md5']);
} catch (Exception $e) {
$this->logException("Could not create encrypted malware sample.", $e);
return ['success' => false];
}
$hashes['success'] = true;
$hashes['data_raw'] = $encrypted;
return $hashes;
}
/**
* @return bool Return true if at least one advanced extraction tool is available
*/
@ -3269,16 +3233,9 @@ class Attribute extends AppModel
public function saveAttributes($attributes, $user)
{
$defaultDistribution = 5;
if (Configure::read('MISP.default_attribute_distribution') != null) {
if (Configure::read('MISP.default_attribute_distribution') === 'event') {
$defaultDistribution = 5;
} else {
$defaultDistribution = Configure::read('MISP.default_attribute_distribution');
}
}
$defaultDistribution = $this->defaultDistribution();
$saveResult = true;
foreach ($attributes as $k => $attribute) {
foreach ($attributes as $attribute) {
if (!empty($attribute['encrypt']) && $attribute['encrypt']) {
$attribute = $this->onDemandEncrypt($attribute);
}
@ -3297,54 +3254,30 @@ class Attribute extends AppModel
return $saveResult;
}
public function onDemandEncrypt($attribute)
/**
* @param array $attribute
* @return array
*/
public function onDemandEncrypt(array $attribute)
{
if (strpos($attribute['value'], '|') !== false) {
$temp = explode('|', $attribute['value']);
$attribute['value'] = $temp[0];
}
$result = $this->handleMaliciousBase64($attribute['event_id'], $attribute['value'], $attribute['data'], array('md5'));
$attribute['data'] = $result['data'];
$content = base64_decode($attribute['data']);
if ($content === false) {
$this->log("Invalid attachment data provided for attribute with ID {$attribute['id']}.");
return $attribute;
}
$result = $this->handleMaliciousRaw($attribute['value'], $content, array('md5'));
$attribute['data_raw'] = $result['data_raw'];
unset($attribute['data']);
$attribute['value'] = $attribute['value'] . '|' . $result['md5'];
return $attribute;
}
public function saveAndEncryptAttribute($attribute, $user = false)
{
$hashes = array('md5' => 'malware-sample', 'sha1' => 'filename|sha1', 'sha256' => 'filename|sha256');
if ($attribute['encrypt']) {
$result = $this->handleMaliciousBase64($attribute['event_id'], $attribute['value'], $attribute['data'], array_keys($hashes));
if (!$result['success']) {
return 'Could not handle the sample';
}
foreach ($hashes as $hash => $typeName) {
if (!$result[$hash]) {
continue;
}
$attributeToSave = array(
'Attribute' => array(
'value' => $attribute['value'] . '|' . $result[$hash],
'category' => $attribute['category'],
'type' => $typeName,
'event_id' => $attribute['event_id'],
'comment' => $attribute['comment'],
'to_ids' => 1,
'distribution' => $attribute['distribution'],
'sharing_group_id' => isset($attribute['sharing_group_id']) ? $attribute['sharing_group_id'] : 0,
)
);
if ($hash == 'md5') {
$attributeToSave['Attribute']['data'] = $result['data'];
}
$this->create();
if (!$this->save($attributeToSave)) {
return $this->validationErrors;
}
}
}
return true;
}
private function __createTagSubQuery($tag_id, $blocked = false, $scope = 'Event', $limitAttributeHitsTo = 'event')
{
$conditionKey = $blocked ? array('NOT' => array('EventTag.tag_id' => $tag_id)) : array('EventTag.tag_id' => $tag_id);
@ -3505,14 +3438,7 @@ class Attribute extends AppModel
public function fetchDistributionData($user)
{
$initialDistribution = 5;
if (Configure::read('MISP.default_attribute_distribution') != null) {
if (Configure::read('MISP.default_attribute_distribution') === 'event') {
$initialDistribution = 5;
} else {
$initialDistribution = Configure::read('MISP.default_attribute_distribution');
}
}
$initialDistribution = $this->defaultDistribution();
$sgs = $this->SharingGroup->fetchAllAuthorised($user, 'name', 1);
$distributionLevels = $this->distributionLevels;
if (empty($sgs)) {
@ -3524,7 +3450,7 @@ class Attribute extends AppModel
public function simpleAddMalwareSample($event_id, $attribute_settings, $filename, $tmpfile)
{
$attributes = array(
'malware-sample' => array('type' => 'malware-sample', 'data' => 1, 'category' => '', 'to_ids' => 1, 'disable_correlation' => 0, 'object_relation' => 'malware-sample'),
'malware-sample' => array('type' => 'malware-sample', 'category' => '', 'to_ids' => 1, 'disable_correlation' => 0, 'object_relation' => 'malware-sample'),
'filename' => array('type' => 'filename', 'category' => '', 'to_ids' => 0, 'disable_correlation' => 0, 'object_relation' => 'filename'),
'md5' => array('type' => 'md5', 'category' => '', 'to_ids' => 1, 'disable_correlation' => 0, 'object_relation' => 'md5'),
'sha1' => array('type' => 'sha1', 'category' => '', 'to_ids' => 1, 'disable_correlation' => 0, 'object_relation' => 'sha1'),
@ -3532,16 +3458,14 @@ class Attribute extends AppModel
'size-in-bytes' => array('type' => 'size-in-bytes', 'category' => 'Other', 'to_ids' => 0, 'disable_correlation' => 1, 'object_relation' => 'size-in-bytes')
);
$hashes = array('md5', 'sha1', 'sha256');
$this->Object = ClassRegistry::init('MispObject');
$this->ObjectTemplate = ClassRegistry::init('ObjectTemplate');
$current = $this->ObjectTemplate->find('first', array(
$current = $this->Object->ObjectTemplate->find('first', array(
'fields' => array('MAX(version) AS version', 'uuid'),
'conditions' => array('uuid' => '688c46fb-5edb-40a3-8273-1af7923e2215'),
'recursive' => -1,
'group' => array('uuid')
));
if (!empty($current)) {
$object_template = $this->ObjectTemplate->find('first', array(
$object_template = $this->Object->ObjectTemplate->find('first', array(
'conditions' => array(
'ObjectTemplate.uuid' => '688c46fb-5edb-40a3-8273-1af7923e2215',
'ObjectTemplate.version' => $current[0]['version']
@ -3571,7 +3495,7 @@ class Attribute extends AppModel
'event_id' => $event_id,
'comment' => !empty($attribute_settings['comment']) ? $attribute_settings['comment'] : ''
);
$result = $this->handleMaliciousBase64($event_id, $filename, base64_encode($tmpfile->read()), $hashes);
$result = $this->handleMaliciousRaw($filename, $tmpfile->read(), $hashes);
foreach ($attributes as $k => $v) {
$attribute = array(
'distribution' => 5,
@ -3583,14 +3507,12 @@ class Attribute extends AppModel
'event_id' => $event_id,
'object_relation' => $v['object_relation']
);
if (isset($v['data'])) {
$attribute['data'] = $result['data'];
}
if ($k == 'malware-sample') {
if ($k === 'malware-sample') {
$attribute['value'] = $filename . '|' . $result['md5'];
} elseif ($k == 'size-in-bytes') {
$attribute['data_raw'] = $result['data_raw'];
} elseif ($k === 'size-in-bytes') {
$attribute['value'] = $tmpfile->size();
} elseif ($k == 'filename') {
} elseif ($k === 'filename') {
$attribute['value'] = $filename;
} else {
$attribute['value'] = $result[$v['type']];
@ -3637,7 +3559,7 @@ class Attribute extends AppModel
public function captureAttribute($attribute, $eventId, $user, $objectId = false, $log = false, $parentEvent = false, &$validationErrors = false, $params = array())
{
$attribute['event_id'] = $eventId;
$attribute['object_id'] = $objectId ? $objectId : 0;
$attribute['object_id'] = $objectId ?: 0;
if (!isset($attribute['to_ids'])) {
$attribute['to_ids'] = $this->typeDefinitions[$attribute['type']]['to_ids'];
}
@ -3660,16 +3582,11 @@ class Attribute extends AppModel
}
}
if (isset($attribute['encrypt'])) {
$result = $this->handleMaliciousBase64($eventId, $attribute['value'], $attribute['data'], array('md5'));
$attribute['data'] = $result['data'];
$attribute['value'] = $attribute['value'] . '|' . $result['md5'];
$attribute = $this->onDemandEncrypt($attribute);
}
$this->create();
if (!isset($attribute['distribution'])) {
$attribute['distribution'] = Configure::read('MISP.default_attribute_distribution');
if ($attribute['distribution'] == 'event') {
$attribute['distribution'] = 5;
}
$attribute['distribution'] = $this->defaultDistribution();
}
$params = array(
'fieldList' => $this->captureFields
@ -3691,13 +3608,18 @@ class Attribute extends AppModel
'Validation errors: ' . json_encode($this->validationErrors) . ' Full Attribute: ' . json_encode($attribute)
);
} else {
if (isset($attribute['AttributeTag'])) {
if (!empty($attribute['AttributeTag'])) {
$toSave = [];
foreach ($attribute['AttributeTag'] as $at) {
unset($at['id']);
$this->AttributeTag->create();
$at['attribute_id'] = $this->id;
$at['event_id'] = $eventId;
$this->AttributeTag->save($at);
$toSave[] = $at;
}
if (!$this->AttributeTag->saveMany($toSave, ['validate' => true])) {
$this->log("Could not save tags when capturing attribute with ID {$this->id}.", LOG_WARNING);
} else if (!empty($this->AttributeTag->validationErrors)) {
$this->log("Could not save some tags when capturing attribute with ID {$this->id}: " . json_encode($this->AttributeTag->validationErrors), LOG_WARNING);
}
}
if (isset($attribute['Tag'])) {
@ -3733,9 +3655,7 @@ class Attribute extends AppModel
$attribute['event_id'] = $eventId;
$attribute['object_id'] = $objectId;
if (isset($attribute['encrypt'])) {
$result = $this->handleMaliciousBase64($eventId, $attribute['value'], $attribute['data'], array('md5'));
$attribute['data'] = $result['data'];
$attribute['value'] = $attribute['value'] . '|' . $result['md5'];
$attribute = $this->onDemandEncrypt($attribute);
}
unset($attribute['id']);
if (isset($attribute['uuid'])) {
@ -3792,10 +3712,7 @@ class Attribute extends AppModel
return 'Invalid sharing group choice.';
}
} else if (!isset($attribute['distribution'])) {
$attribute['distribution'] = Configure::read('MISP.default_attribute_distribution');
if ($attribute['distribution'] === 'event') {
$attribute['distribution'] = 5;
}
$attribute['distribution'] = $this->defaultDistribution();
}
$fieldList = self::EDITABLE_FIELDS;
if (empty($existingAttribute)) {
@ -3813,31 +3730,30 @@ class Attribute extends AppModel
'Validation errors: ' . json_encode($this->validationErrors) . ' Full Attribute: ' . json_encode($attribute)
);
return $this->validationErrors;
} else {
if (isset($attribute['Sighting']) && !empty($attribute['Sighting'])) {
$this->Sighting->captureSightings($attribute['Sighting'], $this->id, $eventId, $user);
}
if ($user['Role']['perm_tagger']) {
/*
We should uncomment the line below in the future once we have tag soft-delete
A solution to still keep the behavior for previous instance could be to not soft-delete the Tag if the remote instance
has a version below x
*/
// $this->AttributeTag->pruneOutdatedAttributeTagsFromSync(isset($attribute['Tag']) ? $attribute['Tag'] : array(), $existingAttribute['AttributeTag']);
if (isset($attribute['Tag'])) {
foreach ($attribute['Tag'] as $tag) {
$tag_id = $this->AttributeTag->Tag->captureTag($tag, $user);
if ($tag_id) {
$tag['id'] = $tag_id;
// fix the IDs here
$this->AttributeTag->handleAttributeTag($this->id, $attribute['event_id'], $tag);
} else {
// If we couldn't attach the tag it is most likely because we couldn't create it - which could have many reasons
// However, if a tag couldn't be added, it could also be that the user is a tagger but not a tag editor
// In which case if no matching tag is found, no tag ID is returned. Logging these is pointless as it is the correct behaviour.
if ($user['Role']['perm_tag_editor']) {
$this->loadLog()->createLogEntry($user, 'edit', 'Attribute', $this->id, 'Failed create or attach Tag ' . $tag['name'] . ' to the attribute.');
}
}
if (!empty($attribute['Sighting'])) {
$this->Sighting->captureSightings($attribute['Sighting'], $this->id, $eventId, $user);
}
if ($user['Role']['perm_tagger']) {
/*
We should uncomment the line below in the future once we have tag soft-delete
A solution to still keep the behavior for previous instance could be to not soft-delete the Tag if the remote instance
has a version below x
*/
// $this->AttributeTag->pruneOutdatedAttributeTagsFromSync(isset($attribute['Tag']) ? $attribute['Tag'] : array(), $existingAttribute['AttributeTag']);
if (isset($attribute['Tag'])) {
foreach ($attribute['Tag'] as $tag) {
$tag_id = $this->AttributeTag->Tag->captureTag($tag, $user);
if ($tag_id) {
$tag['id'] = $tag_id;
// fix the IDs here
$this->AttributeTag->handleAttributeTag($this->id, $attribute['event_id'], $tag);
} else {
// If we couldn't attach the tag it is most likely because we couldn't create it - which could have many reasons
// However, if a tag couldn't be added, it could also be that the user is a tagger but not a tag editor
// In which case if no matching tag is found, no tag ID is returned. Logging these is pointless as it is the correct behaviour.
if ($user['Role']['perm_tag_editor']) {
$this->loadLog()->createLogEntry($user, 'edit', 'Attribute', $this->id, 'Failed create or attach Tag ' . $tag['name'] . ' to the attribute.');
}
}
}
@ -4263,6 +4179,23 @@ class Attribute extends AppModel
return $typeCategoryMapping;
}
/**
* Fetch default distribution from `MISP.default_attribute_distribution` setting. If this setting is not defined,
* default distribution is `5` (Inherit event)
* @return int
*/
public function defaultDistribution()
{
static $distribution;
if ($distribution === null) {
$distribution = Configure::read('MISP.default_attribute_distribution');
if ($distribution === null || $distribution === 'event') {
$distribution = 5;
}
}
return $distribution;
}
public function __isset($name)
{
if ($name === 'typeDefinitions' || $name === 'categoryDefinitions') {
@ -4415,7 +4348,7 @@ class Attribute extends AppModel
'pattern-in-file' => array('desc' => __('Pattern in file that identifies the malware'), 'default_category' => 'Payload installation', 'to_ids' => 1),
'pattern-in-traffic' => array('desc' => __('Pattern in network traffic that identifies the malware'), 'default_category' => 'Network activity', 'to_ids' => 1),
'pattern-in-memory' => array('desc' => __('Pattern in memory dump that identifies the malware'), 'default_category' => 'Payload installation', 'to_ids' => 1),
'pattern-filename' => array('desc' => __('A pattern in the name of a file'), 'default_category' => 'Payload installation', 'to_ids' => 1),
'filename-pattern' => array('desc' => __('A pattern in the name of a file'), 'default_category' => 'Payload installation', 'to_ids' => 1),
'pgp-public-key' => array('desc' => __('A PGP public key'), 'default_category' => 'Person', 'to_ids' => 0),
'pgp-private-key' => array('desc' => __('A PGP private key'), 'default_category' => 'Person', 'to_ids' => 0),
'yara' => array('desc' => __('Yara signature'), 'default_category' => 'Payload installation', 'to_ids' => 1),

View File

@ -9,18 +9,20 @@ class AttributeTag extends AppModel
{
public $actsAs = array('AuditLog', 'Containable');
public $validate = array(
'attribute_id' => array(
'valueNotEmpty' => array(
'rule' => array('valueNotEmpty'),
),
),
'tag_id' => array(
'valueNotEmpty' => array(
'rule' => array('valueNotEmpty'),
),
),
);
public $validate = [
'attribute_id' => [
'rule' => 'numeric',
'required' => true,
],
'event_id' => [
'rule' => 'numeric',
'required' => true,
],
'tag_id' => [
'rule' => 'numeric',
'required' => true,
],
];
public $belongsTo = array(
'Attribute' => array(
@ -33,11 +35,9 @@ class AttributeTag extends AppModel
public function afterSave($created, $options = array())
{
parent::afterSave($created, $options);
$pubToZmq = Configure::read('Plugin.ZeroMQ_enable') && Configure::read('Plugin.ZeroMQ_tag_notifications_enable');
$kafkaTopic = Configure::read('Plugin.Kafka_tag_notifications_topic');
$pubToKafka = Configure::read('Plugin.Kafka_enable') && Configure::read('Plugin.Kafka_tag_notifications_enable') && !empty($kafkaTopic);
if ($pubToZmq || $pubToKafka) {
$kafkaTopic = $this->kafkaTopic('tag');
if ($pubToZmq || $kafkaTopic) {
$tag = $this->find('first', array(
'recursive' => -1,
'conditions' => array('AttributeTag.id' => $this->id),
@ -50,7 +50,7 @@ class AttributeTag extends AppModel
$pubSubTool = $this->getPubSubTool();
$pubSubTool->tag_save($tag, 'attached to attribute');
}
if ($pubToKafka) {
if ($kafkaTopic) {
$kafkaPubTool = $this->getKafkaPubTool();
$kafkaPubTool->publishJson($kafkaTopic, $tag, 'attached to attribute');
}
@ -60,9 +60,8 @@ class AttributeTag extends AppModel
public function beforeDelete($cascade = true)
{
$pubToZmq = Configure::read('Plugin.ZeroMQ_enable') && Configure::read('Plugin.ZeroMQ_tag_notifications_enable');
$kafkaTopic = Configure::read('Plugin.Kafka_tag_notifications_topic');
$pubToKafka = Configure::read('Plugin.Kafka_enable') && Configure::read('Plugin.Kafka_tag_notifications_enable') && !empty($kafkaTopic);
if ($pubToZmq || $pubToKafka) {
$kafkaTopic = $this->kafkaTopic('tag');
if ($pubToZmq || $kafkaTopic) {
if (!empty($this->id)) {
$tag = $this->find('first', array(
'recursive' => -1,
@ -76,7 +75,7 @@ class AttributeTag extends AppModel
$pubSubTool = $this->getPubSubTool();
$pubSubTool->tag_save($tag, 'detached from attribute');
}
if ($pubToKafka) {
if ($kafkaTopic) {
$kafkaPubTool = $this->getKafkaPubTool();
$kafkaPubTool->publishJson($kafkaTopic, $tag, 'detached from attribute');
}
@ -381,5 +380,4 @@ class AttributeTag extends AppModel
$attribute_tags_name['tags'] = array_diff_key($attribute_tags_name['tags'], $attribute_tags_name['clusters']); // de-dup if needed.
return $attribute_tags_name;
}
}

View File

@ -150,12 +150,13 @@ class AuditLogBehavior extends ModelBehavior
$modelName = $model->name === 'MispObject' ? 'Object' : $model->name;
if ($modelName === 'AttributeTag' || $modelName === 'EventTag') {
$action = $model->data[$model->alias]['local'] ? AuditLog::ACTION_TAG_LOCAL : AuditLog::ACTION_TAG;
$isLocal = isset($model->data[$model->alias]['local']) ? $model->data[$model->alias]['local'] : false;
$action = $isLocal ? AuditLog::ACTION_TAG_LOCAL : AuditLog::ACTION_TAG;
$tagInfo = $this->getTagInfo($model, $model->data[$model->alias]['tag_id']);
if ($tagInfo) {
$modelTitle = $tagInfo['tag_name'];
if ($tagInfo['is_galaxy']) {
$action = $model->data[$model->alias]['local'] ? AuditLog::ACTION_GALAXY_LOCAL : AuditLog::ACTION_GALAXY;
$action = $isLocal ? AuditLog::ACTION_GALAXY_LOCAL : AuditLog::ACTION_GALAXY;
if ($tagInfo['galaxy_cluster_name']) {
$modelTitle = $tagInfo['galaxy_cluster_name'];
}
@ -225,12 +226,13 @@ class AuditLogBehavior extends ModelBehavior
$id = $model->id;
if ($modelName === 'AttributeTag' || $modelName === 'EventTag') {
$action = $model->data[$model->alias]['local'] ? AuditLog::ACTION_REMOVE_TAG_LOCAL : AuditLog::ACTION_REMOVE_TAG;
$isLocal = isset($model->data[$model->alias]['local']) ? $model->data[$model->alias]['local'] : false;
$action = $isLocal ? AuditLog::ACTION_REMOVE_TAG_LOCAL : AuditLog::ACTION_REMOVE_TAG;
$tagInfo = $this->getTagInfo($model, $model->data[$model->alias]['tag_id']);
if ($tagInfo) {
$modelTitle = $tagInfo['tag_name'];
if ($tagInfo['is_galaxy']) {
$action = $model->data[$model->alias]['local'] ? AuditLog::ACTION_REMOVE_GALAXY_LOCAL : AuditLog::ACTION_REMOVE_GALAXY;
$action = $isLocal ? AuditLog::ACTION_REMOVE_GALAXY_LOCAL : AuditLog::ACTION_REMOVE_GALAXY;
if ($tagInfo['galaxy_cluster_name']) {
$modelTitle = $tagInfo['galaxy_cluster_name'];
}

View File

@ -8,45 +8,10 @@
*/
class TrimBehavior extends ModelBehavior
{
/**
*
* @param Model $Model
* @param unknown_type $settings
*/
public function setup(Model $Model, $settings = array())
{
if (!isset($this->settings[$Model->alias])) {
$this->settings[$Model->alias] = array(
'fields' => 'all',
);
}
$this->settings[$Model->alias] = array_merge(
$this->settings[$Model->alias],
(array)$settings
);
}
/**
*
* @param $options
*/
public function beforeValidate(Model $Model, $options = array())
{
$this->trimStringFields($Model);
return true;
}
/**
* Trim String Fields
*
* @param Model $Model
* @param unknown_type $array
*/
public function trimStringFields(Model $Model)
{
foreach ($Model->data[$Model->name] as $key => $field) {
if (is_string($field)) {
if ($key !== 'data' && $key !== 'data_raw' && is_string($field)) {
$Model->data[$Model->name][$key] = trim($field);
}
}

View File

@ -15,6 +15,7 @@ App::uses('SendEmailTemplate', 'Tools');
* @property EventTag $EventTag
* @property SharingGroup $SharingGroup
* @property ThreatLevel $ThreatLevel
* @property Sighting $Sighting
*/
class Event extends AppModel
{
@ -397,32 +398,32 @@ class Event extends AppModel
'event_orgc' => $orgc['Orgc']['name'],
'comment' => __('Automatically blocked by deleting event'),
));
}
if (!empty($this->data['Event']['id'])) {
if (Configure::read('Plugin.ZeroMQ_enable') && Configure::read('Plugin.ZeroMQ_event_notifications_enable')) {
$pubSubTool = $this->getPubSubTool();
$pubSubTool->event_save(array('Event' => $this->data['Event']), 'delete');
if (!empty($this->data['Event']['id'])) {
if (Configure::read('Plugin.ZeroMQ_enable') && Configure::read('Plugin.ZeroMQ_event_notifications_enable')) {
$pubSubTool = $this->getPubSubTool();
$pubSubTool->event_save(array('Event' => $this->data['Event']), 'delete');
}
if (Configure::read('Plugin.Kafka_enable')) {
$kafkaEventTopic = Configure::read('Plugin.Kafka_event_notifications_topic');
if(Configure::read('Plugin.Kafka_event_notifications_enable') && !empty($kafkaEventTopic)) {
$kafkaPubTool = $this->getKafkaPubTool();
$kafkaPubTool->publishJson($kafkaEventTopic, array('Event' => $this->data['Event']), 'delete');
}
if (Configure::read('Plugin.Kafka_enable')) {
$kafkaEventTopic = Configure::read('Plugin.Kafka_event_notifications_topic');
if(Configure::read('Plugin.Kafka_event_notifications_enable') && !empty($kafkaEventTopic)) {
$kafkaPubTool = $this->getKafkaPubTool();
$kafkaPubTool->publishJson($kafkaEventTopic, array('Event' => $this->data['Event']), 'delete');
}
$kafkaPubTopic = Configure::read('Plugin.Kafka_event_publish_notifications_topic');
if (!empty($this->data['Event']['published']) && Configure::read('Plugin.Kafka_event_publish_notifications_enable') && !empty($kafkaPubTopic)) {
$hostOrg = $this->Org->find('first', array('conditions' => array('name' => Configure::read('MISP.org')), 'fields' => array('id')));
if (!empty($hostOrg)) {
$user = array('org_id' => $hostOrg['Org']['id'], 'Role' => array('perm_sync' => 0, 'perm_audit' => 0, 'perm_site_admin' => 0), 'Organisation' => $hostOrg['Org']);
$params = array('eventid' => $this->data['Event']['id']);
if (Configure::read('Plugin.Kafka_include_attachments')) {
$params['includeAttachments'] = 1;
}
$fullEvent = $this->fetchEvent($user, $params);
if (!empty($fullEvent)) {
$kafkaPubTool = $this->getKafkaPubTool();
$kafkaPubTool->publishJson($kafkaPubTopic, $fullEvent[0], 'delete');
}
$kafkaPubTopic = Configure::read('Plugin.Kafka_event_publish_notifications_topic');
if (!empty($this->data['Event']['published']) && Configure::read('Plugin.Kafka_event_publish_notifications_enable') && !empty($kafkaPubTopic)) {
$hostOrg = $this->Org->find('first', array('conditions' => array('name' => Configure::read('MISP.org')), 'fields' => array('id')));
if (!empty($hostOrg)) {
$user = array('org_id' => $hostOrg['Org']['id'], 'Role' => array('perm_sync' => 0, 'perm_audit' => 0, 'perm_site_admin' => 0), 'Organisation' => $hostOrg['Org']);
$params = array('eventid' => $this->data['Event']['id']);
if (Configure::read('Plugin.Kafka_include_attachments')) {
$params['includeAttachments'] = 1;
}
$fullEvent = $this->fetchEvent($user, $params);
if (!empty($fullEvent)) {
$kafkaPubTool = $this->getKafkaPubTool();
$kafkaPubTool->publishJson($kafkaPubTopic, $fullEvent[0], 'delete');
}
}
}
@ -460,7 +461,7 @@ class Event extends AppModel
}
if (!isset($this->data['Event']['threat_level_id'])) {
$this->data['Event']['threat_level_id'] = Configure::read('MISP.default_event_threat_level') ? Configure::read('MISP.default_event_threat_level') : 4;
$this->data['Event']['threat_level_id'] = Configure::read('MISP.default_event_threat_level') ?: 4;
}
// generate UUID if it doesn't exist
@ -585,7 +586,6 @@ class Event extends AppModel
public function attachSightingsCountToEvents(array $user, array $events)
{
$eventIds = array_column(array_column($events, 'Event'), 'id');
$this->Sighting = ClassRegistry::init('Sighting');
$this->Sighting->virtualFields['count'] = 'count(Sighting.id)';
$sightings = $this->Sighting->find('list', array(
'fields' => array('Sighting.event_id', 'Sighting.count'),
@ -885,7 +885,7 @@ class Event extends AppModel
* This function receives the reference of the variable, so no return is required as it directly
* modifies the original data.
*/
public function cleanupEventArrayFromXML(&$data)
private function cleanupEventArrayFromXML(&$data)
{
$objects = array('Attribute', 'ShadowAttribute', 'Object');
foreach ($objects as $object) {
@ -2113,9 +2113,6 @@ class Event extends AppModel
$sharingGroupData = $options['sgReferenceOnly'] ? [] : $this->__cacheSharingGroupData($user, $useCache);
// Initialize classes that will be necessary during event fetching
if ((empty($options['metadata']) && empty($options['noSightings'])) && !isset($this->Sighting)) {
$this->Sighting = ClassRegistry::init('Sighting');
}
if (!empty($options['includeDecayScore']) && !isset($this->DecayingModel)) {
$this->DecayingModel = ClassRegistry::init('DecayingModel');
}
@ -3370,6 +3367,12 @@ class Event extends AppModel
return $template;
}
/**
* @param array $element
* @param array $user
* @param bool|false $server
* @return array
*/
public function captureSGForElement($element, $user, $server=false)
{
if (isset($element['SharingGroup'])) {
@ -3388,41 +3391,38 @@ class Event extends AppModel
return $element;
}
// When we receive an event via REST, we might end up with organisations, sharing groups, tags that we do not know
// or which we need to update. All of that is controlled in this method.
private function __captureObjects($data, $user, $server=false)
/**
* When we receive an event via REST, we might end up with organisations, sharing groups, tags that we do not know
* or which we need to update. All of that is controlled in this method.
* @param array $event
* @param array $user
* @param array|false $server
* @return array
* @throws Exception
*/
private function __captureObjects(array $event, array $user, $server=false)
{
// First we need to check whether the event or any attributes are tied to a sharing group and whether the user is even allowed to create the sharing group / is part of it
if (isset($data['Event']['distribution']) && $data['Event']['distribution'] == 4) {
$data['Event'] = $this->captureSGForElement($data['Event'], $user, $server);
if (isset($event['distribution']) && $event['distribution'] == 4) {
$event = $this->captureSGForElement($event, $user, $server);
}
if (!empty($data['Event']['Attribute'])) {
foreach ($data['Event']['Attribute'] as $k => $a) {
unset($data['Event']['Attribute']['id']);
if (!empty($event['Attribute'])) {
foreach ($event['Attribute'] as $k => $a) {
unset($event['Attribute']['id']);
if (isset($a['distribution']) && $a['distribution'] == 4) {
$data['Event']['Attribute'][$k] = $this->captureSGForElement($a, $user, $server);
if ($data['Event']['Attribute'][$k] === false) {
unset($data['Event']['Attribute']);
}
$event['Attribute'][$k] = $this->captureSGForElement($a, $user, $server);
}
}
}
if (!empty($data['Event']['Object'])) {
foreach ($data['Event']['Object'] as $k => $o) {
if (!empty($event['Object'])) {
foreach ($event['Object'] as $k => $o) {
if (isset($o['distribution']) && $o['distribution'] == 4) {
$data['Event']['Object'][$k] = $this->captureSGForElement($o, $user, $server);
if ($data['Event']['Object'][$k] === false) {
unset($data['Event']['Object'][$k]);
continue;
}
$event['Object'][$k] = $this->captureSGForElement($o, $user, $server);
}
if (!empty($o['Attribute'])) {
foreach ($o['Attribute'] as $k2 => $a) {
if (isset($a['distribution']) && $a['distribution'] == 4) {
$data['Event']['Object'][$k]['Attribute'][$k2] = $this->captureSGForElement($a, $user, $server);
if ($data['Event']['Object'][$k]['Attribute'][$k2] === false) {
unset($data['Event']['Object'][$k]['Attribute'][$k2]);
}
$event['Object'][$k]['Attribute'][$k2] = $this->captureSGForElement($a, $user, $server);
}
}
}
@ -3431,89 +3431,120 @@ class Event extends AppModel
// first we want to see how the creator organisation is encoded
// The options here are either by passing an organisation object along or simply passing a string along
if (isset($data['Event']['Orgc'])) {
$data['Event']['orgc_id'] = $this->Orgc->captureOrg($data['Event']['Orgc'], $user);
unset($data['Event']['Orgc']);
} elseif (isset($data['Event']['orgc'])) {
$data['Event']['orgc_id'] = $this->Orgc->captureOrg($data['Event']['orgc'], $user);
unset($data['Event']['orgc']);
if (isset($event['Orgc'])) {
$event['orgc_id'] = $this->Orgc->captureOrg($event['Orgc'], $user);
unset($event['Orgc']);
} elseif (isset($event['orgc'])) {
$event['orgc_id'] = $this->Orgc->captureOrg($event['orgc'], $user);
unset($event['orgc']);
} else {
$data['Event']['orgc_id'] = $user['org_id'];
$event['orgc_id'] = $user['org_id'];
}
$event_tag_ids = array();
if (isset($data['Event']['EventTag'])) {
if (isset($data['Event']['EventTag']['id'])) {
$data['Event']['EventTag'] = array($data['Event']['EventTag']);
$capturedTags = []; // cache captured tag
$eventTags = [];
if (isset($event['EventTag'])) {
if (isset($event['EventTag']['id'])) {
$event['EventTag'] = array($event['EventTag']);
}
$eventTags = array();
foreach ($data['Event']['EventTag'] as $k => $tag) {
$temp = $this->EventTag->Tag->captureTag($data['Event']['EventTag'][$k]['Tag'], $user);
if ($temp && !in_array($temp, $event_tag_ids)) {
$eventTags[] = array('tag_id' => $temp);
$event_tag_ids[] = $temp;
foreach ($event['EventTag'] as $tag) {
$tagId = $this->captureTagWithCache($tag['Tag'], $user, $capturedTags);
if ($tagId && !in_array($tagId, $event_tag_ids)) {
$eventTags[] = array('tag_id' => $tagId);
$event_tag_ids[] = $tagId;
}
unset($data['Event']['EventTag'][$k]);
}
$data['Event']['EventTag'] = $eventTags;
} else {
$data['Event']['EventTag'] = array();
}
if (isset($data['Event']['Tag'])) {
if (isset($data['Event']['Tag']['name'])) {
$data['Event']['Tag'] = array($data['Event']['Tag']);
if (isset($event['Tag'])) {
if (isset($event['Tag']['name'])) {
$event['Tag'] = array($event['Tag']);
}
foreach ($data['Event']['Tag'] as $tag) {
$tag_id = $this->EventTag->Tag->captureTag($tag, $user);
foreach ($event['Tag'] as $tag) {
$tag_id = $this->captureTagWithCache($tag, $user, $capturedTags);
if ($tag_id && !in_array($tag_id, $event_tag_ids)) {
$data['Event']['EventTag'][] = array('tag_id' => $tag_id);
$eventTags[] = [
'tag_id' => $tag_id,
'local' => isset($tag['local']) ? $tag['local'] : 0,
];
$event_tag_ids[] = $tag_id;
}
}
unset($data['Event']['Tag']);
unset($event['Tag']);
}
$event['EventTag'] = $eventTags;
if (!empty($data['Event']['Attribute'])) {
$data['Event']['Attribute'] = $this->__captureAttributeTags($data['Event']['Attribute'], $user);
if (!empty($event['Attribute'])) {
$event['Attribute'] = $this->__captureAttributeTags($event['Attribute'], $user, $capturedTags);
}
if (!empty($data['Event']['Object'])) {
foreach ($data['Event']['Object'] as $k => $object) {
if (!empty($data['Event']['Object'][$k]['Attribute'])) {
$data['Event']['Object'][$k]['Attribute'] = $this->__captureAttributeTags($data['Event']['Object'][$k]['Attribute'], $user);
if (!empty($event['Object'])) {
foreach ($event['Object'] as $k => $object) {
if (!empty($object['Attribute'])) {
$event['Object'][$k]['Attribute'] = $this->__captureAttributeTags($object['Attribute'], $user, $capturedTags);
}
}
}
return $data;
return $event;
}
private function __captureAttributeTags($attributes, $user)
/**
* @param array $tag
* @param array $user
* @param array $capturedTags
* @return false|int
* @throws Exception
*/
private function captureTagWithCache(array $tag, array $user, array &$capturedTags)
{
$tagName = $tag['name'];
if (isset($capturedTags[$tagName])) {
$tagId = $capturedTags[$tagName];
} else {
$tagId = $this->Attribute->AttributeTag->Tag->captureTag($tag, $user);
if ($tagId) {
$tagId = (int)$tagId;
$capturedTags[$tagName] = $tagId;
}
}
return $tagId;
}
/**
* Capture tags for attributes and replace tags just by IDs
* @param array $attributes
* @param array $user
* @param array $capturedTags
* @return array
* @throws Exception
*/
private function __captureAttributeTags(array $attributes, array $user, array &$capturedTags)
{
foreach ($attributes as $k => $a) {
if (isset($attributes[$k]['AttributeTag'])) {
if (isset($attributes[$k]['AttributeTag']['id'])) {
$attributes[$k]['AttributeTag'] = array($attributes[$k]['AttributeTag']);
$attributeTags = [];
if (isset($a['AttributeTag'])) {
if (isset($a['AttributeTag']['id'])) {
$a['AttributeTag'] = array($a['AttributeTag']);
}
$attributeTags = array();
foreach ($attributes[$k]['AttributeTag'] as $tk => $tag) {
$attributeTags[] = array('tag_id' => $this->Attribute->AttributeTag->Tag->captureTag($attributes[$k]['AttributeTag'][$tk]['Tag'], $user));
unset($attributes[$k]['AttributeTag'][$tk]);
foreach ($a['AttributeTag'] as $tag) {
$attributeTags[] = array('tag_id' => $this->captureTagWithCache($tag['Tag'], $user, $capturedTags));
}
$attributes[$k]['AttributeTag'] = $attributeTags;
} else {
$attributes[$k]['AttributeTag'] = array();
}
if (isset($attributes[$k]['Tag'])) {
if (isset($attributes[$k]['Tag']['name'])) {
$attributes[$k]['Tag'] = array($attributes[$k]['Tag']);
if (isset($a['Tag'])) {
if (isset($a['Tag']['name'])) {
$a['Tag'] = array($a['Tag']);
}
foreach ($attributes[$k]['Tag'] as $tag) {
$tag_id = $this->Attribute->AttributeTag->Tag->captureTag($tag, $user);
if ($tag_id) {
$attributes[$k]['AttributeTag'][] = array('tag_id' => $tag_id);
foreach ($a['Tag'] as $tag) {
$tagId = $this->captureTagWithCache($tag, $user, $capturedTags);
if ($tagId) {
$attributeTags[] = [
'tag_id' => $tagId,
'local' => isset($tag['local']) ? $tag['local'] : 0,
];
}
}
unset($attributes[$k]['Tag']);
}
$attributes[$k]['AttributeTag'] = $attributeTags;
}
return $attributes;
}
@ -3626,7 +3657,21 @@ class Event extends AppModel
return $results;
}
// Low level function to add an Event based on an Event $data array
/**
* Low level function to add an Event based on an Event $data array.
*
* @param array $data
* @param bool $fromXml
* @param array $user
* @param int $org_id
* @param int|null $passAlong Server ID or null
* @param bool $fromPull
* @param int|null $jobId
* @param int $created_id
* @param array $validationErrors
* @return bool|int|string True when new event was created, int when event with the same uuid already exists, string when validation errors
* @throws Exception
*/
public function _add(array &$data, $fromXml, array $user, $org_id = 0, $passAlong = null, $fromPull = false, $jobId = null, &$created_id = 0, &$validationErrors = array())
{
if (Configure::read('MISP.enableEventBlocklisting') !== false && isset($data['Event']['uuid'])) {
@ -3742,36 +3787,10 @@ class Event extends AppModel
$created_id = $existingEvent['Event']['id'];
}
return $existingEvent['Event']['id'];
} else {
if ($fromXml) {
$data = $this->__captureObjects($data, $user, $server);
}
if ($data === false) {
$failedCapture = true;
}
}
} else {
if ($fromXml) {
$data = $this->__captureObjects($data, $user, $server);
}
if ($data === false) {
$failedCapture = true;
}
}
if (!empty($failedCapture)) {
$this->Log->create();
$this->Log->save(array(
'org' => $user['Organisation']['name'],
'model' => 'Event',
'model_id' => 0,
'email' => $user['email'],
'action' => 'add',
'user_id' => $user['id'],
'title' => 'Event could not be saved due to a failed sharing group capture.',
'change' => ''
));
$validationErrors['Event'] = 'Issues saving a Sharing Group.';
return json_encode($validationErrors);
if ($fromXml) {
$data['Event'] = $this->__captureObjects($data['Event'], $user, $server);
}
$fieldList = array(
'org_id',
@ -3818,11 +3837,16 @@ class Event extends AppModel
'change' => ''
));
}
if (isset($data['Event']['EventTag'])) {
if (!empty($data['Event']['EventTag'])) {
$toSave = [];
foreach ($data['Event']['EventTag'] as $et) {
$this->EventTag->create();
$et['event_id'] = $this->id;
$this->EventTag->save($et);
$toSave[] = $et;
}
if (!$this->EventTag->saveMany($toSave, ['validate' => true])) {
$this->log("Could not save tags when capturing event with ID {$this->id}.", LOG_WARNING);
} else if (!empty($this->EventTag->validationErrors)) {
$this->log("Could not save some tags when capturing event with ID {$this->id}: " . json_encode($this->EventTag->validationErrors), LOG_WARNING);
}
}
$parentEvent = $this->find('first', array(
@ -3831,11 +3855,11 @@ class Event extends AppModel
));
if (!empty($data['Event']['Attribute'])) {
$attributeHashes = [];
foreach ($data['Event']['Attribute'] as $k => $attribute) {
foreach ($data['Event']['Attribute'] as $attribute) {
$attributeHash = sha1($attribute['value'] . '|' . $attribute['type'] . '|' . $attribute['category'], true);
if (!isset($attributeHashes[$attributeHash])) { // do not save duplicate values
$attributeHashes[$attributeHash] = true;
$data['Event']['Attribute'][$k] = $this->Attribute->captureAttribute($attribute, $this->id, $user, 0, null, $parentEvent);
$this->Attribute->captureAttribute($attribute, $this->id, $user, 0, null, $parentEvent);
}
}
unset($attributeHashes);
@ -3844,7 +3868,7 @@ class Event extends AppModel
if (!empty($data['Event']['Object'])) {
$referencesToCapture = [];
foreach ($data['Event']['Object'] as $object) {
$result = $this->Object->captureObject($object, $this->id, $user, null, false, $breakOnDuplicate, $parentEvent);
$result = $this->Object->captureObject($object, $this->id, $user, false, $breakOnDuplicate, $parentEvent);
if (isset($object['ObjectReference'])) {
foreach ($object['ObjectReference'] as $objectRef) {
$objectRef['source_uuid'] = $object['uuid'];
@ -3868,7 +3892,6 @@ class Event extends AppModel
}
// zeroq: check if sightings are attached and add to event
if (isset($data['Sighting']) && !empty($data['Sighting'])) {
$this->Sighting = ClassRegistry::init('Sighting');
$this->Sighting->captureSightings($data['Sighting'], null, $this->id, $user);
}
if ($fromXml) {
@ -3877,9 +3900,9 @@ class Event extends AppModel
if (!empty($data['Event']['published']) && 1 == $data['Event']['published']) {
// do the necessary actions to publish the event (email, upload,...)
if (('true' != Configure::read('MISP.disablerestalert')) && (empty($server) || empty($server['Server']['publish_without_email']))) {
$this->sendAlertEmailRouter($this->getID(), $user);
$this->sendAlertEmailRouter($this->id, $user);
}
$this->publish($this->getID(), $passAlong);
$this->publish($this->id, $passAlong);
}
if (empty($data['Event']['locked']) && !empty(Configure::read('MISP.default_event_tag_collection'))) {
$this->TagCollection = ClassRegistry::init('TagCollection');
@ -4127,7 +4150,6 @@ class Event extends AppModel
}
// zeroq: if sightings then attach to event
if (isset($data['Sighting']) && !empty($data['Sighting'])) {
$this->Sighting = ClassRegistry::init('Sighting');
$this->Sighting->captureSightings($data['Sighting'], null, $this->id, $user);
}
// if published -> do the actual publishing
@ -4360,16 +4382,13 @@ class Event extends AppModel
* @param array $server
* @param array $event
* @param array $sightingsUuidsToPush
* @throws HttpClientJsonException
* @throws HttpSocketJsonException
* @throws JsonException
* @throws Exception
*/
private function pushSightingsToServer(array $server, array $event, array $sightingsUuidsToPush = [])
{
App::uses('ServerSyncTool', 'Tools');
if (!isset($this->Sighting)) {
$this->Sighting = ClassRegistry::init('Sighting');
}
$serverSync = new ServerSyncTool($server, $this->setupSyncRequest($server));
try {
if ($serverSync->eventExists($event) === false) {
@ -4900,33 +4919,6 @@ class Event extends AppModel
return true;
}
// convenience method to check whether a user can see an event
public function checkIfAuthorised($user, $id)
{
if (!isset($user['id'])) {
throw new MethodNotAllowedException('Invalid user.');
}
$this->id = $id;
if (!$this->exists()) {
return false;
}
if ($user['Role']['perm_site_admin']) {
return true;
}
$event = $this->find('first', array(
'conditions' => array('id' => $id),
'recursive' => -1,
'fields' => array('id', 'sharing_group_id', 'distribution', 'org_id')
));
if ($event['Event']['org_id'] == $user['org_id'] || ($event['Event']['distribution'] > 0 && $event['Event']['distribution'] < 4)) {
return true;
}
if ($event['Event']['distribution'] == 4 && $this->SharingGroup->checkIfAuthorised($user, $event['Event']['sharing_group_id'])) {
return true;
}
return false;
}
// expects a date string in the YYYY-MM-DD format
// returns the passed string or false if the format is invalid
// based on the fix provided by stevengoosensB
@ -5657,13 +5649,7 @@ class Event extends AppModel
public function handleMispFormatFromModuleResult(&$result)
{
$defaultDistribution = 5;
if (!empty(Configure::read('MISP.default_attribute_distribution'))) {
$defaultDistribution = Configure::read('MISP.default_attribute_distribution');
if ($defaultDistribution == 'event') {
$defaultDistribution = 5;
}
}
$defaultDistribution = $this->Attribute->defaultDistribution();
$event = array();
if (!empty($result['results']['Attribute'])) {
$attributes = array();
@ -5916,10 +5902,10 @@ class Event extends AppModel
$shell_command .= ' 2>' . APP . 'tmp/logs/exec-errors.log';
$result = shell_exec($shell_command);
$result = preg_split("/\r\n|\n|\r/", trim($result));
$result = end($result);
$result = trim(end($result));
$tempFile = file_get_contents($tempFilePath);
unlink($tempFilePath);
if (trim($result) == '1') {
if ($result === '1') {
$data = file_get_contents($output_path);
if ($data === false) {
throw new Exception("Could not get content of `$output_path` file.");
@ -5940,24 +5926,26 @@ class Event extends AppModel
$this->publish($created_id);
}
return $created_id;
} else if (is_numeric($result)) {
return __('Event with the same UUID already exists.');
} else if (is_string($result)) {
return $result;
}
return $validationIssues;
} else if ($result === '2') {
$response = __('Issues while loading the stix file. ');
} elseif ($result === '3') {
$response = __('Issues with the maec library. ');
} else {
if (trim($result) == '2') {
$response = __('Issues while loading the stix file. ');
} elseif (trim($result) == '3') {
$response = __('Issues with the maec library. ');
} else {
$response = __('Issues executing the ingestion script or invalid input. ');
}
if (!$user['Role']['perm_site_admin']) {
$response .= __('Please ask your administrator to ');
} else {
$response .= __('Please ');
}
$response .= ' ' . __('check whether the dependencies for STIX are met via the diagnostic tool.');
return $response;
$response = __('Issues executing the ingestion script or invalid input. ');
}
if (!$user['Role']['perm_site_admin']) {
$response .= __('Please ask your administrator to ');
} else {
$response .= __('Please ');
}
$response .= ' ' . __('check whether the dependencies for STIX are met via the diagnostic tool.');
return $response;
}
private function __getTagNamesFromSynonyms($scriptDir)
@ -6785,7 +6773,10 @@ class Event extends AppModel
public function processFreeTextDataRouter($user, $attributes, $id, $default_comment = '', $proposals = false, $adhereToWarninglists = false, $returnRawResults = false)
{
if (Configure::read('MISP.background_jobs') && count($attributes) > 5) { // on background process just big attributes batch
list($job, $randomFileName, $tempFile) = $this->__initiateProcessJob($user, $id);
/** @var Job $job */
$job = ClassRegistry::init('Job');
$job->createJob($user, Job::WORKER_PRIO, "process_freetext_data", 'Event: ' . $id, 'Processing...');
$tempData = array(
'user' => $user,
'attributes' => $attributes,
@ -6796,73 +6787,53 @@ class Event extends AppModel
'jobId' => $job->id,
);
$writeResult = $tempFile->write(json_encode($tempData));
if (!$writeResult) {
return $this->processFreeTextData($user, $attributes, $id, $default_comment, $proposals, $adhereToWarninglists, false, $returnRawResults);
}
$tempFile->close();
$process_id = CakeResque::enqueue(
'prio',
try {
$filePath = FileAccessTool::writeToTempFile(json_encode($tempData));
$process_id = CakeResque::enqueue(
Job::WORKER_PRIO,
'EventShell',
array('processfreetext', $randomFileName),
array('processfreetext', $filePath),
true
);
$job->saveField('process_id', $process_id);
return 'Freetext ingestion queued for background processing. Attributes will be added to the event as they are being processed.';
} else {
return $this->processFreeTextData($user, $attributes, $id, $default_comment, $proposals, $adhereToWarninglists, false, $returnRawResults);
);
$job->saveField('process_id', $process_id);
return 'Freetext ingestion queued for background processing. Attributes will be added to the event as they are being processed.';
} catch (Exception $e) {
$this->logException("Could not process freetext in background.", $e, LOG_NOTICE);
}
}
return $this->processFreeTextData($user, $attributes, $id, $default_comment, $proposals, $adhereToWarninglists, false, $returnRawResults);
}
public function processModuleResultsDataRouter($user, $resolved_data, $id, $default_comment = '', $adhereToWarninglists = false)
public function processModuleResultsDataRouter($user, $resolved_data, $id, $default_comment = '')
{
if (Configure::read('MISP.background_jobs')) {
list($job, $randomFileName, $tempFile) = $this->__initiateProcessJob($user, $id, 'module_results');
/** @var Job $job */
$job = ClassRegistry::init('Job');
$job->createJob($user, Job::WORKER_PRIO, "process_module_results_data", 'Event: ' . $id, 'Processing...');
$tempData = array(
'user' => $user,
'misp_format' => $resolved_data,
'id' => $id,
'default_comment' => $default_comment,
'jobId' => $job->id
'user' => $user,
'misp_format' => $resolved_data,
'id' => $id,
'default_comment' => $default_comment,
'jobId' => $job->id
);
$writeResult = $tempFile->write(json_encode($tempData));
if ($writeResult) {
$tempFile->close();
try {
$filePath = FileAccessTool::writeToTempFile(json_encode($tempData));
$process_id = CakeResque::enqueue(
'prio',
'EventShell',
array('processmoduleresult', $randomFileName),
true
Job::WORKER_PRIO,
'EventShell',
array('processmoduleresult', $filePath),
true
);
$job->saveField('process_id', $process_id);
return 'Module results ingestion queued for background processing. Related data will be added to the event as it is being processed.';
} catch (Exception $e) {
$this->logException("Could not process module results in background.", $e, LOG_NOTICE);
}
$tempFile->delete();
}
return $this->processModuleResultsData($user, $resolved_data, $id, $default_comment = '');
}
private function __initiateProcessJob(array $user, $id, $format = 'freetext')
{
$job = ClassRegistry::init('Job');
$job->create();
$data = array(
'worker' => 'prio',
'job_type' => 'process_' . $format . '_data',
'job_input' => 'Event: ' . $id,
'status' => 0,
'retries' => 0,
'org_id' => $user['org_id'],
'org' => $user['Organisation']['name'],
'message' => 'Processing...'
);
$job->save($data);
$randomFileName = $this->generateRandomFileName() . '.json';
App::uses('Folder', 'Utility');
App::uses('File', 'Utility');
$tempdir = new Folder(APP . 'tmp/cache/ingest', true, 0755);
$tempFile = new File(APP . 'tmp/cache/ingest' . DS . $randomFileName, true, 0644);
return array($job, $randomFileName, $tempFile);
return $this->processModuleResultsData($user, $resolved_data, $id, $default_comment);
}
/**
@ -7196,11 +7167,7 @@ class Event extends AppModel
*/
public function add_original_file($file, $original_filename, $event_id, $format)
{
if (!Configure::check('MISP.default_attribute_distribution') || Configure::read('MISP.default_attribute_distribution') === 'event') {
$distribution = 5;
} else {
$distribution = Configure::read('MISP.default_attribute_distribution');
}
$distribution = $this->Attribute->defaultDistribution();
$this->Object->create();
$object = array(
'name' => 'original-imported-file',
@ -7224,7 +7191,7 @@ class Event extends AppModel
'distribution' => $distribution,
'object_relation' => 'imported-sample',
'value' => $original_filename,
'data' => base64_encode($file),
'data_raw' => $file,
'object_id' => $object_id,
'disable_correlation' => true
),

View File

@ -82,10 +82,7 @@ class EventReport extends AppModel
// Set defaults for when some of the mandatory fields don't have defaults
// These fields all have sane defaults either based on another field, or due to server settings
if (!isset($this->data['EventReport']['distribution'])) {
$this->data['EventReport']['distribution'] = Configure::read('MISP.default_attribute_distribution');
if ($this->data['EventReport']['distribution'] == 'event') {
$this->data['EventReport']['distribution'] = 5;
}
$this->data['EventReport']['distribution'] = $this->Event->Attribute->defaultDistribution();
}
return true;
}

View File

@ -3,22 +3,21 @@ App::uses('AppModel', 'Model');
/**
* @property Event $Event
* @property Tag $Tag
*/
class EventTag extends AppModel
{
public $actsAs = array('AuditLog', 'Containable');
public $validate = array(
'event_id' => array(
'valueNotEmpty' => array(
'rule' => array('valueNotEmpty'),
),
),
'tag_id' => array(
'valueNotEmpty' => array(
'rule' => array('valueNotEmpty'),
),
),
'event_id' => [
'rule' => 'numeric',
'required' => true,
],
'tag_id' => [
'rule' => 'numeric',
'required' => true,
],
);
public $belongsTo = array(
@ -30,9 +29,8 @@ class EventTag extends AppModel
{
parent::afterSave($created, $options);
$pubToZmq = Configure::read('Plugin.ZeroMQ_enable') && Configure::read('Plugin.ZeroMQ_tag_notifications_enable');
$kafkaTopic = Configure::read('Plugin.Kafka_tag_notifications_topic');
$pubToKafka = Configure::read('Plugin.Kafka_enable') && Configure::read('Plugin.Kafka_tag_notifications_enable') && !empty($kafkaTopic);
if ($pubToZmq || $pubToKafka) {
$kafkaTopic = $this->kafkaTopic('tag');
if ($pubToZmq || $kafkaTopic) {
$tag = $this->find('first', array(
'recursive' => -1,
'conditions' => array('EventTag.id' => $this->id),
@ -44,7 +42,7 @@ class EventTag extends AppModel
$pubSubTool = $this->getPubSubTool();
$pubSubTool->tag_save($tag, 'attached to event');
}
if ($pubToKafka) {
if ($kafkaTopic) {
$kafkaPubTool = $this->getKafkaPubTool();
$kafkaPubTool->publishJson($kafkaTopic, $tag, 'attached to event');
}
@ -54,9 +52,8 @@ class EventTag extends AppModel
public function beforeDelete($cascade = true)
{
$pubToZmq = Configure::read('Plugin.ZeroMQ_enable') && Configure::read('Plugin.ZeroMQ_tag_notifications_enable');
$kafkaTopic = Configure::read('Plugin.Kafka_tag_notifications_topic');
$pubToKafka = Configure::read('Plugin.Kafka_enable') && Configure::read('Plugin.Kafka_tag_notifications_enable') && !empty($kafkaTopic);
if ($pubToZmq || $pubToKafka) {
$kafkaTopic = $this->kafkaTopic('tag');
if ($pubToZmq || $kafkaTopic) {
if (!empty($this->id)) {
$tag = $this->find('first', array(
'recursive' => -1,
@ -69,7 +66,7 @@ class EventTag extends AppModel
$pubSubTool = $this->getPubSubTool();
$pubSubTool->tag_save($tag, 'detached from event');
}
if ($pubToKafka) {
if ($kafkaTopic) {
$kafkaPubTool = $this->getKafkaPubTool();
$kafkaPubTool->publishJson($kafkaTopic, $tag, 'detached from event');
}

View File

@ -2,6 +2,7 @@
App::uses('AppModel', 'Model');
App::uses('RandomTool', 'Tools');
App::uses('TmpFileTool', 'Tools');
App::uses('FileAccessTool', 'Tools');
class Feed extends AppModel
{
@ -73,6 +74,8 @@ class Feed extends AppModel
'url_params' => ''
];
const CACHE_DIR = APP . 'tmp' . DS . 'cache' . DS . 'feeds' . DS;
/*
* Cleanup of empty belongsto relationships
*/
@ -92,6 +95,13 @@ class Feed extends AppModel
return $results;
}
public function afterSave($created, $options = array())
{
if (!$created) {
$this->cleanFileCache((int)$this->data['Feed']['id']);
}
}
public function validateInputSource($fields)
{
if (!empty($this->data['Feed']['input_source'])) {
@ -153,20 +163,20 @@ class Feed extends AppModel
*/
public function getNewEventUuids($feed, HttpSocket $HttpSocket = null)
{
$manifest = $this->downloadManifest($feed, $HttpSocket);
$manifest = $this->isFeedLocal($feed) ? $this->downloadManifest($feed) : $this->getRemoteManifest($feed, $HttpSocket);
$this->Event = ClassRegistry::init('Event');
$events = $this->Event->find('all', array(
'conditions' => array(
'Event.uuid' => array_keys($manifest),
),
'recursive' => -1,
'fields' => array('Event.id', 'Event.uuid', 'Event.timestamp')
'fields' => array('Event.uuid', 'Event.timestamp')
));
$result = array('add' => array(), 'edit' => array());
foreach ($events as $event) {
$eventUuid = $event['Event']['uuid'];
if ($event['Event']['timestamp'] < $manifest[$eventUuid]['timestamp']) {
$result['edit'][] = array('uuid' => $eventUuid, 'id' => $event['Event']['id']);
$result['edit'][] = $eventUuid;
} else {
$this->__cleanupFile($feed, '/' . $eventUuid . '.json');
}
@ -209,7 +219,7 @@ class Feed extends AppModel
private function downloadManifest($feed, HttpSocket $HttpSocket = null)
{
$manifestUrl = $feed['Feed']['url'] . '/manifest.json';
$data = $this->feedGetUri($feed, $manifestUrl, $HttpSocket, true);
$data = $this->feedGetUri($feed, $manifestUrl, $HttpSocket);
try {
return $this->jsonDecode($data);
@ -218,6 +228,65 @@ class Feed extends AppModel
}
}
/**
* @param int $feedId
*/
private function cleanFileCache($feedId)
{
foreach (["misp_feed_{$feedId}_manifest.cache.gz", "misp_feed_{$feedId}_manifest.etag", "misp_feed_$feedId.cache", "misp_feed_$feedId.etag"] as $fileName) {
FileAccessTool::deleteFileIfExists(self::CACHE_DIR . $fileName);
}
}
/**
* Get remote manifest for feed with etag checking.
* @param array $feed
* @param HttpSocketExtended $HttpSocket
* @return array
* @throws HttpSocketHttpException
* @throws JsonException
*/
private function getRemoteManifest(array $feed, HttpSocketExtended $HttpSocket)
{
$feedCache = self::CACHE_DIR . 'misp_feed_' . (int)$feed['Feed']['id'] . '_manifest.cache.gz';
$feedCacheEtag = self::CACHE_DIR . 'misp_feed_' . (int)$feed['Feed']['id'] . '_manifest.etag';
$etag = null;
if (file_exists($feedCache) && file_exists($feedCacheEtag)) {
$etag = file_get_contents($feedCacheEtag);
}
$manifestUrl = $feed['Feed']['url'] . '/manifest.json';
try {
$response = $this->feedGetUriRemote($feed, $manifestUrl, $HttpSocket, $etag);
} catch (HttpSocketHttpException $e) {
if ($e->getCode() === 304) { // not modified
$data = file_get_contents("compress.zlib://$feedCache");
if ($data === false) {
return $this->feedGetUriRemote($feed, $manifestUrl, $HttpSocket)->json(); // cache file is not readable, fetch without etag
}
return $this->jsonDecode($data);
} else {
throw $e;
}
}
if ($response->getHeader('ETag')) {
try {
FileAccessTool::writeCompressedFile($feedCache, $response->body);
FileAccessTool::writeToFile($feedCacheEtag, $response->getHeader('ETag'));
} catch (Exception $e) {
FileAccessTool::deleteFileIfExists($feedCacheEtag);
$this->logException("Could not save file `$feedCache` to cache.", $e, LOG_NOTICE);
}
} else {
FileAccessTool::deleteFileIfExists($feedCacheEtag);
}
return $response->json();
}
/**
* @param array $feed
* @param HttpSocket|null $HttpSocket Null can be for local feed
@ -226,11 +295,62 @@ class Feed extends AppModel
*/
public function getManifest(array $feed, HttpSocket $HttpSocket = null)
{
$events = $this->downloadManifest($feed, $HttpSocket);
$events = $this->isFeedLocal($feed) ? $this->downloadManifest($feed) : $this->getRemoteManifest($feed, $HttpSocket);
$events = $this->__filterEventsIndex($events, $feed);
return $events;
}
/**
* Load remote file with cache support and etag checking.
* @param array $feed
* @param HttpSocket $HttpSocket
* @return string
* @throws HttpSocketHttpException
*/
private function getFreetextFeedRemote(array $feed, HttpSocket $HttpSocket)
{
$feedCache = self::CACHE_DIR . 'misp_feed_' . (int)$feed['Feed']['id'] . '.cache';
$feedCacheEtag = self::CACHE_DIR . 'misp_feed_' . (int)$feed['Feed']['id'] . '.etag';
$etag = null;
if (file_exists($feedCache)) {
if (time() - filemtime($feedCache) < 600) {
$data = file_get_contents($feedCache);
if ($data !== false) {
return $data;
}
} else if (file_exists($feedCacheEtag)) {
$etag = file_get_contents($feedCacheEtag);
}
}
try {
$response = $this->feedGetUriRemote($feed, $feed['Feed']['url'], $HttpSocket, $etag);
} catch (HttpSocketHttpException $e) {
if ($e->getCode() === 304) { // not modified
$data = file_get_contents($feedCache);
if ($data === false) {
return $this->feedGetUriRemote($feed, $feed['Feed']['url'], $HttpSocket); // cache file is not readable, fetch without etag
}
return $data;
} else {
throw $e;
}
}
try {
FileAccessTool::writeToFile($feedCache, $response->body);
if ($response->getHeader('ETag')) {
FileAccessTool::writeToFile($feedCacheEtag, $response->getHeader('ETag'));
}
} catch (Exception $e) {
FileAccessTool::deleteFileIfExists($feedCacheEtag);
$this->logException("Could not save file `$feedCache` to cache.", $e, LOG_NOTICE);
}
return $response->body;
}
/**
* @param array $feed
* @param HttpSocket|null $HttpSocket Null can be for local feed
@ -243,29 +363,11 @@ class Feed extends AppModel
*/
public function getFreetextFeed($feed, HttpSocket $HttpSocket = null, $type = 'freetext', $page = 1, $limit = 60, &$params = array())
{
$isLocal = $this->isFeedLocal($feed);
$data = false;
if (!$isLocal) {
$feedCache = APP . 'tmp' . DS . 'cache' . DS . 'misp_feed_' . intval($feed['Feed']['id']) . '.cache';
if (file_exists($feedCache)) {
$file = new File($feedCache);
if (time() - $file->lastChange() < 600) {
$data = $file->read();
if ($data === false) {
throw new Exception("Could not read feed cache file '$feedCache'.");
}
}
}
}
if ($data === false) {
if ($this->isFeedLocal($feed)) {
$feedUrl = $feed['Feed']['url'];
$data = $this->feedGetUri($feed, $feedUrl, $HttpSocket, true);
if (!$isLocal) {
file_put_contents($feedCache, $data); // save to cache
}
$data = $this->feedGetUri($feed, $feedUrl, $HttpSocket);
} else {
$data = $this->getFreetextFeedRemote($feed, $HttpSocket);
}
App::uses('ComplexTypeTool', 'Tools');
@ -599,10 +701,9 @@ class Feed extends AppModel
$currentItem++;
}
foreach ($actions['edit'] as $editTarget) {
$uuid = $editTarget['uuid'];
foreach ($actions['edit'] as $uuid) {
try {
$result = $this->__updateEventFromFeed($HttpSocket, $feed, $uuid, $editTarget['id'], $user, $filterRules);
$result = $this->__updateEventFromFeed($HttpSocket, $feed, $uuid, $user, $filterRules);
if ($result === true) {
$results['add']['success'] = $uuid;
} else if ($result !== 'blocked') {
@ -772,7 +873,7 @@ class Feed extends AppModel
public function downloadEventFromFeed(array $feed, $uuid)
{
$filerRules = $this->__prepareFilterRules($feed);
$HttpSocket = $this->isFeedLocal($feed) ? null : $this->__setupHttpSocket($feed);
$HttpSocket = $this->isFeedLocal($feed) ? null : $this->__setupHttpSocket();
$event = $this->downloadAndParseEventFromFeed($feed, $uuid, $HttpSocket);
return $this->__prepareEvent($event, $feed, $filerRules);
}
@ -887,11 +988,11 @@ class Feed extends AppModel
return $filterRules;
}
private function __setupHttpSocket($feed)
private function __setupHttpSocket()
{
App::uses('SyncTool', 'Tools');
$syncTool = new SyncTool();
return $syncTool->setupHttpSocketFeed($feed);
return $syncTool->setupHttpSocketFeed();
}
/**
@ -918,13 +1019,12 @@ class Feed extends AppModel
* @param HttpSocket|null $HttpSocket Null can be for local feed
* @param array $feed
* @param string $uuid
* @param int $eventId
* @param $user
* @param array|bool $filterRules
* @return mixed
* @throws Exception
*/
private function __updateEventFromFeed(HttpSocket $HttpSocket = null, $feed, $uuid, $eventId, $user, $filterRules)
private function __updateEventFromFeed(HttpSocket $HttpSocket = null, $feed, $uuid, $user, $filterRules)
{
$event = $this->downloadAndParseEventFromFeed($feed, $uuid, $HttpSocket);
$event = $this->__prepareEvent($event, $feed, $filterRules);
@ -963,7 +1063,7 @@ class Feed extends AppModel
$this->data['Feed']['settings'] = json_decode($this->data['Feed']['settings'], true);
}
$HttpSocket = $this->isFeedLocal($this->data) ? null : $this->__setupHttpSocket($this->data);
$HttpSocket = $this->isFeedLocal($this->data) ? null : $this->__setupHttpSocket();
if ($this->data['Feed']['source_format'] === 'misp') {
$this->jobProgress($jobId, 'Fetching event manifest.');
try {
@ -1024,9 +1124,7 @@ class Feed extends AppModel
{
if ($this->isFeedLocal($feed)) {
if (isset($feed['Feed']['delete_local_file']) && $feed['Feed']['delete_local_file']) {
if (file_exists($feed['Feed']['url'] . $file)) {
unlink($feed['Feed']['url'] . $file);
}
FileAccessTool::deleteFileIfExists($feed['Feed']['url'] . $file);
}
}
return true;
@ -1142,13 +1240,10 @@ class Feed extends AppModel
$data[$key]['to_ids'] = $feed['Feed']['override_ids'] ? 0 : $value['to_ids'];
$uniqueValues[$value['value']] = true;
}
$data = array_values($data);
foreach ($data as $k => $chunk) {
$this->Event->Attribute->create();
$this->Event->Attribute->save($chunk);
if ($k % 100 === 0) {
$this->jobProgress($jobId, null, 50 + round(($k + 1) / count($data) * 50));
}
$chunks = array_chunk($data, 100);
foreach ($chunks as $k => $chunk) {
$this->Event->Attribute->saveMany($chunk, ['validate' => true, 'parentEvent' => $event]);
$this->jobProgress($jobId, null, 50 + round(($k * 100 + 1) / count($data) * 50));
}
if (!empty($data) || !empty($attributesToDelete)) {
unset($event['Event']['timestamp']);
@ -1236,7 +1331,7 @@ class Feed extends AppModel
private function __cacheFeed($feed, $redis, $jobId = false)
{
$HttpSocket = $this->isFeedLocal($feed) ? null : $this->__setupHttpSocket($feed);
$HttpSocket = $this->isFeedLocal($feed) ? null : $this->__setupHttpSocket();
if ($feed['Feed']['source_format'] === 'misp') {
return $this->__cacheMISPFeed($feed, $redis, $HttpSocket, $jobId);
} else {
@ -1794,54 +1889,55 @@ class Feed extends AppModel
* @param array $feed
* @param string $uri
* @param HttpSocket|null $HttpSocket Null can be for local feed
* @param bool $followRedirect
* @return string
* @throws Exception
*/
private function feedGetUri($feed, $uri, HttpSocket $HttpSocket = null, $followRedirect = false)
private function feedGetUri($feed, $uri, HttpSocket $HttpSocket = null)
{
if ($this->isFeedLocal($feed)) {
if (file_exists($uri)) {
$data = file_get_contents($uri);
if ($data === false) {
throw new Exception("Could not read local file '$uri'.");
}
return $data;
return FileAccessTool::readFromFile($uri);
} else {
throw new Exception("Local file '$uri' doesn't exists.");
}
}
if ($HttpSocket === null) {
throw new Exception("Feed {$feed['Feed']['name']} is not local, but HttpSocket is not initialized.");
return $this->feedGetUriRemote($feed, $uri, $HttpSocket)->body;
}
/**
* @param array $feed
* @param string $uri
* @param HttpSocket $HttpSocket
* @param string|null $etag
* @return false|HttpSocketResponse
* @throws HttpSocketHttpException
*/
private function feedGetUriRemote(array $feed, $uri, HttpSocket $HttpSocket, $etag = null)
{
$request = $this->__createFeedRequest($feed['Feed']['headers']);
if ($etag) {
$request['header']['If-None-Match'] = $etag;
}
$request = $this->__createFeedRequest($feed['Feed']['headers']);
try {
if ($followRedirect) {
$response = $this->getFollowRedirect($HttpSocket, $uri, $request);
} else {
$response = $HttpSocket->get($uri, array(), $request);
}
$response = $this->getFollowRedirect($HttpSocket, $uri, $request);
} catch (Exception $e) {
throw new Exception("Fetching the '$uri' failed with exception: {$e->getMessage()}", 0, $e);
}
if ($response->code != 200) { // intentionally !=
throw new Exception("Fetching the '$uri' failed with HTTP error {$response->code}: {$response->reasonPhrase}");
throw new HttpSocketHttpException($response, $uri);
}
$data = $response->body;
$contentType = $response->getHeader('Content-Type');
if ($contentType === 'application/zip') {
$zipFile = new File($this->tempFileName());
$zipFile->write($data);
$zipFile->write($response->body);
$zipFile->close();
try {
$data = $this->unzipFirstFile($zipFile);
$response->body = $this->unzipFirstFile($zipFile);
} catch (Exception $e) {
throw new Exception("Fetching the '$uri' failed: {$e->getMessage()}");
} finally {
@ -1849,7 +1945,7 @@ class Feed extends AppModel
}
}
return $data;
return $response;
}
/**
@ -1868,7 +1964,7 @@ class Feed extends AppModel
for ($i = 0; $i < $iterations; $i++) {
$response = $HttpSocket->get($url, array(), $request);
if ($response->isRedirect()) {
$HttpSocket = $this->__setupHttpSocket(null); // Replace $HttpSocket with fresh instance
$HttpSocket = $this->__setupHttpSocket(); // Replace $HttpSocket with fresh instance
$url = trim($response->getHeader('Location'), '=');
} else {
return $response;

View File

@ -7,6 +7,7 @@ App::uses('TmpFileTool', 'Tools');
* @property SharingGroup $SharingGroup
* @property Attribute $Attribute
* @property ObjectReference $ObjectReference
* @property ObjectTemplate $ObjectTemplate
*/
class MispObject extends AppModel
{
@ -299,11 +300,8 @@ class MispObject extends AppModel
$pubToZmq = Configure::read('Plugin.ZeroMQ_enable') &&
Configure::read('Plugin.ZeroMQ_object_notifications_enable') &&
empty($this->data['Object']['skip_zmq']);
$kafkaTopic = Configure::read('Plugin.Kafka_object_notifications_topic');
$pubToKafka = Configure::read('Plugin.Kafka_enable') &&
Configure::read('Plugin.Kafka_object_notifications_enable') &&
!empty($kafkaTopic) &&
empty($this->data['Object']['skip_kafka']);
$kafkaTopic = $this->kafkaTopic('object');
$pubToKafka = $kafkaTopic && empty($this->data['Object']['skip_kafka']);
if ($pubToZmq || $pubToKafka) {
$object = $this->find('first', array(
'conditions' => array('Object.id' => $this->id),
@ -966,14 +964,13 @@ class MispObject extends AppModel
* @param array $object
* @param int $eventId
* @param array $user
* @param false $log - Not used anymore
* @param bool $unpublish
* @param false $breakOnDuplicate
* @param array|false $parentEvent
* @return bool|string
* @throws Exception
*/
public function captureObject($object, $eventId, $user, $log = false, $unpublish = true, $breakOnDuplicate = false, $parentEvent = false)
public function captureObject($object, $eventId, $user, $unpublish = true, $breakOnDuplicate = false, $parentEvent = false)
{
$this->create();
if (!isset($object['Object'])) {
@ -990,28 +987,25 @@ class MispObject extends AppModel
return true;
}
}
if (isset($object['Object']['id'])) {
unset($object['Object']['id']);
}
unset($object['Object']['id']);
$object['Object']['event_id'] = $eventId;
if ($this->save($object)) {
if ($unpublish) {
$this->Event->unpublishEvent($eventId);
}
$objectId = $this->id;
if (!empty($object['Object']['Attribute'])) {
foreach ($object['Object']['Attribute'] as $attribute) {
$this->Attribute->captureAttribute($attribute, $eventId, $user, $objectId, false, $parentEvent);
}
}
return true;
} else {
if (!$this->save($object)) {
$this->loadLog()->createLogEntry($user, 'add', 'Object', 0,
'Object dropped due to validation for Event ' . $eventId . ' failed: ' . $object['Object']['name'],
'Validation errors: ' . json_encode($this->validationErrors) . ' Full Object: ' . json_encode($object)
);
return 'fail';
}
return 'fail';
if ($unpublish) {
$this->Event->unpublishEvent($eventId);
}
$objectId = $this->id;
if (!empty($object['Object']['Attribute'])) {
foreach ($object['Object']['Attribute'] as $attribute) {
$this->Attribute->captureAttribute($attribute, $eventId, $user, $objectId, false, $parentEvent);
}
}
return true;
}
public function editObject($object, array $event, $user, $log, $force = false, &$nothingToChange = false)

View File

@ -45,8 +45,7 @@ class ObjectReference extends AppModel
$this->data['ObjectReference']['uuid'] = CakeText::uuid();
}
if (empty($this->data['ObjectReference']['timestamp'])) {
$date = new DateTime();
$this->data['ObjectReference']['timestamp'] = $date->getTimestamp();
$this->data['ObjectReference']['timestamp'] = time();
}
if (!isset($this->data['ObjectReference']['comment'])) {
$this->data['ObjectReference']['comment'] = '';
@ -57,9 +56,8 @@ class ObjectReference extends AppModel
public function afterSave($created, $options = array())
{
$pubToZmq = Configure::read('Plugin.ZeroMQ_enable') && Configure::read('Plugin.ZeroMQ_object_reference_notifications_enable');
$kafkaTopic = Configure::read('Plugin.Kafka_object_reference_notifications_topic');
$pubToKafka = Configure::read('Plugin.Kafka_enable') && Configure::read('Plugin.Kafka_object_reference_notifications_enable') && !empty($kafkaTopic);
if ($pubToZmq || $pubToKafka) {
$kafkaTopic = $this->kafkaTopic('object_reference');
if ($pubToZmq || $kafkaTopic) {
$object_reference = $this->find('first', array(
'conditions' => array('ObjectReference.id' => $this->id),
'recursive' => -1
@ -72,7 +70,7 @@ class ObjectReference extends AppModel
$pubSubTool = $this->getPubSubTool();
$pubSubTool->object_reference_save($object_reference, $action);
}
if ($pubToKafka) {
if ($kafkaTopic) {
$kafkaPubTool = $this->getKafkaPubTool();
$kafkaPubTool->publishJson($kafkaTopic, $object_reference, $action);
}

View File

@ -1,6 +1,7 @@
<?php
App::uses('AppModel', 'Model');
App::uses('ConnectionManager', 'Model');
App::uses('FileAccessTool', 'Tools');
/**
* @property Event $Event
@ -429,37 +430,39 @@ class Organisation extends AppModel
}
/**
* @param array $event
* Attach organisations to evnet
* @param array $data
* @param array $fields
* @return array
*/
public function attachOrgs($data, $fields, $scope = 'Event')
public function attachOrgs($data, $fields)
{
$event = $data['Event'];
$toFetch = [];
if (!isset($this->__orgCache[$data[$scope]['orgc_id']])) {
$toFetch[] = $data[$scope]['orgc_id'];
if (!isset($this->__orgCache[$event['orgc_id']])) {
$toFetch[] = $event['orgc_id'];
}
if (!isset($this->__orgCache[$data[$scope]['org_id']]) && $data[$scope]['org_id'] != $data[$scope]['orgc_id']) {
$toFetch[] = $data[$scope]['org_id'];
if (!isset($this->__orgCache[$event['org_id']]) && $event['org_id'] != $event['orgc_id']) {
$toFetch[] = $event['org_id'];
}
if (!empty($toFetch)) {
$orgs = $this->find('all', array(
'conditions' => array('id' => $toFetch),
'recursive' => -1,
'fields' => $fields
'fields' => $fields,
));
foreach ($orgs as $org) {
$this->__orgCache[$org[$this->alias]['id']] = $org[$this->alias];
}
}
$data['Orgc'] = $this->__orgCache[$data[$scope]['orgc_id']];
$data['Org'] = $this->__orgCache[$data[$scope]['org_id']];
$data['Orgc'] = $this->__orgCache[$event['orgc_id']];
$data['Org'] = $this->__orgCache[$event['org_id']];
return $data;
}
public function getOrgIdsFromMeta($metaConditions)
{
$orgIds = $this->find('list', array(
$orgIds = $this->find('column', array(
'conditions' => $metaConditions,
'fields' => array('id'),
'recursive' => -1
@ -467,7 +470,7 @@ class Organisation extends AppModel
if (empty($orgIds)) {
return array(-1);
}
return array_values($orgIds);
return $orgIds;
}
public function checkDesiredOrg($suggestedOrg, $registration)
@ -515,12 +518,8 @@ class Organisation extends AppModel
// Check if there is event from given org that can current user see
$eventConditions = $this->Event->createEventConditions($user);
$eventConditions['AND']['Event.orgc_id'] = $orgId;
$event = $this->Event->find('first', array(
'fields' => array('Event.id'),
'recursive' => -1,
'conditions' => $eventConditions,
));
if (empty($event)) {
$event = $this->Event->hasAny($eventConditions);
if (!$event) {
$proposalConditions = $this->Event->ShadowAttribute->buildConditions($user);
$proposalConditions['AND']['ShadowAttribute.org_id'] = $orgId;
$proposal = $this->Event->ShadowAttribute->find('first', array(
@ -574,16 +573,18 @@ class Organisation extends AppModel
return [];
}
/**
* @return array
*/
private function getCountryGalaxyCluster()
{
static $list;
if (!$list) {
$file = new File(APP . '/files/misp-galaxy/clusters/country.json');
if ($file->exists()) {
$list = $this->jsonDecode($file->read())['values'];
$file->close();
} else {
$this->log("MISP Galaxy are not updated, countries will not be available.", LOG_WARNING);
try {
$content = FileAccessTool::readFromFile(APP . '/files/misp-galaxy/clusters/country.json');
$list = $this->jsonDecode($content)['values'];
} catch (Exception $e) {
$this->logException("MISP Galaxy are not updated, countries will not be available.", $e, LOG_WARNING);
$list = [];
}
}

View File

@ -2,6 +2,7 @@
App::uses('AppModel', 'Model');
App::uses('GpgTool', 'Tools');
App::uses('ServerSyncTool', 'Tools');
App::uses('FileAccessTool', 'Tools');
/**
* @property-read array $serverSettings
@ -2141,11 +2142,10 @@ class Server extends AppModel
}
}
$value = trim($value);
if ($setting['type'] == 'boolean') {
$value = ($value ? true : false);
}
if ($setting['type'] == 'numeric') {
$value = intval($value);
if ($setting['type'] === 'boolean') {
$value = (bool)$value;
} else if ($setting['type'] === 'numeric') {
$value = (int)($value);
}
if (!empty($setting['test'])) {
$testResult = $this->{$setting['test']}($value);
@ -2159,27 +2159,25 @@ class Server extends AppModel
$errorMessage = $testResult;
}
return $errorMessage;
} else {
$oldValue = Configure::read($setting['name']);
$settingSaveResult = $this->serverSettingsSaveValue($setting['name'], $value);
if ($settingSaveResult) {
$this->Log = ClassRegistry::init('Log');
$change = array($setting['name'] => array($oldValue, $value));
$this->Log->createLogEntry($user, 'serverSettingsEdit', 'Server', 0, 'Server setting changed', $change);
}
$oldValue = Configure::read($setting['name']);
$settingSaveResult = $this->serverSettingsSaveValue($setting['name'], $value);
if ($settingSaveResult) {
$change = array($setting['name'] => array($oldValue, $value));
$this->loadLog()->createLogEntry($user, 'serverSettingsEdit', 'Server', 0, 'Server setting changed', $change);
// execute after hook
if (isset($setting['afterHook'])) {
$afterResult = call_user_func_array(array($this, $setting['afterHook']), array($setting['name'], $value));
if ($afterResult !== true) {
$change = 'There was an issue after setting a new setting. The error message returned is: ' . $afterResult;
$this->Log->createLogEntry($user, 'serverSettingsEdit', 'Server', 0, 'Server setting issue', $change);
return $afterResult;
}
// execute after hook
if (isset($setting['afterHook'])) {
$afterResult = call_user_func_array(array($this, $setting['afterHook']), array($setting['name'], $value));
if ($afterResult !== true) {
$change = 'There was an issue after setting a new setting. The error message returned is: ' . $afterResult;
$this->loadLog()->createLogEntry($user, 'serverSettingsEdit', 'Server', 0, 'Server setting issue', $change);
return $afterResult;
}
return true;
} else {
return __('Something went wrong. MISP tried to save a malformed config file. Setting change reverted.');
}
return true;
} else {
return __('Something went wrong. MISP tried to save a malformed config file. Setting change reverted.');
}
}
@ -2197,26 +2195,18 @@ class Server extends AppModel
}
// validate if current config.php is intact:
$current = file_get_contents($configFilePath);
$current = trim($current);
if (strlen($current) < 20) {
$this->Log = ClassRegistry::init('Log');
$this->Log->create();
$this->Log->save(array(
'org' => 'SYSTEM',
'model' => 'Server',
'model_id' => 0,
'email' => 'SYSTEM',
'action' => 'error',
'user_id' => 0,
'title' => 'Error: Tried to modify server settings but current config is broken.',
));
$current = FileAccessTool::readFromFile($configFilePath);
if (strlen(trim($current)) < 20) {
$this->loadLog()->createLogEntry('SYSTEM', 'error', 'Server', 0, 'Error: Tried to modify server settings but current config is broken.');
return false;
}
$safeConfigChanges = empty(Configure::read('MISP.server_settings_skip_backup_rotate'));
if ($safeConfigChanges) {
$backupFilePath = APP . 'Config' . DS . 'config.backup.php';
// Create current config file backup
copy($configFilePath, APP . 'Config' . DS . 'config.php.bk');
if (!copy($configFilePath, $backupFilePath)) {
throw new Exception("Could not create config backup `$backupFilePath`.");
}
}
$settingObject = $this->getCurrentServerSettings();
foreach ($settingObject as $branchName => $branch) {
@ -2263,36 +2253,32 @@ class Server extends AppModel
if ($safeConfigChanges) {
$previous_file_perm = substr(sprintf('%o', fileperms($configFilePath)), -4);
$randomFilename = $this->generateRandomFileName();
// To protect us from 2 admin users having a concurrent file write to the config file, solar flares and the bogeyman
if (file_put_contents(APP . 'Config' . DS . $randomFilename, $settingsString) === false) {
try {
$tmpFile = FileAccessTool::writeToTempFile($settingsString);
} catch (Exception $e) {
$this->logException('Could not create temp config file.', $e);
$this->loadLog()->createLogEntry('SYSTEM', 'error', 'Server', 0, 'Error: Could not create temp config file.');
return false;
}
rename(APP . 'Config' . DS . $randomFilename, $configFilePath);
if (!rename($tmpFile, $configFilePath)) {
FileAccessTool::deleteFile($tmpFile);
throw new Exception("Could not rename `$tmpFile` to config file `$configFilePath`.");
}
if (function_exists('opcache_reset')) {
opcache_reset();
}
chmod($configFilePath, octdec($previous_file_perm));
$config_saved = file_get_contents($configFilePath);
$config_saved = FileAccessTool::readFromFile($configFilePath);
// if the saved config file is empty, restore the backup.
if (strlen($config_saved) < 20) {
copy(APP . 'Config' . DS . 'config.php.bk', $configFilePath);
$this->Log = ClassRegistry::init('Log');
$this->Log->create();
$this->Log->save(array(
'org' => 'SYSTEM',
'model' => 'Server',
'model_id' => 0,
'email' => 'SYSTEM',
'action' => 'error',
'user_id' => 0,
'title' => 'Error: Something went wrong saving the config file, reverted to backup file.',
));
rename($backupFilePath, $configFilePath);
$this->loadLog()->createLogEntry('SYSTEM', 'error', 'Server', 0, 'Error: Something went wrong saving the config file, reverted to backup file.');
return false;
} else {
FileAccessTool::deleteFile($backupFilePath);
}
} else {
file_put_contents($configFilePath, $settingsString);
FileAccessTool::writeToFile($configFilePath, $settingsString);
if (function_exists('opcache_reset')) {
opcache_reset();
}
@ -3837,36 +3823,47 @@ class Server extends AppModel
$status = array();
foreach ($submodules_names as $submodule_name_info) {
$submodule_name_info = explode(' ', $submodule_name_info);
$superproject_submodule_commit_id = $submodule_name_info[0];
$submodule_name = $submodule_name_info[1];
list($superproject_submodule_commit_id, $submodule_name) = $submodule_name_info;
$temp = $this->getSubmoduleGitStatus($submodule_name, $superproject_submodule_commit_id);
if ( !empty($temp) ) {
if (!empty($temp) ) {
$status[$submodule_name] = $temp;
}
}
return $status;
}
private function _isAcceptedSubmodule($submodule) {
$accepted_submodules_names = array('PyMISP',
private function _isAcceptedSubmodule($submodule)
{
$accepted_submodules_names = array(
'PyMISP',
'app/files/misp-galaxy',
'app/files/taxonomies',
'app/files/misp-objects',
'app/files/noticelists',
'app/files/warninglists',
'app/files/misp-decaying-models',
'cti-python-stix2'
'app/files/scripts/cti-python-stix2',
'app/files/scripts/misp-opendata',
'app/files/scripts/python-maec',
'app/files/scripts/python-stix',
);
return in_array($submodule, $accepted_submodules_names);
return in_array($submodule, $accepted_submodules_names, true);
}
public function getSubmoduleGitStatus($submodule_name, $superproject_submodule_commit_id) {
/**
* @param string $submodule_name
* @param string $superproject_submodule_commit_id
* @return array
*/
private function getSubmoduleGitStatus($submodule_name, $superproject_submodule_commit_id)
{
$status = array();
if ($this->_isAcceptedSubmodule($submodule_name)) {
$path = APP . '../' . $submodule_name;
$submodule_name=(strpos($submodule_name, '/') >= 0 ? explode('/', $submodule_name) : $submodule_name);
$submodule_name=end($submodule_name);
$submoduleRemote=exec('cd ' . $path . '; git config --get remote.origin.url');
//$submoduleRemote=exec('cd ' . $path . '; git config --get remote.origin.url');
exec(sprintf('cd %s; git rev-parse HEAD', $path), $submodule_current_commit_id);
if (!empty($submodule_current_commit_id[0])) {
$submodule_current_commit_id = $submodule_current_commit_id[0];

View File

@ -868,7 +868,7 @@ class ShadowAttribute extends AppModel
// Thumbnail doesn't exists, we need to generate it
$imageData = $this->getAttachment($shadowAttribute['ShadowAttribute']);
$imageData = $this->Attribute->resizeImage($imageData, $maxWidth, $maxHeight);
$imageData = $this->loadAttachmentTool()->resizeImage($imageData, $maxWidth, $maxHeight);
// Save just when requested default thumbnail size
if ($maxWidth == 200 && $maxHeight == 200) {

View File

@ -69,10 +69,8 @@ class SharingGroup extends AppModel
'access' => array()
);
public function beforeValidate($options = array())
{
parent::beforeValidate();
if (empty($this->data['SharingGroup']['uuid'])) {
$this->data['SharingGroup']['uuid'] = CakeText::uuid();
} else {
@ -86,12 +84,7 @@ class SharingGroup extends AppModel
$this->data['SharingGroup']['active'] = 0;
}
$this->data['SharingGroup']['modified'] = $date;
$sameNameSG = $this->find('first', array(
'conditions' => array('SharingGroup.name' => $this->data['SharingGroup']['name']),
'recursive' => -1,
'fields' => array('SharingGroup.name')
));
if (!empty($sameNameSG) && !isset($this->data['SharingGroup']['id'])) {
if (!isset($this->data['SharingGroup']['id']) && $this->hasAny(['SharingGroup.name' => $this->data['SharingGroup']['name']])) {
$this->data['SharingGroup']['name'] = $this->data['SharingGroup']['name'] . '_' . mt_rand(0, 9999);
}
return true;
@ -99,22 +92,16 @@ class SharingGroup extends AppModel
public function beforeDelete($cascade = false)
{
$countEvent = $this->Event->find('count', array(
'recursive' => -1,
'conditions' => array('sharing_group_id' => $this->id)
));
$countThread = $this->Thread->find('count', array(
'recursive' => -1,
'conditions' => array('sharing_group_id' => $this->id)
));
$countAttribute = $this->Attribute->find('count', array(
'recursive' => -1,
'conditions' => array('sharing_group_id' => $this->id)
));
if (($countEvent + $countThread + $countAttribute) == 0) {
return true;
if ($this->Event->hasAny(['sharing_group_id' => $this->id])) {
return false;
}
return false;
if ($this->Thread->hasAny(['sharing_group_id' => $this->id])) {
return false;
}
if ($this->Attribute->hasAny(['sharing_group_id' => $this->id])) {
return false;
}
return true;
}
/**
@ -273,9 +260,7 @@ class SharingGroup extends AppModel
'fields' => $orgFields,
'conditions' => ['id' => array_keys($orgsToFetch)],
]);
foreach ($orgs as $org) {
$orgsById[$org['Organisation']['id']] = $org['Organisation'];
}
$orgsById = array_column(array_column($orgs, 'Organisation'), null, 'id');
}
$serversById = [];
@ -285,9 +270,7 @@ class SharingGroup extends AppModel
'fields' => $serverFields,
'conditions' => ['id' => array_keys($serverToFetch)],
]);
foreach ($servers as $server) {
$serversById[$server['Server']['id']] = $server['Server'];
}
$serversById = array_column(array_column($servers, 'Server'), null, 'id');
}
foreach ($sharingGroups as &$sg) {
@ -321,7 +304,6 @@ class SharingGroup extends AppModel
// 3. Sync users
// a. as long as they are at least users of the SG (they can circumvent the extend rule to
// avoid situations where no one can create / edit an SG on an instance after a push)
public function checkIfAuthorisedToSave($user, $sg)
{
if (isset($sg[0])) {
@ -335,8 +317,9 @@ class SharingGroup extends AppModel
}
// First let us find out if we already have the SG
$local = $this->find('first', array(
'recursive' => -1,
'conditions' => array('uuid' => $sg['uuid'])
'recursive' => -1,
'conditions' => array('uuid' => $sg['uuid']),
'fields' => ['id'],
));
if (empty($local)) {
$orgCheck = false;
@ -349,6 +332,7 @@ class SharingGroup extends AppModel
if ($org['Organisation']['uuid'] == $user['Organisation']['uuid']) {
if ($user['Role']['perm_sync'] || $org['extend'] == 1) {
$orgCheck = true;
break;
}
}
}
@ -413,36 +397,31 @@ class SharingGroup extends AppModel
return true;
}
}
$sgo = $this->SharingGroupOrg->find('first', array(
'conditions' => array(
'sharing_group_id' => $id,
'org_id' => $user['org_id'],
'extend' => 1,
),
'recursive' => -1,
'fields' => array('id', 'org_id', 'extend')
return $this->SharingGroupOrg->hasAny(array(
'sharing_group_id' => $id,
'org_id' => $user['org_id'],
'extend' => 1,
));
if (empty($sgo)) {
return false;
} else {
return true;
}
}
public function checkIfExists($uuid)
{
return !empty($this->SharingGroup->find('first', array(
'conditions' => array('SharingGroup.uuid' => $uuid),
'recursive' => -1,
'fields' => array('SharingGroup.id')
)));
return $this->hasAny(['SharingGroup.uuid' => $uuid]);
}
// returns true if the SG exists and the user is allowed to see it
/**
* Returns true if the SG exists and the user is allowed to see it
* @param array $user
* @param int|string $id SG ID or UUID
* @param bool $adminCheck
* @return bool|mixed
*/
public function checkIfAuthorised($user, $id, $adminCheck = true)
{
if (isset($this->__sgAuthorisationCache['access'][boolval($adminCheck)][$id])) {
return $this->__sgAuthorisationCache['access'][boolval($adminCheck)][$id];
$adminCheck = (bool)$adminCheck;
if (isset($this->__sgAuthorisationCache['access'][$adminCheck][$id])) {
return $this->__sgAuthorisationCache['access'][$adminCheck][$id];
}
if (Validation::uuid($id)) {
$sgid = $this->find('first', array(
@ -453,6 +432,7 @@ class SharingGroup extends AppModel
if (empty($sgid)) {
return false;
}
$uuid = $id;
$id = $sgid['SharingGroup']['id'];
} else {
if (!$this->exists($id)) {
@ -462,12 +442,15 @@ class SharingGroup extends AppModel
if (!isset($user['id'])) {
throw new MethodNotAllowedException('Invalid user.');
}
if (($adminCheck && $user['Role']['perm_site_admin']) || $this->SharingGroupServer->checkIfAuthorised($id) || $this->SharingGroupOrg->checkIfAuthorised($id, $user['org_id'])) {
$this->__sgAuthorisationCache['access'][boolval($adminCheck)][$id] = true;
return true;
$authorized = ($adminCheck && $user['Role']['perm_site_admin']) ||
$this->SharingGroupServer->checkIfAuthorised($id) ||
$this->SharingGroupOrg->checkIfAuthorised($id, $user['org_id']);
$this->__sgAuthorisationCache['access'][$adminCheck][$id] = $authorized;
if (isset($uuid)) {
// If uuid was provided, cache also result by UUID to make check faster
$this->__sgAuthorisationCache['access'][$adminCheck][$uuid] = $authorized;
}
$this->__sgAuthorisationCache['access'][boolval($adminCheck)][$id] = false;
return false;
return $authorized;
}
/**
@ -552,36 +535,6 @@ class SharingGroup extends AppModel
return false;
}
public function getSGSyncRules($sg)
{
$results = array(
'conditional' => array(),
'full' => array(),
'orgs' => array(),
'no_server_settings' => false
);
if (isset($sg['SharingGroupServer'])) {
foreach ($sg['SharingGroupServer'] as $server) {
if ($server['server_id'] != 0) {
if ($server['all_orgs']) {
$results['full'][] = $server['id'];
} else {
$results['conditional'][] = $server['id'];
}
}
}
if (empty($results['full']) && empty($results['conditional'])) {
return false;
}
} else {
$results['no_server_settings'] = true;
}
foreach ($sg['SharingGroupOrg'] as $org) {
$results['orgs'][] = $org['Organisation']['uuid'];
}
return $results;
}
/*
* Capture a sharing group
* Return false if something goes wrong
@ -598,7 +551,6 @@ class SharingGroup extends AppModel
if (!empty($server) && !empty($server['Server']['local'])) {
$syncLocal = true;
}
$this->Log = ClassRegistry::init('Log');
$existingSG = !isset($sg['uuid']) ? null : $this->find('first', array(
'recursive' => -1,
'conditions' => array('SharingGroup.uuid' => $sg['uuid']),
@ -609,7 +561,6 @@ class SharingGroup extends AppModel
)
));
$forceUpdate = false;
$sg_id = 0;
if (empty($existingSG)) {
if (!$user['Role']['perm_sharing_group']) {
return false;
@ -619,7 +570,7 @@ class SharingGroup extends AppModel
return false;
}
} else {
$existingCaptureResult = $this->captureSGExisting($user, $existingSG, $sg, $syncLocal);
$existingCaptureResult = $this->captureSGExisting($user, $existingSG, $sg);
if ($existingCaptureResult !== true) {
return $existingCaptureResult;
}
@ -640,17 +591,16 @@ class SharingGroup extends AppModel
/*
* Capture updates for an existing sharing group
* Return true if updates are occuring
* Return true if updates are occurring
* Return false if something goes wrong
* Return an integer if no update is done but the sharing group can be attached
*
* @param array $user
* @param array $existingSG
* @param array $sg
* @param boolean syncLocal
* @return int || false || true
*/
public function captureSGExisting($user, $existingSG, $sg, $syncLocal)
private function captureSGExisting($user, $existingSG, $sg)
{
if (!$this->checkIfAuthorised($user, $existingSG['SharingGroup']['id']) && !$user['Role']['perm_sync']) {
return false;
@ -687,7 +637,7 @@ class SharingGroup extends AppModel
* @param boolean syncLocal
* @return int || false
*/
public function captureSGNew($user, $sg, $syncLocal)
private function captureSGNew($user, $sg, $syncLocal)
{
// check if current user is contained in the SG and we are in a local sync setup
if (!empty($sg['uuid'])) {
@ -704,25 +654,14 @@ class SharingGroup extends AppModel
!($user['Role']['perm_sync'] && $syncLocal ) &&
!$authorisedToSave
) {
$this->Log->create();
$entry = array(
'org' => $user['Organisation']['name'],
'model' => 'SharingGroup',
'model_id' => 0,
'email' => $user['email'],
'action' => 'error',
'user_id' => $user['id'],
'title' => "Tried to save a sharing group with UUID '{$sg['uuid']}' but the user does not belong to it."
);
$this->Log->save($entry);
$this->loadLog()->createLogEntry($user, 'error', 'SharingGroup', 0, "Tried to save a sharing group with UUID '{$sg['uuid']}' but the user does not belong to it.");
return false;
}
$this->create();
$newSG = array();
$date = date('Y-m-d H:i:s');
if (empty($sg['name'])) {
return false;
}
$this->create();
$date = date('Y-m-d H:i:s');
$newSG = [
'name' => $sg['name'],
'releasability' => !isset($sg['releasability']) ? '' : $sg['releasability'],
@ -1020,18 +959,4 @@ class SharingGroup extends AppModel
}
return $sg[0];
}
public function getSharingGroupIdByUuid($user, $data)
{
$sg = $this->find('first', array(
'conditions' => array('SharingGroup.uuid' => $data['sharing_group_id']),
'recursive' => -1,
'fields' => array('SharingGroup.id')
));
if (!empty($sg) && $this->checkIfAuthorised($user, $sg['SharingGroup']['id'])) {
$data['sharing_group_id'] = $sg['SharingGroup']['id'];
return $data;
}
return false;
}
}

View File

@ -86,14 +86,9 @@ class SharingGroupOrg extends AppModel
// pass a sharing group ID and an organisation ID, returns true if it has a matching attached organisation object
public function checkIfAuthorised($id, $org_id)
{
$sg = $this->find('first', array(
'conditions' => array('sharing_group_id' => $id, 'org_id' => $org_id),
'recursive' => -1,
'fields' => array('id'),
));
if (!empty($sg)) {
return true;
}
return false;
return $this->hasAny([
'sharing_group_id' => $id,
'org_id' => $org_id,
]);
}
}

View File

@ -97,14 +97,10 @@ class SharingGroupServer extends AppModel
// pass a sharing group ID, returns true if it has the local server object attached with "all_orgs" set
public function checkIfAuthorised($id)
{
$sg = $this->find('first', array(
'conditions' => array('sharing_group_id' => $id, 'all_orgs' => 1, 'server_id' => 0),
'recursive' => -1,
'fields' => array('id'),
));
if (!empty($sg)) {
return true;
}
return false;
return $this->hasAny([
'sharing_group_id' => $id,
'all_orgs' => 1,
'server_id' => 0
]);
}
}

View File

@ -77,11 +77,9 @@ class Sighting extends AppModel
public function afterSave($created, $options = array())
{
parent::afterSave($created, $options = array());
$pubToZmq = Configure::read('Plugin.ZeroMQ_enable') && Configure::read('Plugin.ZeroMQ_sighting_notifications_enable');
$kafkaTopic = Configure::read('Plugin.Kafka_sighting_notifications_topic');
$pubToKafka = Configure::read('Plugin.Kafka_enable') && Configure::read('Plugin.Kafka_sighting_notifications_enable') && !empty($kafkaTopic);
if ($pubToZmq || $pubToKafka) {
$kafkaTopic = $this->kafkaTopic('sighting');
if ($pubToZmq || $kafkaTopic) {
$user = array(
'org_id' => -1,
'Role' => array(
@ -93,7 +91,7 @@ class Sighting extends AppModel
$pubSubTool = $this->getPubSubTool();
$pubSubTool->sighting_save($sighting, 'add');
}
if ($pubToKafka) {
if ($kafkaTopic) {
$kafkaPubTool = $this->getKafkaPubTool();
$kafkaPubTool->publishJson($kafkaTopic, $sighting, 'add');
}
@ -105,9 +103,8 @@ class Sighting extends AppModel
{
parent::beforeDelete();
$pubToZmq = Configure::read('Plugin.ZeroMQ_enable') && Configure::read('Plugin.ZeroMQ_sighting_notifications_enable');
$kafkaTopic = Configure::read('Plugin.Kafka_sighting_notifications_topic');
$pubToKafka = Configure::read('Plugin.Kafka_enable') && Configure::read('Plugin.Kafka_sighting_notifications_enable') && !empty($kafkaTopic);
if ($pubToZmq || $pubToKafka) {
$kafkaTopic = $this->kafkaTopic('sighting');
if ($pubToZmq || $kafkaTopic) {
$user = array(
'org_id' => -1,
'Role' => array(
@ -119,7 +116,7 @@ class Sighting extends AppModel
$pubSubTool = $this->getPubSubTool();
$pubSubTool->sighting_save($sighting, 'delete');
}
if ($pubToKafka) {
if ($kafkaTopic) {
$kafkaPubTool = $this->getKafkaPubTool();
$kafkaPubTool->publishJson($kafkaTopic, $sighting, 'delete');
}

View File

@ -106,11 +106,9 @@ class Tag extends AppModel
public function afterSave($created, $options = array())
{
parent::afterSave($created, $options);
$pubToZmq = Configure::read('Plugin.ZeroMQ_enable') && Configure::read('Plugin.ZeroMQ_tag_notifications_enable');
$kafkaTopic = Configure::read('Plugin.Kafka_tag_notifications_topic');
$pubToKafka = Configure::read('Plugin.Kafka_enable') && Configure::read('Plugin.Kafka_tag_notifications_enable') && !empty($kafkaTopic);
if ($pubToZmq || $pubToKafka) {
$kafkaTopic = $this->kafkaTopic('tag');
if ($pubToZmq || $kafkaTopic) {
$tag = $this->find('first', array(
'recursive' => -1,
'conditions' => array('Tag.id' => $this->id)
@ -120,7 +118,7 @@ class Tag extends AppModel
$pubSubTool = $this->getPubSubTool();
$pubSubTool->tag_save($tag, $action);
}
if ($pubToKafka) {
if ($kafkaTopic) {
$kafkaPubTool = $this->getKafkaPubTool();
$kafkaPubTool->publishJson($kafkaTopic, $tag, $action);
}
@ -130,9 +128,8 @@ class Tag extends AppModel
public function beforeDelete($cascade = true)
{
$pubToZmq = Configure::read('Plugin.ZeroMQ_enable') && Configure::read('Plugin.ZeroMQ_tag_notifications_enable');
$kafkaTopic = Configure::read('Plugin.Kafka_tag_notifications_topic');
$pubToKafka = Configure::read('Plugin.Kafka_enable') && Configure::read('Plugin.Kafka_tag_notifications_enable') && !empty($kafkaTopic);
if ($pubToZmq || $pubToKafka) {
$kafkaTopic = $this->kafkaTopic('tag');
if ($pubToZmq || $kafkaTopic) {
if (!empty($this->id)) {
$tag = $this->find('first', array(
'recursive' => -1,
@ -142,7 +139,7 @@ class Tag extends AppModel
$pubSubTool = $this->getPubSubTool();
$pubSubTool->tag_save($tag, 'delete');
}
if ($pubToKafka) {
if ($kafkaTopic) {
$kafkaPubTool = $this->getKafkaPubTool();
$kafkaPubTool->publishJson($kafkaTopic, $tag, 'delete');
}
@ -386,25 +383,6 @@ class Tag extends AppModel
return $existingTag['Tag']['id'];
}
// find all tags that belong to a given eventId
public function findEventTags($eventId)
{
$tags = array();
$params = array(
'recursive' => 1,
'contain' => 'EventTag',
);
$result = $this->find('all', $params);
foreach ($result as $tag) {
foreach ($tag['EventTag'] as $eventTag) {
if ($eventTag['event_id'] == $eventId) {
$tags[] = $tag['Tag'];
}
}
}
return $tags;
}
public function random_color()
{
$colour = '#';

View File

@ -279,9 +279,8 @@ class User extends AppModel
public function afterSave($created, $options = array())
{
$pubToZmq = Configure::read('Plugin.ZeroMQ_enable') && Configure::read('Plugin.ZeroMQ_user_notifications_enable');
$kafkaTopic = Configure::read('Plugin.Kafka_user_notifications_topic');
$pubToKafka = Configure::read('Plugin.Kafka_enable') && Configure::read('Plugin.Kafka_user_notifications_enable') && !empty($kafkaTopic);
if ($pubToZmq || $pubToKafka) {
$kafkaTopic = $this->kafkaTopic('user');
if ($pubToZmq || $kafkaTopic) {
if (!empty($this->data)) {
$user = $this->data;
if (!isset($user['User'])) {
@ -311,7 +310,7 @@ class User extends AppModel
$pubSubTool = $this->getPubSubTool();
$pubSubTool->modified($user, 'user', $action);
}
if ($pubToKafka) {
if ($kafkaTopic) {
$kafkaPubTool = $this->getKafkaPubTool();
$kafkaPubTool->publishJson($kafkaTopic, $user, $action);
}

View File

@ -1,189 +0,0 @@
<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));
?>
<fieldset>
<legend><?php echo $action == 'add' ? __('Add Attribute') : __('Edit Attribute'); ?></legend>
<div id="formWarning" class="message ajaxMessage"></div>
<div id="compositeWarning" class="message <?php echo !empty($ajax) ? 'ajaxMessage' : '';?>" style="display:none;">Did you consider adding an object instead of a composite attribute?</div>
<div class="add_attribute_fields">
<?php
echo $this->Form->hidden('event_id');
echo $this->Form->input('category', array(
'empty' => __('(choose one)'),
'label' => __('Category ') . $this->element('formInfo', array('type' => 'category')),
));
echo $this->Form->input('type', array(
'empty' => __('(first choose category)'),
'label' => __('Type ') . $this->element('formInfo', array('type' => 'type')),
));
$initialDistribution = 5;
if (Configure::read('MISP.default_attribute_distribution') != null) {
if (Configure::read('MISP.default_attribute_distribution') === 'event') {
$initialDistribution = 5;
} else {
$initialDistribution = Configure::read('MISP.default_attribute_distribution');
}
}
?>
<div class="input clear"></div>
<?php
$distArray = array(
'options' => array($distributionLevels),
'label' => __('Distribution ') . $this->element('formInfo', array('type' => 'distribution')),
);
if ($action == 'add') {
$distArray['selected'] = $initialDistribution;
}
echo $this->Form->input('distribution', $distArray);
?>
<div id="SGContainer" style="display:none;">
<?php
if (!empty($sharingGroups)) {
echo $this->Form->input('sharing_group_id', array(
'options' => array($sharingGroups),
'label' => __('Sharing Group'),
));
}
?>
</div>
<?php
echo $this->Form->input('value', array(
'type' => 'textarea',
'error' => array('escape' => false),
'div' => 'input clear',
'class' => 'input-xxlarge'
));
?>
<div class="input clear"></div>
<?php
echo $this->Form->input('comment', array(
'type' => 'text',
'label' => __('Contextual Comment'),
'error' => array('escape' => false),
'div' => 'input clear',
'class' => 'input-xxlarge'
));
?>
<div class="input clear"></div>
<?php
echo $this->Form->input('to_ids', array(
'label' => __('for Intrusion Detection System'),
'type' => 'checkbox'
));
echo $this->Form->input('batch_import', array(
'type' => 'checkbox'
));
echo '<div class="input clear"></div>';
echo $this->Form->input('disable_correlation', array(
'type' => 'checkbox'
));
?>
</div>
</fieldset>
<p id="notice_message" style="display:none;"></p>
<?php if ($ajax): ?>
<div class="overlay_spacing">
<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 $action == 'add' ? $event_id : $attribute['Attribute']['id'];?>', '<?php echo $action; ?>')"><?php echo __('Submit'); ?></span>
<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
else:
?>
<?php
echo $this->Form->button('Submit', array('class' => 'btn btn-primary'));
endif;
echo $this->Form->end();
?>
</div>
<?php
if (!$ajax) {
$event['Event']['id'] = $event_id;
$event['Event']['published'] = $published;
echo $this->element('/genericElements/SideMenu/side_menu', array('menuList' => 'event', 'menuItem' => 'addAttribute', 'event' => $event));
}
?>
<script type="text/javascript">
var notice_list_triggers = <?php echo $notice_list_triggers; ?>;
var fieldsArray = new Array('AttributeCategory', 'AttributeType', 'AttributeValue', 'AttributeDistribution', 'AttributeComment', 'AttributeToIds', 'AttributeBatchImport', 'AttributeSharingGroupId');
<?php
$formInfoTypes = array('distribution' => 'Distribution', 'category' => 'Category', 'type' => 'Type');
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;
}
?>
//
//Generate Category / Type filtering array
//
var category_type_mapping = new Array();
<?php
foreach ($categoryDefinitions as $category => $def) {
echo "category_type_mapping['" . addslashes($category) . "'] = {";
$first = true;
foreach ($def['types'] as $type) {
if ($first) $first = false;
else echo ', ';
echo "'" . addslashes($type) . "' : '" . addslashes($type) . "'";
}
echo "}; \n";
}
?>
var composite_types = <?php echo json_encode($compositeTypes); ?>;
$(document).ready(function() {
<?php
if ($action == 'edit'):
?>
checkNoticeList('attribute');
<?php
endif;
?>
initPopoverContent('Attribute');
$('#AttributeDistribution').change(function() {
if ($('#AttributeDistribution').val() == 4) $('#SGContainer').show();
else $('#SGContainer').hide();
});
$("#AttributeCategory").on('change', function(e) {
formCategoryChanged('Attribute');
if ($(this).val() === 'Internal reference') {
$("#AttributeDistribution").val('0');
$('#SGContainer').hide();
}
});
$("#AttributeCategory, #AttributeType").change(function() {
checkNoticeList('attribute');
});
$("#AttributeCategory, #AttributeType, #AttributeDistribution").change(function() {
var start = $("#AttributeType").val();
initPopoverContent('Attribute');
$("#AttributeType").val(start);
if ($.inArray(start, composite_types) > -1) {
$('#compositeWarning').show();
} else {
$('#compositeWarning').hide();
}
});
<?php if ($ajax): ?>
$('#cancel_attribute_add').click(function() {
cancelPopoverForm();
});
<?php endif; ?>
});
</script>
<?php echo $this->Js->writeBuffer(); // Write cached scripts

View File

@ -1,193 +1,358 @@
<div class="attributes index">
<h2><?php echo __('Attributes'); ?></h2>
<?php
if ($isSearch == 1) {
// The following block should serve as an example and food
// for thought on how to optimize i18n & l10n (especially for languages that are not SOV)
$filterOptions = array(
'value' => __(" with the value containing "),
'tags' => __(" being tagged with "),
'id' => __(" from the events "),
'tag' => __(" carrying the tag(s) "),
'type' => __(" of type "),
'category' => __(" of category "),
'org' => __(" created by organisation ")
);
$temp = '';
foreach ($filterOptions as $fo => $text) {
if (!empty($filters[$fo])) {
$filter_options_string = $filters[$fo];
if (is_array($filter_options_string)) {
$filter_options_string = implode(' OR ', $filter_options_string);
}
$temp .= sprintf('%s <b>%s</b>', $text, h($filter_options_string));
}
}
echo sprintf("<h4>%s%s</h4>", __("Results for all attributes"), $temp);
}
?>
<div class="pagination">
<ul>
<?php
$paginator = $this->Paginator->prev('&laquo; ' . __('previous'), array('tag' => 'li', 'escape' => false), null, array('tag' => 'li', 'class' => 'prev disabled', 'escape' => false, 'disabledTag' => 'span'));
$paginator .= $this->Paginator->numbers(array('modulus' => 20, 'separator' => '', 'tag' => 'li', 'currentClass' => 'active', 'currentTag' => 'span'));
$paginator .= $this->Paginator->next(__('next') . ' &raquo;', array('tag' => 'li', 'escape' => false), null, array('tag' => 'li', 'class' => 'next disabled', 'escape' => false, 'disabledTag' => 'span'));
echo $paginator;
?>
</ul>
</div>
<?php
$headers = array(
$this->Paginator->sort('timestamp', __('Date')),
$this->Paginator->sort('event_id'),
$this->Paginator->sort('Event.orgc_id', __('Org')),
$this->Paginator->sort('category'),
$this->Paginator->sort('type'),
$this->Paginator->sort('value'),
__('Tags'),
__('Galaxies'),
$this->Paginator->sort('comment'),
__('Correlate'),
__('Related Events'),
__('Feed hits'),
sprintf('<span title="%s">%s', $attrDescriptions['signature']['desc'], $this->Paginator->sort('IDS')),
sprintf('<span title="%s">%s', $attrDescriptions['distribution']['desc'], $this->Paginator->sort('distribution')),
__('Sightings'),
__('Activity'),
__('Actions')
);
foreach ($headers as $k => &$header) {
$header = "<th>$header</th>";
}
?>
<table class="table table-striped table-hover table-condensed">
<tr><?= implode('', $headers) ?></tr>
<?php
$currentCount = 0;
if ($isSearch == 1) {
// sanitize data
$toHighlight = array('value', 'comment');
$keywordArray = array();
foreach ($toHighlight as $highlightedElement) {
if (!empty($filters[$highlightedElement])) {
if (!is_array($filters[$highlightedElement])) {
$filters[$highlightedElement] = array($filters[$highlightedElement]);
}
foreach ($filters[$highlightedElement] as $highlightedString) {
$keywordArray[] = $highlightedString;
}
}
}
// build the $replacePairs variable used to highlight the keywords
$replacePairs = $this->Highlight->build_replace_pairs($keywordArray);
}
foreach ($attributes as $k => $attribute) {
$event = array(
'Event' => $attribute['Event'],
'Orgc' => $attribute['Event']['Orgc'],
);
$mayModify = ($isSiteAdmin || ($isAclModify && $event['Event']['user_id'] == $me['id'] && $attribute['Event']['orgc_id'] == $me['org_id']) || ($isAclModifyOrg && $attribute['Event']['orgc_id'] == $me['org_id']));
$mayPublish = ($isAclPublish && $attribute['Event']['orgc_id'] == $me['org_id']);
$mayChangeCorrelation = !Configure::read('MISP.completely_disable_correlation') && ($isSiteAdmin || ($mayModify && Configure::read('MISP.allow_disabling_correlation')));
if (!empty($attribute['Attribute']['RelatedAttribute'])) {
$event['RelatedAttribute'] = array($attribute['Attribute']['id'] => $attribute['Attribute']['RelatedAttribute']);
}
$attribute['Attribute']['objectType'] = 'attribute';
echo $this->element('/Events/View/row_attribute', array(
'object' => $attribute['Attribute'],
'k' => $k,
'mayModify' => $mayModify,
'mayChangeCorrelation' => $mayChangeCorrelation,
'page' => 1,
'fieldCount' => 11,
'includeRelatedTags' => 0,
'event' => $event,
'me' => $me,
'extended' => 1,
'disable_multi_select' => 1,
'context' => 'list'
));
}
?>
</table>
<?php
// Generate form for adding sighting just once, generation for every attribute is surprisingly too slow
echo $this->Form->create('Sighting', ['id' => 'SightingForm', 'url' => $baseurl . '/sightings/add/', 'style' => 'display:none;']);
echo $this->Form->input('id', ['label' => false, 'type' => 'number']);
echo $this->Form->input('type', ['label' => false]);
echo $this->Form->end();
?>
<p>
<?php
echo $this->Paginator->counter(array(
'format' => __('Page {:page} of {:pages}, showing {:current} records out of {:count} total, starting on record {:start}, ending on {:end}')
));
?>
</p>
<div class="pagination">
<ul>
<?= $paginator ?>
</ul>
</div>
</div>
<?php
if ($isSearch == 1) {
$class = 'searchAttributes2';
} else {
$class = 'listAttributes';
}
$modules = isset($modules) ? $modules : null;
$cortex_modules = isset($cortex_modules) ? $cortex_modules : null;
echo '<div class="index">';
echo $this->element('/genericElements/IndexTable/index_table', [
'data' => [
'title' => __('Attributes'),
'data' => $attributes,
'fields' => [
[
'name' => __('Date'),
'sort' => 'Attribute.timestamp',
'element' => 'timestamp',
'time_format' => 'Y-m-d',
'data_path' => 'Attribute.timestamp'
],
[
'name' => __('Event'),
'sort' => 'Attribute.event_id',
'data_path' => 'Attribute.event_id'
],
[
'name' => __('Org'),
'sort' => 'Event.Orgc.name',
'data_path' => 'Event.Orgc',
'element' => 'org'
],
[
'name' => __('Category'),
'sort' => 'Attribute.category',
'data_path' => 'Attribute.category'
],
[
'name' => __('Type'),
'sort' => 'Attribute.type',
'data_path' => 'Attribute.type'
],
[
'name' => __('Value'),
'sort' => 'Attribute.value',
'data_path' => 'Attribute.value'
],
[
'name' => __('Tags'),
'element' => 'attributeTags',
'class' => 'short'
],
[
'name' => __('Galaxies'),
'element' => 'attributeGalaxies',
'class' => 'short'
],
[
'name' => __('Comment'),
'data_path' => 'Attribute.comment'
],
[
'name' => __('Correlate'),
'element' => 'correlate',
'data' => [
'object' => [
'value_path' => 'Attribute'
]
]
],
[
'name' => __('Related Events'),
'element' => 'relatedEvents',
'data' => [
'object' => [
'value_path' => 'Attribute'
],
'scope' => 'Attribute'
]
],
[
'name' => __('Feed hits'),
'element' => 'feedHits',
'data' => [
'object' => [
'value_path' => 'Attribute'
]
]
],
[
'name' => __('IDS'),
'element' => 'toIds',
'data' => [
'object' => [
'value_path' => 'Attribute'
]
],
],
[
'name' => __('Distribution'),
'element' => 'distribution_levels',
'data_path' => 'Attribute.distribution',
'distributionLevels' => $distributionLevels,
'data' => [
'object' => [
'value_path' => 'Attribute'
],
'scope' => 'Attribute'
],
'quickedit' => true
],
[
'name' => __('Sightings'),
'element' => 'sightings',
'data' => [
'object' => [
'value_path' => 'Attribute'
],
],
'sightings' => $sightingsData
],
[
'name' => __('Activity'),
'element' => 'sightingsActivity',
'data' => [
'object' => [
'value_path' => 'Attribute'
],
],
'sightings' => $sightingsData
],
],
'actions' => [
[
'url' => $baseurl . '/shadow_attributes/edit',
'url_params_data_paths' => [
'Attribute.id'
],
'icon' => 'comment',
'complex_requirement' => [
'function' => function ($object) use ($isSiteAdmin, $me) {
return $isSiteAdmin || ($object['Event']['orgc_id'] !== $me['org_id']);
}
]
],
[
'onclick' => "deleteObject('shadow_attributes', 'delete', '[onclick_params_data_path]');",
'onclick_params_data_path' => 'Attribute.id',
'icon' => 'trash',
'title' => __('Propose deletion'),
'complex_requirement' => [
'function' => function ($object) use ($isSiteAdmin, $me) {
return $isSiteAdmin || ($object['Event']['orgc_id'] !== $me['org_id']);
}
]
],
[
'title' => __('Propose enrichment'),
'icon' => 'asterisk',
'onclick' => 'simplePopup(\'' . $baseurl . '/events/queryEnrichment/[onclick_params_data_path]/ShadowAttribute\');',
'onclick_params_data_path' => 'Attribute.id',
'complex_requirement' => [
'function' => function ($object) use ($modules, $isSiteAdmin, $me) {
return (
($isSiteAdmin || ($object['Event']['orgc_id'] !== $me['org_id'])) &&
isset($cortex_modules) &&
isset($cortex_modules['types'][$object['type']])
);
},
'options' => [
'datapath' => [
'type' => 'Attribute.type'
]
],
],
],
[
'title' => __('Propose enrichment through Cortex'),
'icon' => 'eye',
'onclick' => 'simplePopup(\'' . $baseurl . '/events/queryEnrichment/[onclick_params_data_path]/ShadowAttribute/Cortex\');',
'onclick_params_data_path' => 'Attribute.id',
'complex_requirement' => [
'function' => function ($object) use ($cortex_modules, $isSiteAdmin, $me) {
return (
($isSiteAdmin || ($object['Event']['orgc_id'] !== $me['org_id'])) &&
isset($cortex_modules) &&
isset($cortex_modules['types'][$object['type']])
);
},
'options' => [
'datapath' => [
'type' => 'Attribute.type'
]
],
],
],
[
'icon' => 'grip-lines-vertical',
'requirement' => $isSiteAdmin
],
[
'title' => __('Add enrichment'),
'icon' => 'asterisk',
'onclick' => 'simplePopup(\'' . $baseurl . '/events/queryEnrichment/[onclick_params_data_path]/Attribute\');',
'onclick_params_data_path' => 'Attribute.id',
'complex_requirement' => [
'function' => function ($object) use ($modules, $isSiteAdmin, $me) {
return (
($isSiteAdmin || ($object['Event']['orgc_id'] === $me['org_id'])) &&
isset($cortex_modules) &&
isset($cortex_modules['types'][$object['type']])
);
},
'options' => [
'datapath' => [
'type' => 'Attribute.type'
]
],
],
],
[
'title' => __('Add enrichment via Cortex'),
'icon' => 'eye',
'onclick' => 'simplePopup(\'' . $baseurl . '/events/queryEnrichment/[onclick_params_data_path]/Attribute/Cortex\');',
'onclick_params_data_path' => 'Attribute.id',
'complex_requirement' => [
'function' => function ($object) use ($cortex_modules, $isSiteAdmin, $me) {
return (
($isSiteAdmin || ($object['Event']['orgc_id'] === $me['org_id'])) &&
isset($cortex_modules) &&
isset($cortex_modules['types'][$object['type']])
);
},
'options' => [
'datapath' => [
'type' => 'Attribute.type'
]
],
],
],
[
'url' => $baseurl . '/attributes/edit',
'url_params_data_paths' => [
'Attribute.id'
],
'icon' => 'edit',
'complex_requirement' => [
'function' => function ($object) use ($isSiteAdmin, $me) {
return $isSiteAdmin || ($object['Event']['orgc_id'] === $me['org_id']);
}
]
],
[
'onclick' => "deleteObject('attributes', 'delete', '[onclick_params_data_path]');",
'onclick_params_data_path' => 'Attribute.id',
'icon' => 'trash',
'title' => __('Soft delete attribute'),
'requirement' => $isSiteAdmin,
'complex_requirement' => [
'function' => function ($object) use ($isSiteAdmin, $me) {
return (
(
$isSiteAdmin ||
$object['Event']['orgc_id'] !== $me['org_id'])
) &&
!empty($object['Event']['publish_timestamp']
);
},
]
],
[
'onclick' => "deleteObject('attributes', 'delete', '[onclick_params_data_path]/true');",
'onclick_params_data_path' => 'Attribute.id',
'icon' => 'trash',
'title' => __('Permanently delete attribute'),
'requirement' => $isSiteAdmin,
'complex_requirement' => [
'function' => function ($object) use ($isSiteAdmin, $me) {
return (
(
$isSiteAdmin ||
$object['Event']['orgc_id'] !== $me['org_id'])
) &&
empty($object['Event']['publish_timestamp']
);
},
]
]
]
]
]);
echo '</div>';
// Generate form for adding sighting just once, generation for every attribute is surprisingly too slow
echo $this->Form->create('Sighting', ['id' => 'SightingForm', 'url' => $baseurl . '/sightings/add/', 'style' => 'display:none;']);
echo $this->Form->input('id', ['label' => false, 'type' => 'number']);
echo $this->Form->input('type', ['label' => false]);
echo $this->Form->end();
$class = $isSearch == 1 ? 'searchAttributes2' : 'listAttributes';
echo $this->element('/genericElements/SideMenu/side_menu', ['menuList' => 'event-collection', 'menuItem' => $class]);
?>
<?= $this->element('/genericElements/SideMenu/side_menu', array('menuList' => 'event-collection', 'menuItem' => $class)); ?>
<script type="text/javascript">
// tooltips
$(function () {
$("td, div").tooltip({
'placement': 'top',
'container' : 'body',
delay: { show: 500, hide: 100 }
});
$('.screenshot').click(function() {
screenshotPopup($(this).attr('src'), $(this).attr('title'));
});
$('.addGalaxy').click(function() {
addGalaxyListener(this);
});
$('.sightings_advanced_add').click(function() {
var selected = [];
var object_context = $(this).data('object-context');
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 + "/" + object_context;
genericPopup(url, '#popover_box');
});
$('.correlation-toggle').click(function() {
var attribute_id = $(this).data('attribute-id');
getPopup(attribute_id, 'attributes', 'toggleCorrelation', '', '#confirmation_box');
return false;
});
$('.toids-toggle').click(function() {
var attribute_id = $(this).data('attribute-id');
getPopup(attribute_id, 'attributes', 'toggleToIDS', '', '#confirmation_box');
return false;
});
popoverStartup();
$(document).on('click', function (e) {
//did not click a popover toggle or popover
if ($(e.target).data('toggle') !== 'popover'
&& $(e.target).parents('.popover.in').length === 0) {
// filter for only defined popover
var definedPopovers = $('[data-toggle="popover"]').filter(function(i, e) {
// tooltips
$(function() {
$("td, div").tooltip({
'placement': 'top',
'container': 'body',
delay: {
show: 500,
hide: 100
}
});
$('.screenshot').click(function() {
screenshotPopup($(this).attr('src'), $(this).attr('title'));
});
$('.addGalaxy').click(function() {
addGalaxyListener(this);
});
$('.sightings_advanced_add').click(function() {
var selected = [];
var object_context = $(this).data('object-context');
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 + "/" + object_context;
genericPopup(url, '#popover_box');
});
$('.correlation-toggle').click(function() {
var attribute_id = $(this).data('attribute-id');
getPopup(attribute_id, 'attributes', 'toggleCorrelation', '', '#confirmation_box');
return false;
});
$('.toids-toggle').click(function() {
var attribute_id = $(this).data('attribute-id');
getPopup(attribute_id, 'attributes', 'toggleToIDS', '', '#confirmation_box');
return false;
});
popoverStartup();
$(document).on('click', function(e) {
//did not click a popover toggle or popover
if ($(e.target).data('toggle') !== 'popover' &&
$(e.target).parents('.popover.in').length === 0) {
// filter for only defined popover
var definedPopovers = $('[data-toggle="popover"]').filter(function(i, e) {
return $(e).data('popover') !== undefined;
});
definedPopovers.popover('hide');
}
});
definedPopovers.popover('hide');
}
});
});
});
</script>

View File

@ -4,11 +4,11 @@ $linkColour = ($scope == 'Attribute') ? 'red' : 'white';
// remove duplicates
$relatedEvents = array();
foreach ($event['Related' . $scope][$object['id']] as $k => $relatedAttribute) {
if (isset($relatedEvents[$relatedAttribute['id']])) {
unset($event['Related' . $scope][$object['id']][$k]);
} else {
$relatedEvents[$relatedAttribute['id']] = true;
}
if (isset($relatedEvents[$relatedAttribute['id']])) {
unset($event['Related' . $scope][$object['id']][$k]);
} else {
$relatedEvents[$relatedAttribute['id']] = true;
}
}
$event['Related' . $scope][$object['id']] = array_values($event['Related' . $scope][$object['id']]);
$count = count($event['Related' . $scope][$object['id']]);
@ -33,7 +33,7 @@ foreach ($event['Related' . $scope][$object['id']] as $relatedAttribute) {
}
$link = $this->Html->link(
$relatedAttribute['id'],
array('controller' => 'events', 'action' => 'view', $relatedAttribute['id'], true, $event['Event']['id']),
array('controller' => 'events', 'action' => 'view', $relatedAttribute['id'], true, $event['id']),
array('class' => ($relatedAttribute['org_id'] == $me['org_id']) ? $linkColour : 'blue')
);
echo sprintf(

View File

@ -0,0 +1,13 @@
<?php
$attribute = Hash::extract($row, 'Attribute');
$event = Hash::extract($row, 'Event');
$mayModify = ($isSiteAdmin || ($isAclModify && $event['user_id'] == $me['id'] && $event['org_id'] == $me['org_id']) || ($isAclModifyOrg && $event['orgc_id'] == $me['org_id']));
echo $this->element('galaxyQuickViewNew', array(
'mayModify' => $mayModify,
'isAclTagger' => $isAclTagger,
'data' => (!empty($attribute['Galaxy']) ? $attribute['Galaxy'] : array()),
'event' => ['Event' => $event],
'target_id' => $attribute['id'],
'target_type' => 'attribute',
));

View File

@ -0,0 +1,36 @@
<?php
$attribute = Hash::extract($row, 'Attribute');
$event = Hash::extract($row, 'Event');
$mayModify = ($isSiteAdmin || ($isAclModify && $event['user_id'] == $me['id'] && $event['orgc_id'] == $me['org_id']) || ($isAclModifyOrg && $event['orgc_id'] == $me['org_id']));
$objectId = intval($attribute['id']);
?>
<div id="Attribute_<?= $objectId ?>" class="attributeTagContainer">
<?php echo $this->element(
'ajaxTags',
array(
'attributeId' => $attribute['id'],
'tags' => $attribute['AttributeTag'],
'tagAccess' => ($isSiteAdmin || $mayModify),
'localTagAccess' => ($isSiteAdmin || $mayModify || $me['org_id'] == $event['org_id'] || (int)$me['org_id'] === Configure::read('MISP.host_org_id')),
'context' => 'event',
'scope' => 'attribute',
'tagConflicts' => isset($attribute['tagConflicts']) ? $attribute['tagConflicts'] : array()
)
); ?>
</div>
<?php
if (!empty($includeRelatedTags)) {
$element = '';
if (!empty($attribute['RelatedTags'])) {
$element = $this->element('ajaxAttributeTags', array('attributeId' => $attribute['id'], 'attributeTags' => $attribute['RelatedTags'], 'tagAccess' => false));
}
echo sprintf(
'<td class="shortish"><div %s>%s</div></td>',
'class="attributeRelatedTagContainer" id="#Attribute_' . $objectId . 'Related_tr .attributeTagContainer"',
$element
);
}

View File

@ -0,0 +1,22 @@
<?php
$object = Hash::extract($row, $field['data']['object']['value_path']);
$event = Hash::extract($row, 'Event');
$mayModify = ($isSiteAdmin || ($isAclModify && $event['user_id'] == $me['id'] && $event['orgc_id'] == $me['org_id']) || ($isAclModifyOrg && $event['orgc_id'] == $me['org_id']));
$mayChangeCorrelation = !Configure::read('MISP.completely_disable_correlation') && ($isSiteAdmin || ($mayModify && Configure::read('MISP.allow_disabling_correlation')));
$objectId = h($object['id']);
?>
<input
id="correlation_toggle_<?= $objectId ?>"
class="correlation-toggle"
aria-label="<?php echo __('Toggle correlation');?>"
title="<?php echo __('Toggle correlation');?>"
type="checkbox"
data-attribute-id="<?= $objectId ?>"
<?php
echo $object['disable_correlation'] ? '' : ' checked';
echo ($mayChangeCorrelation && empty($event['disable_correlation'])) ? '' : ' disabled';
?>
/>

View File

@ -1,15 +1,37 @@
<?php
$distributionLevel = (Hash::extract($row, $field['data_path'])[0]);
echo sprintf(
'<span class="%s bold">%s</span>',
$distributionLevel == 0 ? 'red' : '',
$distributionLevel != 4 ? $distributionLevels[$distributionLevel] :
sprintf(
'<a href="%s/sharing_groups/view/%s">%s</a>',
$baseurl,
h($row['SharingGroup']['id']),
h($row['SharingGroup']['name'])
)
);
$quickedit = isset($field['quickedit']) && $field['quickedit'];
if ($quickedit) {
$object = Hash::extract($row, $field['data']['object']['value_path']);
$event = Hash::extract($row, 'Event');
$objectId = h($object['id']);
$scope = $field['data']['scope'];
}
?>
$distributionLevel = (Hash::extract($row, $field['data_path'])[0]);
echo sprintf('<div %s>', $quickedit ? sprintf(
" onmouseenter=\"quickEditHover(this, '%s', %s, 'distribution', %s);\"",
$scope,
$objectId,
$event['id']
) : '');
if ($quickedit) {
echo sprintf("<div id='%s_%s_distribution_placeholder' class='inline-field-placeholder'></div>", $scope, $objectId);
echo sprintf("<div id='%s_%s_distribution_solid' class='inline-field-solid'>", $scope, $objectId);
}
echo sprintf(
'<span class="%s bold">%s</span>',
$distributionLevel == 0 ? 'red' : '',
$distributionLevel != 4 ? $distributionLevels[$distributionLevel] :
sprintf(
'<a href="%s/sharing_groups/view/%s">%s</a>',
$baseurl,
h($row['SharingGroup']['id']),
h($row['SharingGroup']['name'])
)
);
if ($quickedit) {
echo '</div></div>';
}

View File

@ -0,0 +1,87 @@
<?php
$object = Hash::extract($row, $field['data']['object']['value_path']);
if (isset($object['Feed'])) {
foreach ($object['Feed'] as $feed) {
$relatedData = array(
__('Name') => h($feed['name']),
__('Provider') => h($feed['provider']),
);
if (isset($feed['event_uuids'])) {
$relatedData[__('Event UUIDs')] = implode('<br>', array_map('h', $feed['event_uuids']));
}
$popover = '';
foreach ($relatedData as $k => $v) {
$popover .= '<span class="bold black">' . $k . '</span>: <span class="blue">' . $v . '</span><br>';
}
if ($isSiteAdmin || $hostOrgUser) {
if ($feed['source_format'] === 'misp') {
$liContents = sprintf(
'<form action="%s/feeds/previewIndex/%s" method="post" style="margin:0;line-height:auto;">%s%s</form>',
$baseurl,
h($feed['id']),
sprintf(
'<input type="hidden" name="data[Feed][eventid]" value="%s">',
h(json_encode($feed['event_uuids']))
),
sprintf(
'<input type="submit" class="linkButton useCursorPointer" value="%s" data-toggle="popover" data-content="%s" data-trigger="hover" style="margin-right:3px;line-height:normal;vertical-align: text-top;">',
h($feed['id']),
h($popover)
)
);
} else {
$liContents = sprintf(
'<a href="%s/feeds/previewIndex/%s" data-toggle="popover" data-content="%s" data-trigger="hover">%s</a>',
$baseurl,
h($feed['id']),
h($popover),
h($feed['id'])
);
}
} else {
$liContents = sprintf(
'<span>%s</span>',
h($feed['id'])
);
}
echo "<li>$liContents</li>";
}
}
if (isset($object['Server'])) {
foreach ($object['Server'] as $server) {
$popover = '';
foreach ($server as $k => $v) {
if ($k == 'id') continue;
if (is_array($v)) {
foreach ($v as $k2 => $v2) {
$v[$k2] = h($v2);
}
$v = implode('<br />', $v);
} else {
$v = h($v);
}
$popover .= '<span class=\'bold black\'>' . Inflector::humanize(h($k)) . '</span>: <span class="blue">' . $v . '</span><br />';
}
foreach ($server['event_uuids'] as $k => $event_uuid) {
$liContents = '';
if ($isSiteAdmin) {
$liContents .= sprintf(
'<a href="%s/servers/previewEvent/%s/%s" data-toggle="popover" data-content="%s" data-trigger="hover">%s</a>&nbsp;',
$baseurl,
h($server['id']),
h($event_uuid),
h($popover),
'S' . h($server['id']) . ':' . ($k + 1)
);
} else {
$liContents .= sprintf(
'<span>%s</span>',
'S' . h($server['id']) . ':' . ($k + 1)
);
}
echo "<li>$liContents</li>";
}
}
}

View File

@ -0,0 +1,18 @@
<?php
$object = Hash::extract($row, $field['data']['object']['value_path']);
$event = Hash::extract($row, 'Event');
if (!empty($object['RelatedAttribute'])) {
$event['RelatedAttribute'] = array($object['id'] => $object['RelatedAttribute']);
}
if (!empty($event['RelatedAttribute'][$object['id']])) {
echo '<ul class="inline" style="margin:0">';
echo $this->element('Events/View/attribute_correlations', array(
'scope' => $field['data']['scope'],
'object' => $object,
'event' => $event,
));
echo '</ul>';
}

View File

@ -0,0 +1,40 @@
<?php
$object = Hash::extract($row, $field['data']['object']['value_path']);
$objectId = intval($object['id']);
$sightings = $field['sightings'];
$html = '';
if (isset($sightings['data'][$objectId])) {
$objectSighting = $sightings['data'][$objectId];
foreach ($objectSighting as $type => $typeData) {
$name = $type !== 'expiration' ? Inflector::pluralize($type) : $type;
$html .= '<span class="blue bold">' . ucfirst(h($name)) . '</span><br>';
foreach ($typeData['orgs'] as $org => $orgData) {
$extra = $org === $me['Organisation']['name'] ? ' class="bold"' : "";
if ($type == 'expiration') {
$html .= '<span' . $extra . '>' . h($org) . '</span>: <span class="orange bold">' . $this->Time->time($orgData['date']) . '</span><br>';
} else {
$html .= '<span' . $extra . '>' . h($org) . '</span>: <span class="' . ($type === 'sighting' ? 'green' : 'red') . ' bold">' . h($orgData['count']) . ' (' . $this->Time->time($orgData['date']) . ')</span><br>';
}
}
}
$s = isset($objectSighting['sighting']['count']) ? intval($objectSighting['sighting']['count']) : 0;
$f = isset($objectSighting['false-positive']['count']) ? intval($objectSighting['false-positive']['count']) : 0;
$e = isset($objectSighting['expiration']['count']) ? intval($objectSighting['expiration']['count']) : 0;
} else {
$s = $f = $e = 0;
}
if ($isAclSighting) :
?>
<i class="far fa-thumbs-up useCursorPointer" title="<?php echo __('Add sighting'); ?>" role="button" tabindex="0" aria-label="<?php echo __('Add sighting'); ?>" onmouseover="flexibleAddSighting(this, '0', '<?= $objectId ?>', '<?php echo h($object['event_id']); ?>', 'top');" onclick="addSighting('0', '<?= $objectId ?>', '<?php echo h($object['event_id']); ?>');">&nbsp;</i>
<i class="far fa-thumbs-down useCursorPointer" title="<?php echo __('Mark as false-positive'); ?>" role="button" tabindex="0" aria-label="<?php echo __('Mark as false-positive'); ?>" onmouseover="flexibleAddSighting(this, '1', '<?= $objectId ?>', '<?php echo h($object['event_id']); ?>', 'bottom');" onclick="addSighting('1', '<?= $objectId ?>', '<?php echo h($object['event_id']); ?>');">&nbsp;</i>
<i class="fas fa-wrench useCursorPointer sightings_advanced_add" title="<?php echo __('Advanced sightings'); ?>" role="button" tabindex="0" aria-label="<?php echo __('Advanced sightings'); ?>" data-object-id="<?= $objectId ?>" data-object-context="attribute">&nbsp;</i>
<?php
endif;
?>
<span id="sightingCount_<?php echo $objectId; ?>" class="bold" data-placement="top" data-toggle="popover" data-trigger="hover" data-content="<?= h($html) ?>">
(<span class="green"><?= $s ?></span>/<span class="red"><?= $f ?></span>/<span class="orange"><?= $e ?></span>)
</span>

View File

@ -0,0 +1,9 @@
<?php
$object = Hash::extract($row, $field['data']['object']['value_path']);
$objectId = intval($object['id']);
$sightings = $field['sightings'];
if (!empty($sightings['csv'][$objectId])) {
echo $this->element('sparkline', array('scope' => 'object', 'id' => $objectId, 'csv' => $sightings['csv'][$objectId]));
}

View File

@ -0,0 +1,13 @@
<?php
$object = Hash::extract($row, $field['data']['object']['value_path']);
$event = Hash::extract($row, 'Event');
$mayModify = ($isSiteAdmin || ($isAclModify && $event['user_id'] == $me['id'] && $event['orgc_id'] == $me['org_id']) || ($isAclModifyOrg && $event['orgc_id'] == $me['org_id']));
$objectId = h($object['id']);
?>
<div id="Attribute_<?= $objectId ?>_to_ids_placeholder" class="inline-field-placeholder"></div>
<div id="Attribute_<?= $objectId ?>_to_ids_solid" class="inline-field-solid">
<input type="checkbox" class="toids-toggle" id="toids_toggle_<?= $objectId ?>" data-attribute-id="<?= $objectId ?>" aria-label="<?= __('Toggle IDS flag') ?>" title="<?= __('Toggle IDS flag') ?>" <?= $object['to_ids'] ? ' checked' : ''; ?><?= $mayModify ? '' : ' disabled' ?>>
</div>

View File

@ -1,39 +1,42 @@
<div class="news form">
<?php
echo $this->Form->create('News');
?>
<fieldset>
<legend><?php echo __('Add News Item'); ?></legend>
<?php
echo $this->Form->input('title', array(
'type' => 'text',
'error' => array('escape' => false),
'div' => 'input clear',
'class' => 'input-xxlarge'
));
?>
<div class="input clear"></div>
<?php
echo $this->Form->input('message', array(
'type' => 'textarea',
'error' => array('escape' => false),
'div' => 'input clear',
'class' => 'input-xxlarge'
));
?>
<div class="input clear"></div>
<?php
echo $this->Form->input('anonymise', array(
'checked' => false,
'label' => __('Create anonymously'),
));
?>
</fieldset>
<?php
echo $this->Form->button(__('Submit'), array('class' => 'btn btn-primary'));
echo $this->Form->end();
?>
</div>
<?php
echo $this->element('/genericElements/SideMenu/side_menu', array('menuList' => 'news', 'menuItem' => 'add'));
?>
$edit = $this->request->params['action'] === 'edit' ? true : false;
echo $this->element(
'/genericElements/SideMenu/side_menu',
[
'menuList' => 'news',
'menuItem' => $edit ? 'edit' : 'add'
]
);
echo $this->element('genericElements/Form/genericForm', [
'data' => [
'title' => $edit ? __('Edit News Item') : __('Add News Item'),
'fields' => [
[
'field' => 'title',
'label' => __('Title'),
'type' => 'text',
'error' => ['escape' => false],
'div' => 'input clear',
'class' => 'input-xxlarge',
],
[
'field' => 'message',
'label' => __('Message'),
'type' => 'textarea',
'error' => ['escape' => false],
'div' => 'input clear',
'class' => 'input-xxlarge'
],
[
'field' => 'anonymise',
'label' => __('Create anonymously'),
'type' => 'checkbox',
],
],
'submit' => [
'action' => $this->request->params['action'],
'ajaxSubmit' => 'submitGenericFormInPlace();'
]
]
]);

View File

@ -1,40 +0,0 @@
<div class="news form">
<?php
echo $this->Form->create('News');
?>
<fieldset>
<legend><?php echo __('Edit News Item'); ?></legend>
<?php
echo $this->Form->input('title', array(
'type' => 'text',
'error' => array('escape' => false),
'div' => 'input clear',
'class' => 'input-xxlarge'
));
?>
<div class="input clear"></div>
<?php
echo $this->Form->input('message', array(
'type' => 'textarea',
'error' => array('escape' => false),
'div' => 'input clear',
'class' => 'input-xxlarge'
));
?>
<div class="input clear"></div>
<?php
echo $this->Form->input('anonymise', array(
'type' => 'checkbox',
'checked' => $newsItem['News']['user_id'] == 0,
'label' => __('Create anonymously'),
));
?>
</fieldset>
<?php
echo $this->Form->button(__('Submit'), array('class' => 'btn btn-primary'));
echo $this->Form->end();
?>
</div>
<?php
echo $this->element('/genericElements/SideMenu/side_menu', array('menuList' => 'news', 'menuItem' => 'edit'));
?>

View File

@ -1,54 +1,61 @@
<div class="templates view">
<h2><?php echo __('News');?></h2>
<div>
<?php
if (!empty($newsItems)):
foreach ($newsItems as $newsItem): ?>
<div class="templateTableRow" style="width:800px;">
<div class="templateElementHeader" style="width:100%;position:relative;<?php if ($newsItem['News']['new']) echo 'background-color:red;'?>">
<div class="templateGlass"></div>
<div class ="templateElementHeaderText" style="width:100%;">
<div style="float:left;width:83%;"><?php echo $newsItem['User']['email'] ? h($newsItem['User']['email']) : 'Administrator'; ?></div>
<div style="float:left;width:17%;"><?= $this->Time->time($newsItem['News']['date_created']); ?></div>
</div>
</div>
<div style="padding:6px;">
<h4><?php echo h($newsItem['News']['title']);?></h4>
<?php
$message = h($newsItem['News']['message']);
echo nl2br(preg_replace('#https?:\/\/[^\s]*#i', '<a href="$0">$0</a>', $message));
if ($isSiteAdmin):
?>
<br /><a href="<?php echo $baseurl; ?>/news/edit/<?php echo h($newsItem['News']['id']);?>" class="fa fa-edit" title="<?php echo __('Edit news message');?>" aria-label="<?php echo __('Edit');?>"></a>
<?php
echo $this->Form->postLink('', array('action' => 'delete', $newsItem['News']['id']), array('class' => 'fa fa-trash', 'title' => __('Delete'), 'aria-label' => __('Delete')), __('Are you sure you want to delete news item #%s?', $newsItem['News']['id']));
endif;
?>
</div>
</div>
<br />
<?php
endforeach;
echo $this->Paginator->counter(array(
'format' => __('Page {:page} of {:pages}, showing {:current} records out of {:count} total, starting on record {:start}, ending on {:end}')
));
?>
<div class="pagination">
<ul>
<?php
echo $this->Paginator->prev('&laquo; ' . __('previous'), array('tag' => 'li', 'escape' => false), null, array('tag' => 'li', 'class' => 'prev disabled', 'escape' => false, 'disabledTag' => 'span'));
echo $this->Paginator->numbers(array('modulus' => 20, 'separator' => '', 'tag' => 'li', 'currentClass' => 'active', 'currentTag' => 'span'));
echo $this->Paginator->next(__('next') . ' &raquo;', array('tag' => 'li', 'escape' => false), null, array('tag' => 'li', 'class' => 'next disabled', 'escape' => false, 'disabledTag' => 'span'));
?>
</ul>
</div>
<?php
else:
echo __('There are currently no news messages.');
endif;
?>
</div>
</div>
<?php
echo $this->element('/genericElements/SideMenu/side_menu', array('menuList' => 'news', 'menuItem' => 'index'));
?>
$this->set('menuData', ['menuList' => 'news', 'menuItem' => 'index']);
echo $this->element('genericElements/IndexTable/scaffold', [
'scaffold_data' => [
'data' => [
'data' => $newsItems,
'fields' => [
[
'name' => __('Id'),
'sort' => 'id',
'data_path' => 'News.id'
],
[
'name' => __('User'),
'sort' => 'email',
'data_path' => 'User.email'
],
[
'name' => __('Title'),
'sort' => 'title',
'data_path' => 'News.title'
],
[
'name' => __('Message'),
'sort' => 'message',
'data_path' => 'News.message'
],
[
'name' => __('Created at'),
'sort' => 'date_created',
'data_path' => 'News.date_created',
'element' => 'datetime'
],
],
'title' => empty($ajax) ? __('News') : false,
'pull' => 'right',
'actions' => [
[
'url' => $baseurl . '/news/edit',
'url_params_data_paths' => [
'News.id'
],
'icon' => 'edit',
'title' => 'Edit News',
],
[
'onclick' => sprintf(
'openGenericModal(\'%s/news/delete/[onclick_params_data_path]\');',
$baseurl
),
'onclick_params_data_path' => 'News.id',
'icon' => 'trash',
'title' => __('Delete news'),
]
]
]
]
]);

View File

@ -20,7 +20,7 @@
"uuid": "6201a65a-38fa-4b4a-bea7-cf1fb7bc1563",
"org_uuid": "55f6ea5e-2c60-40e5-964f-47a8950d210f",
"org_name": "CIRCL",
"description": "CIRCL operates an information sharing dedicated to national/governmental CSIRTs/CERTs with national responsibilities in the EEA (European Economic Area).",
"description": "CIRCL operates an information sharing community dedicated to national/governmental CSIRTs/CERTs with national responsibilities in the EEA (European Economic Area).",
"url": "https://www.circl.lu/misp/",
"sector": "n/g CSIRTs in EEA",
"nationality": "International",
@ -36,9 +36,9 @@
"uuid": "79e28013-747a-4430-b7ba-ed92d053b221",
"org_uuid": "55f6e5ae-2c60-40e5-964f-47a8950d210f",
"org_name": "CIRCL",
"description": "CIRCL operates an information sharing dedicated to the financial sector.",
"description": "CIRCL operates an information sharing community dedicated to the financial sector.",
"url": "https://www.circl.lu/misp/",
"sector": "Financial sector including banks, financial institution or payement processing organisations",
"sector": "Financial sector including banks, financial institutions and payment processing organisations",
"nationality": "International",
"type": "Vetted Information Sharing Community",
"email": "info@circl.lu",
@ -68,7 +68,7 @@
"uuid": "5e4656fe-8e34-441c-9c47-6545011fb688",
"org_uuid": "569b6c1f-bd1c-49c8-9244-0484bce2ab96",
"org_name": "eCrimeLabs",
"description": "The Danish MISP User group/Community operates as an informaiton sharing dedicated to Danish organisations",
"description": "The Danish MISP User group/Community operates an information sharing community dedicated to Danish organisations.",
"url": "https://dk.ecrimelabs.net",
"sector": "undefined",
"nationality": "Danish",

View File

@ -1135,7 +1135,7 @@ function loadAttributeTags(id) {
dataType:"html",
cache: false,
success:function (data) {
$("#Attribute_"+id+"_tr .attributeTagContainer").html(data);
$("#Attribute_"+id+".attributeTagContainer").html(data);
},
error: xhrFailCallback,
url: baseurl + "/tags/showAttributeTag/" + id

26
tests/build-test.sh Normal file
View File

@ -0,0 +1,26 @@
#!/usr/bin/env bash
set -e
set -o xtrace
# Check if dependencies for zmq are properly installed
python3 ./../app/files/scripts/mispzmq/mispzmqtest.py
# Check if all attachments handlers dependencies are correctly installed
python3 ./../app/files/scripts/generate_file_objects.py -c | python3 -c 'import sys, json; data = json.load(sys.stdin); print(data); sys.exit(0 if len([i for i in data.values() if i == True]) == 0 else 1)'
# Try to extract data from file
python3 ./../app/files/scripts/generate_file_objects.py -p /bin/ls | python3 -c 'import sys, json; data = json.load(sys.stdin); sys.exit(0 if "objects" in data else 1)'
# Test converting stix1 to MISP format
curl https://stixproject.github.io/documentation/idioms/c2-indicator/indicator-for-c2-ip-address.xml > ./../app/files/scripts/tmp/test-stix1.xml
python3 ./../app/files/scripts/stix2misp.py test-stix1.xml 1 1 ./../app/files/scripts/synonymsToTagNames.json | python3 -c 'import sys; data = sys.stdin.read().strip(); print(data); sys.exit(0 if data == "1" else 1)'
rm -f ./../app/files/scripts/tmp/{test-stix1.xml,test-stix1.xml.json}
# Test converting stix2 to MISP format
curl https://raw.githubusercontent.com/oasis-open/cti-stix2-json-schemas/master/examples/indicator-for-c2-ip-address.json > ./../app/files/scripts/tmp/test-stix2.json
python3 ./../app/files/scripts/stix2/stix2misp.py ./../tmp/test-stix2.json 1 1 ./../app/files/scripts/synonymsToTagNames.json | python3 -c 'import sys; data = sys.stdin.read().strip(); print(data); sys.exit(0 if data == "1" else 1)'
rm -f ./../app/files/scripts/tmp/{test-stix2.json,test-stix2.json.stix2}
# Test converting MISP to STIX2
python3 ./../app/files/scripts/stix2/misp2stix2.py -i event.json | python3 -c 'import sys, json; data = json.load(sys.stdin); print(data); sys.exit(0 if data["success"] == 1 else 1)'
rm -f event.json.out