Merge branch 'develop' of github.com:MISP/MISP into develop

pull/8580/head
iglocska 2022-09-09 14:45:37 +02:00
commit 8480b8ec8d
No known key found for this signature in database
GPG Key ID: BEA224F1FEF113AC
53 changed files with 2367 additions and 189 deletions

2
PyMISP

@ -1 +1 @@
Subproject commit 3ca8717e6c7780718cd20328040799261a39a281
Subproject commit b1896d43f20f13b9f29a432017f3dfb2c4faf082

View File

@ -578,6 +578,37 @@ class ServerShell extends AppShell
$this->Task->saveField('message', count($servers) . ' job(s) completed at ' . date('d/m/Y - H:i:s') . '.');
}
public function sendPeriodicSummaryToUsers()
{
$this->ConfigLoad->execute();
$periods = $this->__getPeriodsForToday();
$start_time = time();
echo __n('Started periodic summary generation for the %s period', 'Started periodic summary generation for periods: %s', count($periods), implode(', ', $periods)) . PHP_EOL;
foreach ($periods as $period) {
$users = $this->User->getSubscribedUsersForPeriod($period);
echo __n('%s user has subscribed for the `%s` period', '%s users has subscribed for the `%s` period', count($users), count($users), $period) . PHP_EOL;
foreach ($users as $user) {
echo __('Sending `%s` report to `%s`', $period, $user['User']['email']) . PHP_EOL;
$emailTemplate = $this->User->generatePeriodicSummary($user['User']['id'], $period, false);
$this->User->sendEmail($user, $emailTemplate, false, null);
}
}
echo __('All reports sent. Task took %s secondes', time() - $start_time) . PHP_EOL;
}
private function __getPeriodsForToday(): array
{
$today = new DateTime();
$periods = ['daily'];
if ($today->format('j') == 1) {
$periods[] = 'weekly';
}
if ($today->format('N') == 1) {
$periods[] = 'monthly';
}
return $periods;
}
/**
* @param int $userId
* @return array

View File

@ -36,7 +36,7 @@ class AppController extends Controller
public $helpers = array('OrgImg', 'FontAwesome', 'UserName');
private $__queryVersion = '144';
public $pyMispVersion = '2.4.160';
public $pyMispVersion = '2.4.162';
public $phpmin = '7.2';
public $phprec = '7.4';
public $phptoonew = '8.0';

View File

@ -729,6 +729,7 @@ class ACLComponent extends Component
'initiatePasswordReset' => ['AND' => ['perm_admin', 'password_change_enabled']],
'login' => array('*'),
'logout' => array('*'),
'notificationSettings' => ['*'],
'register' => array('*'),
'registrations' => array(),
'resetAllSyncAuthKeys' => array(),
@ -743,6 +744,7 @@ class ACLComponent extends Component
'verifyCertificate' => array(),
'verifyGPG' => array(),
'view' => array('*'),
'viewPeriodicSummary' => ['*'],
'getGpgPublicKey' => array('*'),
'unsubscribe' => ['*'],
),
@ -784,6 +786,7 @@ class ACLComponent extends Component
'executeWorkflow'=> [],
'debugToggleField'=> [],
'massToggleField'=> [],
'moduleStatelessExecution'=> [],
],
'workflowBlueprints' => [
'add' => [],

View File

@ -88,7 +88,7 @@ class RestResponseComponent extends Component
'restSearch' => array(
'description' => "Search MISP using a list of filter parameters and return the data in the selected format. The search is available on an event and an attribute level, just select the scope via the URL (/events/restSearch vs /attributes/restSearch). Besides the parameters listed, other, format specific ones can be passed along (for example: requested_attributes and includeContext for the CSV export). This API allows pagination via the page and limit parameters.",
'mandatory' => array('returnFormat'),
'optional' => array('page', 'limit', 'value', 'type', 'category', 'org', 'tag', 'tags', 'searchall', 'date', 'last', 'eventid', 'withAttachments', 'metadata', 'uuid', 'published', 'publish_timestamp', 'timestamp', 'enforceWarninglist', 'sgReferenceOnly', 'eventinfo', 'sharinggroup', 'excludeLocalTags', 'threat_level_id'),
'optional' => array('page', 'limit', 'value', 'type', 'category', 'org', 'tag', 'tags', 'event_tags', 'searchall', 'date', 'last', 'eventid', 'withAttachments', 'metadata', 'uuid', 'published', 'publish_timestamp', 'timestamp', 'enforceWarninglist', 'sgReferenceOnly', 'eventinfo', 'sharinggroup', 'excludeLocalTags', 'threat_level_id'),
'params' => array()
),
'addTag' => array(

View File

@ -74,6 +74,7 @@ class UsersController extends AppController
} else {
$this->set('user', $user);
$this->set('admin_view', false);
$this->set('periodic_notifications', $this->User::PERIODIC_NOTIFICATIONS);
}
}
@ -189,7 +190,7 @@ class UsersController extends AppController
}
if (!$abortPost) {
// What fields should be saved (allowed to be saved)
$fieldList = array('autoalert', 'gpgkey', 'certif_public', 'nids_sid', 'contactalert', 'disabled', 'date_modified');
$fieldList = array('autoalert', 'gpgkey', 'certif_public', 'nids_sid', 'contactalert', 'disabled', 'date_modified', 'notification_daily', 'notification_weekly', 'notification_monthly');
if ($this->__canChangeLogin()) {
$fieldList[] = 'email';
}
@ -469,6 +470,7 @@ class UsersController extends AppController
} else {
$this->set('urlparams', $urlParams);
$this->set('passedArgsArray', $passedArgsArray);
$this->set('periodic_notifications', $this->User::PERIODIC_NOTIFICATIONS);
$conditions = array();
if ($this->_isSiteAdmin()) {
$users = $this->paginate();
@ -609,6 +611,7 @@ class UsersController extends AppController
$user2 = $this->User->find('first', array('conditions' => array('User.id' => $user['User']['invited_by']), 'recursive' => -1));
$this->set('id', $id);
$this->set('user2', $user2);
$this->set('periodic_notifications', $this->User::PERIODIC_NOTIFICATIONS);
$this->set('admin_view', true);
$this->render('view');
}
@ -2761,6 +2764,59 @@ class UsersController extends AppController
}
}
public function notificationSettings()
{
$user_id = $this->Auth->user('id');
$user = $this->User->find('first', [
'recursive' => -1,
'conditions' => ['User.id' => $user_id],
'contain' => [
'UserSetting',
]
]);
if (empty($user)) {
throw new NotFoundException(__('Invalid user'));
}
if ($this->request->is('post') || $this->request->is('put')) {
$success = $this->User->saveNotificationSettings($user_id, $this->request->data);
if (!empty($success)) {
$message = __('Notification settings saved');
$this->Flash->success($message);
$this->redirect(['action' => 'view', 'me']);
} else {
$message = __('Notification settings could not be saved');
$this->Flash->error($message);
}
}
$user['periodic_settings'] = $this->User->extractPeriodicSettingForUser($user);
$this->request->data = $user;
$this->set('user', $user);
$this->loadModel('Attribute');
$distributionData = $this->Attribute->fetchDistributionData($this->Auth->user());
unset($distributionData['levels'][5]);
$this->set('sharingGroups', $distributionData['sgs']);
$this->set('distributionLevels', $distributionData['levels']);
$this->loadModel('Organisation');
$orgs = $this->Organisation->find('list', [
'conditions' => ['local' => 1],
'fields' => ['id', 'name'],
'order' => 'name',
]);
$this->set('orgs', $orgs);
$this->set('user', $user);
}
public function viewPeriodicSummary(string $period)
{
$summary = $this->User->generatePeriodicSummary($this->Auth->user('id'), $period);
$periodic_settings = $this->User->extractPeriodicSettingForUser($this->Auth->user('id'));
$notification_settings = $this->User->getUsablePeriodicSettingForUser($periodic_settings, $period);
$this->set('periodic_settings', $periodic_settings);
$this->set('summary', $summary);
$this->set('period', $period);
}
private function __canChangePassword()
{
return $this->ACL->canUserAccess($this->Auth->user(), 'users', 'change_pw');

View File

@ -13,6 +13,7 @@ class WorkflowsController extends AppController
{
parent::beforeFilter();
$this->Security->unlockedActions[] = 'checkGraph';
$this->Security->unlockedActions[] = 'moduleStatelessExecution';
$requirementErrors = [];
if (empty(Configure::read('MISP.background_jobs'))) {
$requirementErrors[] = __('Background workers must be enabled to use workflows');
@ -425,4 +426,13 @@ class WorkflowsController extends AppController
];
return $this->RestResponse->viewData($data, 'json');
}
public function moduleStatelessExecution($module_id)
{
$this->request->allowMethod(['post']);
$input_data = JsonTool::decode($this->request->data['input_data']);
$param_data = $this->request->data['module_indexed_param'];
$result = $this->Workflow->moduleStatelessExecution($module_id, $input_data, $param_data);
return $this->RestResponse->viewData($result, 'json');
}
}

View File

@ -22,6 +22,8 @@ class ContextExport
private $__taxonomyFetched = [];
private $__galaxyFetched = [];
private $__passedOptions = [];
public $non_restrictive_export = true;
public $renderView = 'context_view';
@ -33,7 +35,6 @@ class ContextExport
$this->__aggregate($attribute, Hash::extract($attribute, 'AttributeTag.{n}.Tag'));
}
}
$this->__attack_export_tool->handler($data, $options);
return '';
}
@ -45,6 +46,7 @@ class ContextExport
App::uses('AttackExport', 'Export');
$this->__attack_export_tool = new AttackExport();
$this->__attack_export_tool->handler($options);
$this->__passedOptions = $options;
return '';
}
@ -55,6 +57,9 @@ class ContextExport
$this->__aggregateTagsPerTaxonomy();
$this->__aggregateClustersPerGalaxy();
$attackData = json_decode($attackFinal, true);
if (!empty($this->__passedOptions['filters']['staticHtml'])) {
$attackData['static'] = true;
}
return json_encode([
'attackData' => $attackData,
'tags' => $this->__aggregatedTags,

View File

@ -0,0 +1,133 @@
<?php
class TrendingTool
{
private $eventModel;
public function __construct($eventModel)
{
$this->eventModel = $eventModel;
}
public function getTrendsForTags(array $events, int $baseDayRange, int $rollingWindows=3, $tagFilterPrefixes=null): array
{
$clusteredTags = $this->__clusterTagsForRollingWindow($events, $baseDayRange, $rollingWindows, $tagFilterPrefixes);
$trendAnalysis = $this->__computeTrendAnalysis($clusteredTags);
return [
'clustered_tags' => $clusteredTags,
'trend_analysis' => $trendAnalysis,
];
}
private function __computeTrendAnalysis(array $clusteredTags): array
{
$tagsPerRollingWindow = $clusteredTags['tagsPerRollingWindow'];
$eventNumberPerRollingWindow = $clusteredTags['eventNumberPerRollingWindow'];
$trendAnalysis = [];
$allTimestamps = array_keys($tagsPerRollingWindow);
$allTags = [];
foreach ($allTimestamps as $i => $timestamp) {
$trendAnalysis[$timestamp] = [];
$tags = $tagsPerRollingWindow[$timestamp];
$nextTimestamp = isset($allTimestamps[$i + 1]) ? $allTimestamps[$i + 1] : false;
$previousTimestamp = isset($allTimestamps[$i - 1]) ? $allTimestamps[$i - 1] : false;
foreach ($tags as $tag => $amount) {
$rawChange = 0;
$percentChange = 0;
if (!empty($nextTimestamp)) {
$nextAmount = !empty($tagsPerRollingWindow[$nextTimestamp][$tag]) ? $tagsPerRollingWindow[$nextTimestamp][$tag] : 0;
$rawChange = $amount - $nextAmount;
$percentChange = 100 * $rawChange / $amount;
}
$allTags[$tag] = true;
$trendAnalysis[$timestamp][$tag] = [
'occurence' => round($amount / $eventNumberPerRollingWindow[$timestamp], 2),
'raw_change' => $rawChange,
'percent_change' => $percentChange,
'change_sign' => $rawChange > 0 ? 1 : ($rawChange < 0 ? -1 : 0),
];
}
if (!empty($previousTimestamp)) {
foreach (array_keys($trendAnalysis[$timestamp]) as $tag) {
if (empty($trendAnalysis[$previousTimestamp][$tag])) {
$trendAnalysis[$previousTimestamp][$tag] = [
'occurence' => 0,
'raw_change' => -$amount,
'percent_change' => 100 * (-$amount / $amount),
'change_sign' => -$amount > 0 ? 1 : (-$amount < 0 ? -1 : 0),
];
}
}
}
}
return $trendAnalysis;
}
private function __clusterTagsForRollingWindow(array $events, int $baseDayRange, int $rollingWindows = 3, $tagFilterPrefixes = null): array
{
$fullDayNumber = $baseDayRange + $baseDayRange * $rollingWindows;
$tagsPerRollingWindow = [];
$eventNumberPerRollingWindow = [];
$timestampRollingWindow = [];
for ($i = 0; $i <= $fullDayNumber; $i += $baseDayRange) {
$timestamp = $this->eventModel->resolveTimeDelta($i . 'd');
$timestampRollingWindow[] = $timestamp;
$tagsPerRollingWindow[$timestamp] = [];
}
$tagsPerRollingWindow = array_map(function () {
return [];
}, array_flip(array_slice($timestampRollingWindow, 1)));
$eventNumberPerRollingWindow = array_map(function () {
return 0;
}, array_flip(array_slice($timestampRollingWindow, 1)));
$allTagsPerPrefix = [];
foreach ($events as $event) {
$allTags = $this->eventModel->extractAllTagNames($event);
$rollingTimestamps = $this->__getTimestampFromRollingWindow($event['Event']['timestamp'], $timestampRollingWindow);
$filteredTags = array_filter($allTags, function ($tag) use ($tagFilterPrefixes, &$allTagsPerPrefix) {
if (is_null($tagFilterPrefixes)) {
return true;
} else {
foreach ($tagFilterPrefixes as $tagPrefix) {
if (substr($tag, 0, strlen($tagPrefix)) === $tagPrefix) {
$allTagsPerPrefix[$tagPrefix][$tag] = true;
return true;
}
}
return false;
}
});
foreach ($filteredTags as $tag) {
if (empty($tagsPerRollingWindow[$rollingTimestamps['current']][$tag])) {
$tagsPerRollingWindow[$rollingTimestamps['current']][$tag] = 0;
}
$tagsPerRollingWindow[$rollingTimestamps['current']][$tag] += 1;
}
$eventNumberPerRollingWindow[$rollingTimestamps['current']] += 1;
}
return [
'tagsPerRollingWindow' => $tagsPerRollingWindow,
'eventNumberPerRollingWindow' => $eventNumberPerRollingWindow,
'allTagsPerPrefix' => array_map(function ($clusteredTags) {
return array_keys($clusteredTags);
}, $allTagsPerPrefix),
];
}
private function __getTimestampFromRollingWindow(int $eventTimestamp, array $rollingWindow): array
{
$i = 0;
if (count($rollingWindow) > 2) {
for ($i = 0; $i < count($rollingWindow) - 1; $i++) {
if ($eventTimestamp >= $rollingWindow[$i]) {
break;
}
}
}
return [
'previous' => isset($rollingWindow[$i - 1]) ? $rollingWindow[$i - 1] : null,
'current' => $rollingWindow[$i],
'next' => isset($rollingWindow[$i + 1]) ? $rollingWindow[$i + 1] : null,
];
}
}

View File

@ -83,7 +83,7 @@ class AppModel extends Model
75 => false, 76 => true, 77 => false, 78 => false, 79 => false, 80 => false,
81 => false, 82 => false, 83 => false, 84 => false, 85 => false, 86 => false,
87 => false, 88 => false, 89 => false, 90 => false, 91 => false, 92 => false,
93 => false, 94 => false, 95 => false, 96 => false,
93 => false, 94 => false, 95 => true, 96 => false, 97 => true,
);
const ADVANCED_UPDATES_DESCRIPTION = array(
@ -1866,6 +1866,13 @@ class AppModel extends Model
case 95:
$sqlArray[] = "ALTER TABLE `servers` ADD `remove_missing_tags` tinyint(1) NOT NULL DEFAULT 0 AFTER `skip_proxy`;";
break;
case 97:
$sqlArray[] = "ALTER TABLE `users`
ADD COLUMN `notification_daily` tinyint(1) NOT NULL DEFAULT 0,
ADD COLUMN `notification_weekly` tinyint(1) NOT NULL DEFAULT 0,
ADD COLUMN `notification_monthly` tinyint(1) NOT NULL DEFAULT 0
;";
break;
case 'fixNonEmptySharingGroupID':
$sqlArray[] = 'UPDATE `events` SET `sharing_group_id` = 0 WHERE `distribution` != 4;';
$sqlArray[] = 'UPDATE `attributes` SET `sharing_group_id` = 0 WHERE `distribution` != 4;';

View File

@ -1177,14 +1177,15 @@ class Attribute extends AppModel
public function set_filter_tags(&$params, $conditions, $options)
{
if (empty($params['tags'])) {
if (empty($params['tags']) && empty($params['event_tags'])) {
return $conditions;
}
/** @var Tag $tag */
$tag = ClassRegistry::init('Tag');
$params['tags'] = $this->dissectArgs($params['tags']);
$tag_key = !empty($params['tags']) ? 'tags' : 'event_tags';
$params[$tag_key] = $this->dissectArgs($params[$tag_key]);
foreach (array(0, 1, 2) as $tag_operator) {
$tagArray[$tag_operator] = $tag->fetchTagIdsSimple($params['tags'][$tag_operator]);
$tagArray[$tag_operator] = $tag->fetchTagIdsSimple($params[$tag_key][$tag_operator]);
}
$temp = array();
if (!empty($tagArray[0])) {
@ -1204,19 +1205,21 @@ class Attribute extends AppModel
$temp,
$this->subQueryGenerator($tag->EventTag, $subquery_options, $lookup_field)
);
$subquery_options = array(
'conditions' => array(
'tag_id' => $tagArray[0]
),
'fields' => array(
$options['scope'] === 'Event' ? 'Event.id' : 'attribute_id'
)
);
$lookup_field = $options['scope'] === 'Event' ? 'Event.id' : 'Attribute.id';
$temp = array_merge(
$temp,
$this->subQueryGenerator($tag->AttributeTag, $subquery_options, $lookup_field)
);
if ($tag_key != 'event_tags') {
$subquery_options = array(
'conditions' => array(
'tag_id' => $tagArray[0]
),
'fields' => array(
$options['scope'] === 'Event' ? 'Event.id' : 'attribute_id'
)
);
$lookup_field = $options['scope'] === 'Event' ? 'Event.id' : 'Attribute.id';
$temp = array_merge(
$temp,
$this->subQueryGenerator($tag->AttributeTag, $subquery_options, $lookup_field)
);
}
}
}
if (!empty($temp)) {
@ -1269,37 +1272,39 @@ class Attribute extends AppModel
$temp[$k]['OR'],
$this->subQueryGenerator($tag->EventTag, $subquery_options, $lookup_field)
);
$subquery_options = array(
'conditions' => array(
'tag_id' => $anded_tag
),
'fields' => array(
$options['scope'] === 'Event' ? 'Event.id' : 'attribute_id'
)
);
$lookup_field = $options['scope'] === 'Event' ? 'Event.id' : 'Attribute.id';
$temp[$k]['OR'] = array_merge(
$temp[$k]['OR'],
$this->subQueryGenerator($tag->AttributeTag, $subquery_options, $lookup_field)
);
if ($tag_key != 'event_tags') {
$subquery_options = array(
'conditions' => array(
'tag_id' => $anded_tag
),
'fields' => array(
$options['scope'] === 'Event' ? 'Event.id' : 'attribute_id'
)
);
$lookup_field = $options['scope'] === 'Event' ? 'Event.id' : 'Attribute.id';
$temp[$k]['OR'] = array_merge(
$temp[$k]['OR'],
$this->subQueryGenerator($tag->AttributeTag, $subquery_options, $lookup_field)
);
}
}
}
}
if (!empty($temp)) {
$conditions['AND'][] = array('AND' => $temp);
}
$params['tags'] = array();
$params[$tag_key] = array();
if (!empty($tagArray[0]) && empty($options['pop'])) {
$params['tags']['OR'] = $tagArray[0];
$params[$tag_key]['OR'] = $tagArray[0];
}
if (!empty($tagArray[1])) {
$params['tags']['NOT'] = $tagArray[1];
$params[$tag_key]['NOT'] = $tagArray[1];
}
if (!empty($tagArray[2]) && empty($options['pop'])) {
$params['tags']['AND'] = $tagArray[2];
$params[$tag_key]['AND'] = $tagArray[2];
}
if (empty($params['tags'])) {
unset($params['tags']);
if (empty($params[$tag_key])) {
unset($params[$tag_key]);
}
return $conditions;
}

View File

@ -638,6 +638,36 @@ class DecayingModel extends AppModel
return $attribute;
}
public function attachBaseScoresToEvent($user, $event, $model_id=false, $model_overrides=array(), $include_full_model=0)
{
$models = [];
if ($model_id === false) { // fetch all allowed and associated models
$models = $this->fetchAllAllowedModels($user, false, [], ['DecayingModel.enabled' => true]);
} else {
$models = $this->fetchModels($user, $model_id, false, array());
}
foreach ($models as $model) {
if (!empty($model_overrides)) {
$model = $this->overrideModelParameters($model, $model_overrides);
}
$basescore = $this->getBaseScoreForEvent($event, $model, $user);
$decayed = $this->isBaseScoreDecayed($model, $basescore);
$to_attach = [
'base_score' => $basescore,
'decayed' => $decayed,
'DecayingModel' => [
'id' => $model['DecayingModel']['id'],
'name' => $model['DecayingModel']['name']
]
];
if ($include_full_model) {
$to_attach['DecayingModel'] = $model['DecayingModel'];
}
$event['event_base_score'][] = $to_attach;
}
return $event;
}
public function getScore($attribute, $model, $user=false)
{
if (is_numeric($attribute) && $user !== false) {
@ -657,6 +687,18 @@ class DecayingModel extends AppModel
return $this->Computation->computeCurrentScore($user, $model, $attribute);
}
public function getBaseScoreForEvent(array $event, array $model): float
{
$this->Computation = $this->getModelClass($model);
return $this->Computation->computeBasescore($model, $event)['base_score'];
}
public function isBaseScoreDecayed(array $model, float $basescore): bool
{
$threshold = $model['DecayingModel']['parameters']['threshold'];
return $threshold > $basescore;
}
public function isDecayed($attribute, $model, $score=false, $user=false)
{
if ($score === false) {

View File

@ -1370,6 +1370,7 @@ class Event extends AppModel
'eventinfo' => array('function' => 'set_filter_eventinfo'),
'ignore' => array('function' => 'set_filter_ignore'),
'tags' => array('function' => 'set_filter_tags'),
'event_tags' => array('function' => 'set_filter_tags', 'pop' => true),
'from' => array('function' => 'set_filter_timestamp', 'pop' => true),
'to' => array('function' => 'set_filter_timestamp', 'pop' => true),
'date' => array('function' => 'set_filter_date', 'pop' => true),
@ -1620,6 +1621,7 @@ class Event extends AppModel
'includeRelatedTags',
'excludeLocalTags',
'includeDecayScore',
'includeBaseScoresOnEvent',
'includeSightingdb',
'includeFeedCorrelations',
'includeServerCorrelations',
@ -1629,7 +1631,8 @@ class Event extends AppModel
'limit',
'page',
'order',
'protected'
'protected',
'published',
);
if (!isset($options['excludeLocalTags']) && !empty($user['Role']['perm_sync']) && empty($user['Role']['perm_site_admin'])) {
$options['excludeLocalTags'] = 1;
@ -1766,6 +1769,9 @@ class Event extends AppModel
if ($options['protected']) {
$conditions['AND'][] = array('Event.protected' => $options['protected']);
}
if ($options['published']) {
$conditions['AND'][] = array('Event.published' => $options['published']);
}
if (!empty($options['includeRelatedTags'])) {
$options['includeGranularCorrelations'] = 1;
}
@ -1924,7 +1930,7 @@ class Event extends AppModel
$sharingGroupData = $sharingGroupReferenceOnly ? [] : $this->__cacheSharingGroupData($user, $useCache);
// Initialize classes that will be necessary during event fetching
if (!empty($options['includeDecayScore']) && !isset($this->DecayingModel)) {
if ((!empty($options['includeDecayScore']) || !empty($options['includeBaseScoresOnEvent'])) && !isset($this->DecayingModel)) {
$this->DecayingModel = ClassRegistry::init('DecayingModel');
}
if ($options['includeServerCorrelations'] && !$isSiteAdmin && $user['org_id'] != Configure::read('MISP.host_org_id')) {
@ -2021,6 +2027,9 @@ class Event extends AppModel
}
//$event['RelatedShadowAttribute'] = $this->getRelatedAttributes($user, $event['Event']['id'], true);
}
if (!empty($options['includeBaseScoresOnEvent'])) {
$event = $this->DecayingModel->attachBaseScoresToEvent($user, $event);
}
$shadowAttributeByOldId = [];
if (!empty($event['ShadowAttribute'])) {
if ($isSiteAdmin && $options['includeFeedCorrelations']) {
@ -2689,7 +2698,7 @@ class Event extends AppModel
public function set_filter_tags(&$params, $conditions, $options)
{
if (!empty($params['tags'])) {
if (!empty($params['tags']) || !empty($params['event_tags'])) {
$conditions = $this->Attribute->set_filter_tags($params, $conditions, $options);
}
return $conditions;
@ -7623,4 +7632,39 @@ class Event extends AppModel
$kafkaPubTool->publishJson($kafkaTopic, $fullEvent[0], 'publish');
}
}
public function getTrendsForTags(array $user, array $eventFilters=[], int $baseDayRange, int $rollingWindows=3, $tagFilterPrefixes=null): array
{
$fullDayNumber = $baseDayRange + $baseDayRange * $rollingWindows;
$fullRange = $this->resolveTimeDelta($fullDayNumber . 'd');
$eventFilters['last'] = $fullRange . 'd';
$eventFilters['order'] = 'timestamp DESC';
$events = $this->fetchEvent($user, $eventFilters);
App::uses('TrendingTool', 'Tools');
$trendingTool = new TrendingTool($this);
$trendAnalysis = $trendingTool->getTrendsForTags($events, $baseDayRange, $rollingWindows, $tagFilterPrefixes);
$clusteredTags = $trendAnalysis['clustered_tags'];
$trendAnalysis = $trendAnalysis['trend_analysis'];
return [
'clustered_tags' => $trendAnalysis,
'clustered_events' => $clusteredTags['eventNumberPerRollingWindow'],
'all_tags' => $clusteredTags['allTagsPerPrefix'],
'all_timestamps' => array_keys($clusteredTags['eventNumberPerRollingWindow']),
];
}
public function getTrendsForTagsFromEvents(array $events, int $baseDayRange, int $rollingWindows=3, $tagFilterPrefixes=null): array
{
App::uses('TrendingTool', 'Tools');
$trendingTool = new TrendingTool($this);
$trendAnalysis = $trendingTool->getTrendsForTags($events, $baseDayRange, $rollingWindows, $tagFilterPrefixes);
$clusteredTags = $trendAnalysis['clustered_tags'];
$trendAnalysis = $trendAnalysis['trend_analysis'];
return [
'clustered_tags' => $trendAnalysis,
'clustered_events' => $clusteredTags['eventNumberPerRollingWindow'],
'all_tags' => $clusteredTags['allTagsPerPrefix'],
'all_timestamps' => array_keys($clusteredTags['eventNumberPerRollingWindow']),
];
}
}

View File

@ -1004,4 +1004,16 @@ class SharingGroup extends AppModel
}
return $sg[0];
}
/**
* fetchAllSharingGroup collect all saved sharing group ignore ACL checks
*
* @return array
*/
public function fetchAllSharingGroup(): array
{
return $this->find('all', [
'recursive' => -1,
]);
}
}

View File

@ -18,6 +18,9 @@ App::uses('BlowfishConstantPasswordHasher', 'Controller/Component/Auth');
*/
class User extends AppModel
{
private const PERIODIC_USER_SETTING_KEY = 'periodic_notification_filters';
public const PERIODIC_NOTIFICATIONS = ['notification_daily', 'notification_weekly', 'notification_monthly'];
public $displayField = 'email';
public $validate = array(
@ -1645,4 +1648,244 @@ class User extends AppModel
$salt = Configure::read('Security.salt');
return substr(hash('sha256', "{$user['id']}|$salt"), 0, 8);
}
public function extractPeriodicSettingForUser($user, $decode=false): array
{
$filter_names = ['orgc_id', 'distribution', 'sharing_group_id', 'event_info', 'tags', 'trending_for_tags'];
$filter_to_decode = ['tags', 'trending_for_tags', ];
if (is_numeric($user)) {
$user = $this->find('first', [
'recursive' => -1,
'conditions' => ['User.id' => $user],
'contain' => [
'UserSetting',
]
]);
}
$periodic_settings = array_values(array_filter($user['UserSetting'], function ($userSetting) {
return $userSetting['setting'] == self::PERIODIC_USER_SETTING_KEY;
}));
$periodic_settings_indexed = [];
if (!empty($periodic_settings)) {
foreach ($filter_names as $filter_name) {
$periodic_settings_indexed[$filter_name] = $periodic_settings[0]['value'][$filter_name];
}
}
foreach ($filter_to_decode as $filter) {
if (!empty($decode) && !empty($periodic_settings_indexed[$filter])) {
$periodic_settings_indexed[$filter] = JsonTool::decode($periodic_settings_indexed[$filter]);
}
}
return $periodic_settings_indexed;
}
public function getUsablePeriodicSettingForUser(array $periodicSettings, $period='daily'): array
{
return $this->__getUsableFilters($periodicSettings, $period);
}
public function saveNotificationSettings(int $user_id, array $data): bool
{
$existingUser = $this->find('first', [
'recursive' => -1,
'conditions' => ['User.id' => $user_id],
]);
if (empty($existingUser)) {
return false;
}
foreach (self::PERIODIC_NOTIFICATIONS as $notification_period) {
$existingUser['User'][$notification_period] = $data['User'][$notification_period];
}
$success = $this->save($existingUser, [
'fieldList' => self::PERIODIC_NOTIFICATIONS
]);
if ($success) {
$periodic_settings = $data['periodic_settings'];
$param_to_decode = ['tags', 'trending_for_tags', ];
foreach ($param_to_decode as $param) {
if (empty($periodic_settings[$param])) {
$periodic_settings[$param] = '[]';
} else {
$decodedTags = json_decode($periodic_settings[$param], true);
if ($decodedTags === null) {
return false;
}
}
}
$notification_filters = [
'orgc_id' => $periodic_settings['orgc_id'] ?? [],
'distribution' => $periodic_settings['distribution'] ?? '',
'sharing_group_id' => $periodic_settings['distribution'] != 4 ? '' : ($periodic_settings['sharing_group_id'] ?? []),
'event_info' => $periodic_settings['event_info'] ?? '',
'tags' => $periodic_settings['tags'] ?? '[]',
'trending_for_tags' => $periodic_settings['trending_for_tags'] ?? '[]',
];
$new_user_setting = [
'UserSetting' => [
'user_id' => $existingUser['User']['id'],
'setting' => self::PERIODIC_USER_SETTING_KEY,
'value' => $notification_filters
]
];
$success = $this->UserSetting->setSetting($existingUser['User'], $new_user_setting);
}
return !empty($success);
}
public function getSubscribedUsersForPeriod(string $period): array
{
return $this->find('all', [
'recursive' => -1,
'conditions' => ["notification_$period" => true],
]);
}
/**
* generatePeriodicSummary
*
* @param int $user_id
* @param string $period
* @return string|SendEmailTemplate
* @throws NotFoundException
* @throws InvalidArgumentException
*/
public function generatePeriodicSummary(int $user_id, string $period, $rendered=true)
{
$existingUser = $this->getUserById($user_id);
$user = $this->rearrangeToAuthForm($existingUser);
$allowed_periods = array_map(function($period) {
return substr($period, strlen('notification_'));
}, self::PERIODIC_NOTIFICATIONS);
if (!in_array($period, $allowed_periods)) {
throw new InvalidArgumentException(__('Invalid period. Must be one of %s', JsonTool::encode(self::PERIODIC_NOTIFICATIONS)));
}
App::import('Tools', 'SendEmail');
$emailTemplate = $this->prepareEmailTemplate($period);
$periodicSettings = $this->extractPeriodicSettingForUser($user, true);
$filters = $this->getUsablePeriodicSettingForUser($periodicSettings, $period);
$filtersForRestSearch = $filters; // filters for restSearch are slightly different than fetchEvent
$filters['last'] = $this->resolveTimeDelta($filters['last']);
$filters['sgReferenceOnly'] = true;
$filters['includeEventCorrelations'] = false;
$filters['noSightings'] = true;
$filters['includeGalaxy'] = false;
$events = $this->__getEventsForFilters($user, $filters);
$elementCounter = 0;
$renderView = false;
$filtersForRestSearch['publish_timestamp'] = $filtersForRestSearch['last'];
$filtersForRestSearch['returnFormat'] = 'context';
$filtersForRestSearch['staticHtml'] = true;
unset($filtersForRestSearch['last']);
if (!empty($filtersForRestSearch['tags'])) {
$filtersForRestSearch['event_tags'] = $filtersForRestSearch['tags'];
unset($filtersForRestSearch['tags']);
}
$finalContext = $this->Event->restSearch($user, 'context', $filtersForRestSearch, false, false, $elementCounter, $renderView);
$finalContext = json_decode($finalContext->intoString(), true);
$aggregated_context = $this->__renderAggregatedContext($finalContext);
$rollingWindows = 2;
$trendAnalysis = $this->Event->getTrendsForTagsFromEvents($events, $this->__periodToDays($period), $rollingWindows, $periodicSettings['trending_for_tags']);
$trendData = [
'trendAnalysis' => $trendAnalysis,
'tagFilterPrefixes' => $periodicSettings['trending_for_tags'],
];
$trending_summary = $this->__renderTrendingSummary($trendData);
$emailTemplate->set('baseurl', $this->Event->__getAnnounceBaseurl());
$emailTemplate->set('events', $events);
$emailTemplate->set('filters', $filters);
$emailTemplate->set('period', $period);
$emailTemplate->set('aggregated_context', $aggregated_context);
$emailTemplate->set('trending_summary', $trending_summary);
$emailTemplate->set('analysisLevels', $this->Event->analysisLevels);
$emailTemplate->set('distributionLevels', $this->Event->distributionLevels);
if (!empty($rendered)) {
$summary = $emailTemplate->render();
return $summary->format() == 'text' ? $summary->text : $summary->html;
}
return $emailTemplate;
}
private function __renderAggregatedContext(array $restSearchOutput): string
{
return $this->__renderGeneric('Events' . DS . 'module_views', 'context_view', $restSearchOutput);
}
private function __renderTrendingSummary(array $trendData): string
{
return $this->__renderGeneric('Elements' . DS . 'Events', 'trendingSummary', $trendData);
}
private function __renderGeneric(string $viewPath, string $viewFile, array $viewVars): string
{
$view = new View();
$view->autoLayout = false;
$view->helpers = ['TextColour'];
$view->loadHelpers();
$view->set($viewVars);
$view->set('baseurl', $this->Event->__getAnnounceBaseurl());
$view->viewPath = $viewPath;
return $view->render($viewFile, false);
}
private function __getUsableFilters(array $period_filters, string $period='daily'): array
{
$filters = [
'last' => $this->__genTimerangeFilter($period),
'published' => true,
'includeBaseScoresOnEvent' => true,
];
if (!empty($period_filters['orgc_id'])) {
$filters['orgc_id'] = $period_filters['orgc_id'];
}
if (isset($period_filters['distribution']) && $period_filters['distribution'] >= 0) {
$filters['distribution'] = intval($period_filters['distribution']);
}
if (!empty($period_filters['sharing_group_id'])) {
$filters['sharing_group_id'] = $period_filters['sharing_group_id'];
}
if (!empty($period_filters['event_info'])) {
$filters['event_info'] = $period_filters['event_info'];
}
if (!empty($period_filters['tags'])) {
$filters['tags'] = $period_filters['tags'];
}
return $filters;
}
private function __genTimerangeFilter(string $period='daily'): string
{
$timerange = '1d';
if ($period == 'weekly') {
$timerange = '7d';
} else if ($period == 'monthly'){
$timerange = '31d';
}
return $timerange;
}
private function __periodToDays(string $period='daily'): int
{
return ($period == 'daily' ? 1 : (
$period == 'weekly' ? 7 : 31)
);
}
private function __getEventsForFilters(array $user, array $filters): array
{
$this->Event = ClassRegistry::init('Event');
$events = $this->Event->fetchEvent($user, $filters);
return $events;
}
public function prepareEmailTemplate(string $period='daily'): SendEmailTemplate
{
$subject = sprintf('[%s MISP] %s %s', Configure::read('MISP.org'), Inflector::humanize($period), __('Notification - %s', (new DateTime())->format('Y-m-d')));
$template = new SendEmailTemplate("notification_$period");
$template->subject($subject);
return $template;
}
}

View File

@ -100,6 +100,15 @@ class UserSetting extends AppModel
'oidc' => [ // Data saved by OIDC plugin
'internal' => true,
],
'periodic_notification_filters' => [
'placeholder' => [
'orgc_id' => '1',
'distribution' => '1',
'sharing_group_id' => '1',
'event_info' => 'phishing',
'tags' => '["tlp:red"]',
],
],
);
// massage the data before we send it off for validation before saving anything

View File

@ -552,38 +552,7 @@ class Workflow extends AppModel
'executed_nodes' => [],
'blocked_paths' => [],
];
$this->Organisation = ClassRegistry::init('Organisation');
$hostOrg = $this->Organisation->find('first', [
'recursive' => -1,
'conditions' => [
'id' => Configure::read('MISP.host_org_id')
],
]);
if (!empty($hostOrg)) {
$userForWorkflow = [
'email' => 'SYSTEM',
'id' => 0,
'org_id' => $hostOrg['Organisation']['id'],
'Role' => ['perm_site_admin' => 1],
'Organisation' => $hostOrg['Organisation']
];
} else {
$this->User = ClassRegistry::init('User');
$userForWorkflow = $this->User->find('first', [
'recursive' => -1,
'conditions' => [
'Role.perm_site_admin' => 1,
'User.disabled' => 0
],
'contain' => [
'Organisation' => ['fields' => ['name']],
'Role' => ['fields' => ['*']],
],
'fields' => ['User.org_id', 'User.id', 'User.email'],
]);
$userForWorkflow['Server'] = [];
$userForWorkflow = $this->User->rearrangeToAuthForm($userForWorkflow);
}
$userForWorkflow = $this->getUserForWorkflow();
if (empty($userForWorkflow)) {
$errors[] = __('Could not find a valid user to run the workflow. Please set setting `MISP.host_org_id` or make sure a valid site_admin user exists.');
return false;
@ -626,6 +595,43 @@ class Workflow extends AppModel
return true;
}
public function getUserForWorkflow(): array
{
$this->Organisation = ClassRegistry::init('Organisation');
$hostOrg = $this->Organisation->find('first', [
'recursive' => -1,
'conditions' => [
'id' => Configure::read('MISP.host_org_id')
],
]);
if (!empty($hostOrg)) {
$userForWorkflow = [
'email' => 'SYSTEM',
'id' => 0,
'org_id' => $hostOrg['Organisation']['id'],
'Role' => ['perm_site_admin' => 1],
'Organisation' => $hostOrg['Organisation']
];
} else {
$this->User = ClassRegistry::init('User');
$userForWorkflow = $this->User->find('first', [
'recursive' => -1,
'conditions' => [
'Role.perm_site_admin' => 1,
'User.disabled' => 0
],
'contain' => [
'Organisation' => ['fields' => ['name']],
'Role' => ['fields' => ['*']],
],
'fields' => ['User.org_id', 'User.id', 'User.email'],
]);
$userForWorkflow['Server'] = [];
$userForWorkflow = $this->User->rearrangeToAuthForm($userForWorkflow);
return $userForWorkflow;
}
}
public function executeNode(array $node, WorkflowRoamingData $roamingData, array &$errors=[]): bool
{
$roamingData->setCurrentNode($node['id']);
@ -779,6 +785,11 @@ class Workflow extends AppModel
];
}
}
if ($moduleType == 'modules_action') {
$moduleClass = $this->getModuleClassByType('action', $module['id']);
$diagnostic = $moduleClass->diagnostic();
$modules[$moduleType][$i]['notifications'] = array_merge_recursive($modules[$moduleType][$i]['notifications'], $diagnostic);
}
}
}
return $modules;
@ -1262,6 +1273,56 @@ class Workflow extends AppModel
return $data;
}
/**
* moduleSattelesExecution Executes a module using the provided configuration and returns back the result
*
* @param string $module_id
* @param string|array $input_data
* @param array $param_data
* @return array
*/
public function moduleStatelessExecution(string $module_id, $input_data=[], array $param_data=[]): array
{
$result = [];
$input_data = !empty($input_data) ? $input_data : [];
$eventPublishTrigger = $this->getModuleClassByType('trigger', 'event-publish');
$data = $this->__normalizeDataForTrigger($eventPublishTrigger, $input_data);
$module_config = $this->getModuleByID($module_id);
$node = $this->genNodeFromConfig($module_config, $param_data);
$module_class = $this->getModuleClass($node);
$user_for_workflow = $this->getUserForWorkflow();
if (empty($user_for_workflow)) {
$result['error'][] = __('Could not find a valid user to run the workflow. Please set setting `MISP.host_org_id` or make sure a valid site_admin user exists.');
return $result;
}
$roaming_data = $this->workflowGraphTool->getRoamingData($user_for_workflow, $data);
$errors = [];
$success = $module_class->exec($node, $roaming_data, $errors);
$result['success'] = $success;
$result['errors'] = $errors;
return $result;
}
public function genNodeFromConfig(array $module_config, $indexed_params): array
{
$node = [
'id' => 1,
'name' => $module_config['name'],
'data' => [
'id' => $module_config['id'],
'name' => $module_config['name'],
'module_type' => $module_config['module_type'],
'module_version' => $module_config['version'],
'indexed_params' => $indexed_params,
'saved_filters' => $module_config['saved_filters'],
'module_data' => $module_config,
],
'inputs' => [],
'outputs' => [],
];
return $node;
}
/**
* hasPathWarnings
*

View File

@ -209,6 +209,23 @@ class WorkflowBaseModule
}
return $items;
}
protected function addNotification(array $errors, string $severity, string $text, string $description='', array $details=[], bool $showInSidebar=false, bool $showInNode=false): array
{
$errors[$severity][] = [
'text' => $text,
'description' => $description,
'details' => $details,
'__show_in_sidebar' => $showInSidebar,
'__show_in_node' => $showInNode,
];
return $errors;
}
public function diagnostic(): array
{
return [];
}
}
class WorkflowBaseTriggerModule extends WorkflowBaseModule

View File

@ -24,13 +24,13 @@ class Module_webhook extends WorkflowBaseActionModule
$this->params = [
[
'id' => 'url',
'label' => 'Payload URL',
'type' => 'input',
'label' => __('Payload URL'),
'type' => 'text',
'placeholder' => 'https://example.com/test',
],
[
'id' => 'content_type',
'label' => 'Content type',
'label' => __('Content type'),
'type' => 'select',
'default' => 'json',
'options' => [
@ -40,17 +40,40 @@ class Module_webhook extends WorkflowBaseActionModule
],
[
'id' => 'data_extraction_path',
'label' => 'Data extraction path',
'type' => 'input',
'label' => __('Data extraction path'),
'type' => 'text',
'default' => '',
'placeholder' => 'Attribute.{n}.AttributeTag.{n}.Tag.name',
],
];
}
public function diagnostic(): array
{
$errors = array_merge(parent::diagnostic(), []);
if (empty(Configure::read('Security.rest_client_enable_arbitrary_urls'))) {
$errors = $this->addNotification(
$errors,
'error',
__('`rest_client_enable_arbitrary_urls` is turned off.'),
__('The module will not send any request as long as `Security.rest_client_enable_arbitrary_urls` is turned off.'),
[
__('This is a security measure to ensure a site-admin do not send arbitrary request to internal services')
],
true,
true
);
}
return $errors;
}
public function exec(array $node, WorkflowRoamingData $roamingData, array &$errors = []): bool
{
parent::exec($node, $roamingData, $errors);
if (empty(Configure::read('Security.rest_client_enable_arbitrary_urls'))) {
$errors[] = __('`Security.rest_client_enable_arbitrary_urls` is turned off');
return false;
}
$params = $this->getParamsWithValues($node);
if (empty($params['url']['value'])) {
$errors[] = __('URL not provided.');

View File

@ -5,6 +5,7 @@ class Module_distribution_if extends WorkflowBaseLogicModule
{
public $id = 'distribution-if';
public $name = 'IF :: Distribution';
public $version = '0.2';
public $description = 'Distribution IF / ELSE condition block. The `then` output will be used if the encoded conditions is satisfied, otherwise the `else` output will be used.';
public $icon = 'code-branch';
public $inputs = 1;
@ -13,7 +14,7 @@ class Module_distribution_if extends WorkflowBaseLogicModule
public $expect_misp_core_format = true;
public $params = [];
private $Attribute;
private $Attribute, $SharingGroup;
private $operators = [
'equals' => 'Is',
'not_equals' => 'Is not',
@ -26,12 +27,15 @@ class Module_distribution_if extends WorkflowBaseLogicModule
parent::__construct();
$this->Attribute = ClassRegistry::init('Attribute');
$distributionLevels = $this->Attribute->shortDist;
unset($distributionLevels[4]);
unset($distributionLevels[5]);
$distribution_param = [];
foreach ($distributionLevels as $i => $text) {
$distribution_param[] = ['name' => $text, 'value' => $i];
}
$this->SharingGroup = ClassRegistry::init('SharingGroup');
$sharing_groups = Hash::combine($this->SharingGroup->fetchAllSharingGroup(), '{n}.SharingGroup.id', '{n}.SharingGroup.name');
$this->params = [
[
'id' => 'scope',
@ -58,6 +62,18 @@ class Module_distribution_if extends WorkflowBaseLogicModule
'options' => $distribution_param,
'placeholder' => __('Pick a distribution'),
],
[
'id' => 'sharing_group_id',
'label' => 'Sharing Groups',
'type' => 'picker',
'multiple' => true,
'options' => $sharing_groups,
'default' => [],
'placeholder' => __('Pick a sharing group'),
'display_on' => [
'distribution' => '4',
],
],
];
}
@ -68,7 +84,8 @@ class Module_distribution_if extends WorkflowBaseLogicModule
$scope = $params['scope']['value'];
$operator = $params['condition']['value'];
$value = $params['distribution']['value'];
$selected_distribution = $params['distribution']['value'];
$selected_sharing_groups = !empty($params['sharing_group_id']['value']) ? $params['sharing_group_id']['value'] : [];
$data = $roamingData->getData();
$final_distribution = $this->__getPropagatedDistribution($data['Event']);
if ($scope == 'attribute') {
@ -78,20 +95,33 @@ class Module_distribution_if extends WorkflowBaseLogicModule
$data['Event']['Attribute'][0]
);
}
if ($final_distribution == -1) {
if (!in_array($final_distribution, range(0, 4))) {
$errors[] = __('Distribution level not supported');
return false; // distribution not supported
}
if ($operator == 'more_restrictive_or_equal_than') {
$operator = 'in';
$distribution_range = range(0, $value);
} else if ($operator == 'more_permisive_or_equal_than') {
$operator = 'in';
$distribution_range = range($value, 3);
if ($selected_distribution == 4) {
$final_sharing_group = $this->__extractSharingGroupIDs(
$data['Event'],
$data['Event']['Attribute'][0]['Object'] ?? [],
$data['Event']['Attribute'][0]
);
if ($operator == 'equals') {
return !array_diff($final_sharing_group, $selected_sharing_groups); // All sharing groups are in the selection
} else if ($operator == 'not_equals') {
return count(array_diff($final_sharing_group, $selected_sharing_groups)) == count($final_sharing_group); // All sharing groups are in the selection
}
$errors[] = __('Condition operator not supported for that distribution level');
return false;
} else {
$distribution_range = intval($value);
}
if ($operator == 'more_restrictive_or_equal_than' || $operator == 'more_permisive_or_equal_than') {
$distribution_range = array_diff($value, [4]); // ignore sharing_group for now
if ($operator == 'more_restrictive_or_equal_than') {
$operator = 'in';
$distribution_range = range(0, $selected_distribution);
} else if ($operator == 'more_permisive_or_equal_than') {
$operator = 'in';
$distribution_range = range($selected_distribution, 3);
} else {
$distribution_range = intval($selected_distribution);
}
}
$eval = $this->evaluateCondition($distribution_range, $operator, $final_distribution);
return !empty($eval);
@ -112,17 +142,35 @@ class Module_distribution_if extends WorkflowBaseLogicModule
$finalDistribution = intval($attribute['distribution']);
}
if (!empty($object)) { // downgrade based on the object distribution
$finalDistribution = min($finalDistribution, intval($object['distribution']));
}
$finalDistribution = min($finalDistribution, intval($event['distribution'])); // downgrade based on the event distribution
if (!empty($attribute)) {
if ($attribute['distribution'] == 5) { // mirror distribution for the one of the event
$attribute['distribution'] = intval($event['distribution']);
}
}
if ($finalDistribution == 4) { // ignore sharing group for now
$finalDistribution = -1;
$finalDistribution = $this->__getMostRestrictiveDistribution($finalDistribution, intval($object['distribution'])); // downgrade based on the object distribution
}
$finalDistribution = $this->__getMostRestrictiveDistribution($finalDistribution, intval($event['distribution'])); // downgrade based on the event distribution
return $finalDistribution;
}
private function __getMostRestrictiveDistribution(int $distri1, int $distri2): int
{
if ($distri1 == 0 || $distri2 == 0) {
return 0;
}
if ($distri1 == 4 || $distri2 == 4) {
return 4;
}
return min($distri1, $distri2);
}
private function __extractSharingGroupIDs(array $event, array $object=[], array $attribute=[]): array
{
$sgIDs = [];
if (!empty($event) && $event['distribution'] == 4) {
$sgIDs[] = $event['sharing_group_id'];
}
if (!empty($attribute) && $attribute['distribution'] == 4) {
$sgIDs[] = $attribute['sharing_group_id'];
}
if (!empty($object) && $object['distribution'] == 4) { // downgrade based on the object distribution
$sgIDs[] = $object['sharing_group_id'];
}
return $sgIDs;
}
}

View File

@ -0,0 +1,345 @@
<?php
$clusteredTags = $trendAnalysis['clustered_tags'];
$clusteredEvents = $trendAnalysis['clustered_events'];
$allTags = $trendAnalysis['all_tags'];
$allTimestamps = $trendAnalysis['all_timestamps'];
$currentPeriod = $allTimestamps[0];
$previousPeriod = $allTimestamps[1];
$previousPeriod2 = $allTimestamps[2];
$allUniqueTagsPerPeriod = array_map(function ($tags) {
return array_keys($tags);
}, $clusteredTags);
$allUniqueTags = array_unique(array_merge($allUniqueTagsPerPeriod[$currentPeriod], $allUniqueTagsPerPeriod[$previousPeriod], $allUniqueTagsPerPeriod[$previousPeriod2]));
App::uses('ColourPaletteTool', 'Tools');
$paletteTool = new ColourPaletteTool();
$COLOR_PALETTE = $paletteTool->createColourPalette(max(count($allUniqueTags), 1));
$trendIconMapping = [
1 => '▲',
-1 => '▼',
0 => '⮞',
'?' => '',
];
$trendColorMapping = [
1 => '#b94a48',
-1 => '#468847',
0 => '#3a87ad',
'?' => '#999999',
];
$now = new DateTime();
$currentPeriodDate = DateTime::createFromFormat('U', $currentPeriod);
$previousPeriodDate = DateTime::createFromFormat('U', $previousPeriod);
$previousPeriod2Date = DateTime::createFromFormat('U', $previousPeriod2);
$colorForTags = [];
$chartData = [];
$maxValue = 0;
foreach ($allUniqueTags as $i => $tag) {
$colorForTags[$tag] = $COLOR_PALETTE[$i];
$chartData[$tag] = [
$clusteredTags[$previousPeriod2][$tag]['occurence'] ?? 0,
$clusteredTags[$previousPeriod][$tag]['occurence'] ?? 0,
$clusteredTags[$currentPeriod][$tag]['occurence'] ?? 0,
];
$maxValue = max($maxValue, max($chartData[$tag]));
}
$canvasWidth = 600;
$canvasHeight = 150;
foreach (array_keys($chartData) as $tag) {
$chartData[$tag][0] = [0, $canvasHeight - ($chartData[$tag][0] / $maxValue) * $canvasHeight];
$chartData[$tag][1] = [$canvasWidth / 2, $canvasHeight - ($chartData[$tag][1] / $maxValue) * $canvasHeight];
$chartData[$tag][2] = [$canvasWidth, $canvasHeight - ($chartData[$tag][2] / $maxValue) * $canvasHeight];
}
if (!function_exists('reduceTag')) {
function reduceTag(string $tagname, int $reductionLength = 1): string
{
$re = '/^(?<namespace>[a-z0-9_-]+)(:(?<predicate>[a-z0-9_-]+)="(?<value>[^"]+)"$)?(:(?<predicate2>[a-z0-9_-]+))?/';
$matches = [];
preg_match($re, $tagname, $matches);
if (!empty($matches['predicate2'])) {
return $reductionLength == 0 ? $tagname : $matches['predicate2'];
} else if (!empty($matches['value'])) {
return $reductionLength == 0 ? $tagname : ($reductionLength == 1 ? sprintf('%s="%s"', $matches['predicate'], $matches['value']) : $matches['value']
);
} else if (!empty($matches['namespace'])) {
return $matches['namespace'];
} else {
return $tagname;
}
}
}
if (!function_exists('computeLinePositions')) {
function computeLinePositions(float $x1, float $y1, float $x2, float $y2): array
{
$x_offset = 3.5;
$y_offset = 1;
$conf = [
'left' => $x1 + $x_offset,
'top' => $y1 + $y_offset,
'width' => sqrt(pow($y2 - $y1, 2) + pow($x2 - $x1, 2)),
'angle' => atan(($y2 - $y1) / ($x2 - $x1)),
];
return $conf;
}
}
?>
<div style="display: flex; column-gap: 20px; justify-content: space-around; margin-bottom: 40px;">
<div style="display: flex; align-items: center;">
<table class="table table-condensed" style="min-width: 300px; max-width: 400px; margin: 0;">
<tbody>
<tr>
<td><?= __('Period duration') ?></td>
<td><?= __('%s days', $currentPeriodDate->diff($now)->format('%a')); ?></td>
</tr>
<tr>
<td><?= __('Starting period') ?></td>
<td><?= sprintf('%s', $currentPeriodDate->format('M d, o. (\W\e\e\k W)')); ?></td>
</tr>
<tr>
<td><?= __('Previous period') ?></td>
<td><?= sprintf('%s', $previousPeriodDate->format('M d, o. (\W\e\e\k W)')); ?></td>
</tr>
<tr>
<td><?= __('Period-2') ?></td>
<td><?= sprintf('%s', $previousPeriod2Date->format('M d, o. (\W\e\e\k W)')); ?></td>
</tr>
</tbody>
</table>
</div>
<?php if (!empty($allUniqueTags)) : ?>
<div style="padding: 0 40px;">
<div class="chart-container">
<div class="y-axis-container">
<div>
<span class="y-axis-label" style="<?= sprintf('left: %spx; top: %spx; transform: translate(-100%%, %s%%)', 0, 0, -25) ?>"><?= h($maxValue) ?></span>
<span class="y-axis-label" style="<?= sprintf('left: %spx; top: %spx; transform: translate(-100%%, %s%%)', 0, ($canvasHeight - 20)/2, 0) ?>"><?= h(round($maxValue / 2, 2)) ?></span>
<span class="y-axis-label" style="<?= sprintf('left: %spx; top: %spx; transform: translate(-100%%, %s%%)', 0, ($canvasHeight - 20), 25) ?>"><?= 0 ?></span>
</div>
</div>
<div class="canvas">
<?php foreach ($chartData as $tag => $coords) : ?>
<?php for ($i = 0; $i < 3; $i++) : ?>
<?php
$coord = $coords[$i];
$previousCoord = isset($coords[$i - 1]) ? $coords[$i - 1] : false;
?>
<span class="dot" style="<?= sprintf('left: %spx; top: %spx; background-color: %s;', $coord[0], $coord[1], $colorForTags[$tag]) ?>" title="<?= h($tag) ?>"></span>
<?php
if (!empty($previousCoord)) {
$linePosition = computeLinePositions($previousCoord[0], $previousCoord[1], $coord[0], $coord[1]);
echo sprintf(
'<span class="line" style="left: %spx; top: %spx; width: %spx; transform: rotate(%srad); background-color: %s;" title="%s"></span>',
$linePosition['left'],
$linePosition['top'],
$linePosition['width'],
$linePosition['angle'],
$colorForTags[$tag],
h($tag)
);
}
?>
<?php endfor ?>
<?php endforeach ?>
</div>
<div class="x-axis-container">
<span class="x-axis-label" style="<?= sprintf('left: %spx; top: %spx;', 0, 0) ?>"><?= __('Period-2') ?></span>
<span class="x-axis-label" style="<?= sprintf('left: %spx; top: %spx;', $canvasWidth / 2, 0) ?>"><?= __('Previous period') ?></span>
<span class="x-axis-label" style="<?= sprintf('left: %spx; top: %spx;', $canvasWidth, 0) ?>"><?= __('Starting period') ?></span>
</div>
</div>
</div>
<?php else : ?>
<p><?= __('- No tag for the selected tag namespace -') ?></p>
<?php endif; ?>
</div>
<?php if (!empty($allTags)) : ?>
<table class="table table-condensed no-border">
<thead>
<tr>
<th></th>
<th>
<span>
<div><?= __('Period-2') ?></div>
<div style="font-weight: normal;"><?= __('%s events', h($clusteredEvents[$previousPeriod2])) ?></div>
</span>
<table>
<thead style="font-size: small;">
<tr>
<td style="min-width: 20px;">#</td>
<td style="min-width: 15px;">⥮</td>
<td>%</td>
<td></td>
</tr>
</thead>
</table>
</th>
<th>
<span>
<div><?= __('Previous period') ?></div>
<div style="font-weight: normal;"><?= __('%s events', h($clusteredEvents[$previousPeriod])) ?></div>
</span>
<table>
<thead style="font-size: small;">
<tr>
<td style="min-width: 20px;">#</td>
<td style="min-width: 15px;">⥮</td>
<td>%</td>
<td></td>
</tr>
</thead>
</table>
</th>
<th>
<span>
<div><?= __('Starting period') ?></div>
<div style="font-weight: normal;"><?= __('%s events', h($clusteredEvents[$currentPeriod])) ?></div>
</span>
<table>
<thead style="font-size: small;">
<tr>
<td style="min-width: 20px;">#</td>
<td style="min-width: 15px;">⥮</td>
<td>%</td>
<td></td>
</tr>
</thead>
</table>
</th>
</tr>
</thead>
<?php foreach ($tagFilterPrefixes as $tagPrefix) : ?>
<?php
if (empty($allTags[$tagPrefix])) {
continue;
}
?>
<tbody>
<tr>
<td colspan="4">
<h4><?= __('Tag namespace: %s', sprintf('<code>%s</code>', h($tagPrefix))) ?></h4>
</td>
</tr>
<?php foreach ($allTags[$tagPrefix] as $tagName) : ?>
<tr>
<td style="padding-left: 15px;">
<span class="tag-legend" style="background-color: <?= $colorForTags[$tagName] ?>;"></span>
<code><?= h(reduceTag($tagName, count(explode(':', $tagPrefix)))) ?></code>
</td>
<td>
<table class="table-condensed no-border">
<tbody>
<tr>
<td><?= h($clusteredTags[$previousPeriod2][$tagName]['occurence'] ?? '-') ?></td>
<td><?= h($clusteredTags[$previousPeriod2][$tagName]['raw_change'] ?? '-') ?></td>
<td><?= h($clusteredTags[$previousPeriod2][$tagName]['percent_change'] ?? '-') ?>%</td>
<td style="font-size: large; color: <?= $trendColorMapping[$clusteredTags[$previousPeriod2][$tagName]['change_sign'] ?? '?'] ?>"><?= $trendIconMapping[$clusteredTags[$previousPeriod2][$tagName]['change_sign'] ?? '?'] ?></td>
</tr>
</tbody>
</table>
</td>
<td>
<table class="table-condensed no-border">
<tbody>
<tr>
<td><?= h($clusteredTags[$previousPeriod][$tagName]['occurence'] ?? '-') ?></td>
<td><?= h($clusteredTags[$previousPeriod][$tagName]['raw_change'] ?? '-') ?></td>
<td><?= h($clusteredTags[$previousPeriod][$tagName]['percent_change'] ?? '-') ?>%</td>
<td style="font-size: large; color: <?= $trendColorMapping[$clusteredTags[$previousPeriod][$tagName]['change_sign'] ?? '?'] ?>"><?= $trendIconMapping[$clusteredTags[$previousPeriod][$tagName]['change_sign'] ?? '?'] ?></td>
</tr>
</tbody>
</table>
</td>
<td>
<table class="table-condensed no-border">
<tbody>
<tr>
<td><?= h($clusteredTags[$currentPeriod][$tagName]['occurence'] ?? '-') ?></td>
<td><?= h($clusteredTags[$currentPeriod][$tagName]['raw_change'] ?? '-') ?></td>
<td><?= h($clusteredTags[$currentPeriod][$tagName]['percent_change'] ?? '-') ?>%</td>
<td style="font-size: large; color: <?= $trendColorMapping[$clusteredTags[$currentPeriod][$tagName]['change_sign'] ?? '?'] ?>"><?= $trendIconMapping[$clusteredTags[$currentPeriod][$tagName]['change_sign'] ?? '?'] ?></td>
</tr>
</tbody>
</table>
</td>
</tr>
<?php endforeach; ?>
</tbody>
<?php endforeach; ?>
</table>
<?php endif; ?>
<style>
.dot {
position: absolute;
height: 7px;
width: 7px;
border-radius: 50%;
}
.line {
position: absolute;
background: blue;
height: 3px;
transform-origin: left center;
box-shadow: 1px 1px 2px 0px #00000066;
}
.chart-container {
position: relative;
background-color: #dddddd33;
padding: 5px 35px 5px 45px;
border-radius: 5px;
border: 1px solid #ddd;
}
.canvas {
width: 610px;
height: 160px;
position: relative;
}
.x-axis-container {
position: relative;
height: 20px;
}
.x-axis-label {
position: absolute;
white-space: nowrap;
translate: -50%;
}
.y-axis-container {
height: 150px;
border-right: 1px solid #000;
position: absolute;
left: -5px;
top: 10px;
padding-left: inherit;
}
.y-axis-container > div {
position: relative;
height: 100%;
}
.y-axis-label {
position: absolute;
white-space: nowrap;
/* transform: translate(-100%, 0%); */
padding: 0 5px;
}
.tag-legend {
display: inline-block;
width: 10px;
height: 10px;
border: 1px solid #000;
}
</style>

View File

@ -0,0 +1,193 @@
<?php
$type_mapper = [
'picker' => 'dropdown',
];
?>
<h3><?= __('Stateless module execution') ?></h3>
<div class="row">
<div class="span6">
<h5><?= __('Module parameters') ?></h5>
<div>
<?php
$formFields = array_map(function ($param) use ($type_mapper) {
$param['field'] = $param['id'];
$param['class'] = 'span6';
$param['type'] = $type_mapper[$param['type']] ?? $param['type'];
if (!empty($param['options']) && array_keys($param['options']) === range(0, count($param['options']) - 1)) {
// Sequential arrays should be keyed with their value
if (!empty($param['options'])) {
if (isset($param['options'][0]['name']) && isset($param['options'][0]['value'])) {
$tmp = [];
foreach ($param['options'] as $option) {
$tmp[$option['value']] = $option['name'];
};
$param['options'] = $tmp;
} else {
$param['options'] = array_combine($param['options'], $param['options']);
}
}
}
return $param;
}, $module['params']);
echo $this->element('genericElements/Form/genericForm', [
'data' => [
'skip_side_menu' => true,
'title' => ' ',
'fields' => $formFields,
'submit' => [
'no_submit' => true,
'action' => $this->request->params['action'],
'ajaxSubmit' => 'submitGenericFormInPlace();'
]
]
]);
?>
</div>
</div>
<div class="span6">
<h5><?= __('Input data') ?></h5>
<div>
<?php
$formFields = [
[
'field' => 'module_input_data',
'type' => 'textarea',
'class' => 'span6',
]
];
echo $this->element('genericElements/Form/genericForm', [
'data' => [
'skip_side_menu' => true,
'title' => ' ',
'fields' => $formFields,
'submit' => [
'no_submit' => true,
'action' => $this->request->params['action'],
'ajaxSubmit' => 'submitGenericFormInPlace();'
]
]
]);
?>
</div>
</div>
</div>
<div>
<button id="run-module" class="btn btn-primary">
<span class="fa fa-spin fa-spinner loading-span hidden"></span>
<?= __('Execute module') ?>
</button>
</div>
<div class="row" style="margin-top: 10px;">
<div class="span9">
<div style="margin: 0.5em 0;">
<strong><?= __('Execution result:') ?></strong>
<span id="executionResultStatus" class="label"><?= __('none') ?></span>
</div>
<pre id="executionResultText"><?= __('- not executed -') ?></pre>
<div id="executionResultHtml"></div>
</div>
</div>
<script>
var module = <?= JsonTool::encode($module) ?>;
var $runModuleBtn = $('#run-module')
var $executionResultStatus = $('#executionResultStatus')
var $executionResultText = $('#executionResultText')
var $formParams = $('#WorkflowModuleViewForm')
var $inputData = $('#WorkflowModuleInputData')
$(document).ready(function() {
$runModuleBtn.click(submitModuleExecution)
$('select[multiple]').chosen()
})
function submitModuleExecution() {
var data = collectData()
performRequest(data)
}
function toggleLoading($button, loading) {
if (loading) {
$button
.prop('disabled', true)
.find('.loading-span').show()
} else {
$button
.prop('disabled', false)
.find('.loading-span').hide()
}
}
function collectData() {
var formData = new FormData($formParams[0])
var indexedParams = {}
Array.from(formData.keys()).forEach(function(fullFieldName) {
var myRegexp = new RegExp(/data\[Workflow\]\[(\w+)\](\[\])*/, 'g');
var match = myRegexp.exec(fullFieldName);
if (match != null) {
fieldName = match[1]
isMultiple = match[2] !== undefined
if (isMultiple) {
indexedParams[fieldName] = formData.getAll(fullFieldName)
} else {
indexedParams[fieldName] = formData.get(fullFieldName)
}
}
})
return {
module_indexed_param: indexedParams,
input_data: $inputData.val(),
}
}
function showExecutionResult(jqXHR, result) {
$executionResultStatus
.text(jqXHR.status + ' [' + jqXHR.duration + ' ms]')
.removeClass(['label-success', 'label-important'])
.addClass(jqXHR.status == 200 ? 'label-success' : 'label-important')
if (typeof result === 'object') {
$executionResultText.text(JSON.stringify(result, '', 4));
} else {
$('#executionResultHtml').html(result);
// $executionResultText.text(result);
}
}
function performRequest(data) {
url = '<?= $baseurl ?>/workflows/moduleStatelessExecution/<?= h($module['id']) ?>'
var start = new Date().getTime();
$.ajax({
data: data,
beforeSend: function() {
toggleLoading($runModuleBtn, true)
},
success: function(result, textStatus, jqXHR) {
jqXHR.duration = (new Date().getTime() - start);
if (result) {
showExecutionResult(jqXHR, result)
}
},
error: function(jqXHR, _, _) {
jqXHR.duration = (new Date().getTime() - start);
errorThrown = jqXHR.responseJSON ? jqXHR.responseJSON.errors : (jqXHR.responseText ? jqXHR.responseText : jqXHR.statusText)
showExecutionResult(jqXHR, errorThrown)
},
complete: function() {
toggleLoading($runModuleBtn, false)
},
type: 'post',
url: url
})
}
</script>
<style>
.loading-span {
margin-right: 5px;
margin-left: 0px;
line-height: 20px;
}
</style>

View File

@ -28,7 +28,7 @@ $classFromSeverity = [
<?php foreach (array_keys($classFromSeverity) as $severity) : ?>
<?php
$visibleNotifications = array_filter($block['notifications'][$severity], function ($notification) {
return $notification['__show_in_sidebar'];
return !empty($notification['__show_in_sidebar']);
});
?>
<?php if (!empty($visibleNotifications)) : ?>

View File

@ -1,8 +1,22 @@
<?php
$seed = 's-' . mt_rand();
$fieldData['type'] = 'select';
if (empty($fieldData['class'])) {
$fieldData['class'] = $seed;
} else {
$fieldData['class'] .= ' ' . $seed;
}
echo $this->Form->input($fieldData['field'], $fieldData);
if (!empty($params['description'])) {
echo sprintf('<small class="clear form-field-description apply_css_arrow">%s</small>', h($params['description']));
}
?>
<?php if (!empty($fieldData['picker'])) : ?>
<script>
var chosenOptions = <?= JsonTool::encode($fieldData['_chosenOptions'] ?? []) ?>;
$('select.<?= $seed ?>').chosen(chosenOptions)
</script>
<?php endif; ?>

View File

@ -0,0 +1,33 @@
<?php
$seed = mt_rand();
$input = $this->Form->input($fieldData['field'], [
'class' => ($fieldData['class'] ?? '') . ' tag-textarea',
'label' => $fieldData['label'] ?? __('Tag list'),
'type' => 'textarea',
'placeholder' => $fieldData['placeholder'] ?? 'tlp:red, PAP:GREEN',
'div' => 'input text input-append',
'after' => sprintf('<button type="button" class="btn" onclick="pickerTags.call(this);">%s</button>', __('Pick tags')),
]);
?>
<div class="seed-<?= $seed ?>">
<?= $input ?>
</div>
<script>
function pickerTags() {
$(this).data('popover-no-submit', true);
$(this).data('popover-callback-function', setTagsAfterSelect);
var target_id = 0;
var target_type = 'galaxyClusterRelation';
popoverPopup(this, target_id + '/' + target_type, 'tags', 'selectTaxonomy')
}
function setTagsAfterSelect(selected, additionalData) {
selectedTags = [];
selected.forEach(function(selection) {
selectedTags.push(additionalData.itemOptions[selection].tag_name);
});
$('div.seed-<?= $seed ?> textarea.tag-textarea').val(JSON.stringify(selectedTags));
}
</script>

View File

@ -105,7 +105,7 @@ if (!empty($ajax)) {
),
sprintf(
'<div class="modal-footer">%s</div>',
$this->element('genericElements/Form/submitButton', $submitButtonData)
!empty($data['submit']['no_submit']) ? '' : $this->element('genericElements/Form/submitButton', $submitButtonData)
)
);
} else {
@ -119,7 +119,7 @@ if (!empty($ajax)) {
empty($data['description']) ? '' : $data['description'],
$fieldsString,
$metaFieldString,
$this->element('genericElements/Form/submitButton', $submitButtonData),
!empty($data['submit']['no_submit']) ? '' : $this->element('genericElements/Form/submitButton', $submitButtonData),
$formEnd
);
}

View File

@ -377,6 +377,18 @@ $divider = $this->element('/genericElements/SideMenu/side_menu_divider');
'text' => __('View delegation requests')
));
}
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'element_id' => 'viewPeriodicSummary',
'url' => $baseurl . '/users/viewPeriodicSummary/daily',
'text' => __('View periodic summary')
));
if ($menuItem === 'viewPeriodicSummary' || $menuItem === 'notification_settings') {
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'element_id' => 'notification_settings',
'url' => $baseurl . '/users/notificationSettings',
'text' => __('Periodic summary settings')
));
}
echo $divider;
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'url' => $baseurl . '/events/export',
@ -633,6 +645,11 @@ $divider = $this->element('/genericElements/SideMenu/side_menu_divider');
'url' => $baseurl . '/user_settings/index/user_id:me',
'text' => __('My Settings')
));
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'element_id' => 'notification_settings',
'url' => $baseurl . '/users/notificationSettings',
'text' => __('Periodic summary settings')
));
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'element_id' => 'user_settings_set',
'url' => $baseurl . '/user_settings/setSetting',

View File

@ -32,15 +32,21 @@
- $scores: The score associate with either the value or the tag name (if provided)
- $removeTrailling: How much part of the name of the cell should be remove: e.g. $removeTrailling=2 => "abc def ghi", will be: "abc"
- $colours: The colour associated with the tag name (if provided)
- $static: Should the output be inert. Used for embedding in other webpages or mails
*
*
*
*/
echo $this->element('genericElements/assetLoader', [
'css' => ['attack_matrix'],
'js' => ['attack_matrix'],
]);
if (!empty($static)) {
$pickingMode = false;
}
if (empty($static)) {
echo $this->element('genericElements/assetLoader', [
'css' => ['attack_matrix'],
'js' => ['attack_matrix'],
]);
}
$clustersNamesMapping = array(); // used to map name with id for the chosen select
if (isset($interpolation) && !empty($interpolation)) {
foreach ($interpolation as $k => $colArr) {
@ -55,6 +61,8 @@ if (isset($interpolation) && !empty($interpolation)) {
$colorScale = 'black';
}
?>
<?php if (empty($static)): ?>
<div class="attack-matrix-options" style="right: initial; background: transparent;">
<ul id="attack-matrix-tabscontroller" class="nav nav-tabs" style="margin-bottom: 2px;">
<?php
@ -63,17 +71,23 @@ if (!isset($defaultTabName)) {
$defaultTabName = key($tabs); // get first key
}
if (empty($static)):
foreach($tabs as $tabName => $column):
?>
<li class="tactic <?php echo $tabName==$defaultTabName ? "active" : ""; ?>"><span href="#tabMatrix-<?php echo h($tabName); ?>" data-toggle="tab" style="padding-top: 3px; padding-bottom: 3px;"><?php echo h($tabName); ?></span></li>
<?php endforeach; ?>
<?php endif; ?>
</ul>
</div>
<?php endif; ?>
<?php if (empty($static)): ?>
<div class="attack-matrix-options matrix-div-submit submit-container">
<span class="btn btn-inverse btn-matrix-submit" role="button" tabindex="0" style="padding: 1px 5px !important;font-size: 12px !important;font-weight: bold;"><?php echo __('Submit'); ?></span>
</div>
<?php endif; ?>
<?php if (empty($static)): ?>
<div class="attack-matrix-options">
<?php if (isset($interpolation)): ?>
<span id="matrix-heatmap-legend-caret">
@ -88,8 +102,9 @@ foreach($tabs as $tabName => $column):
<?php endif; ?>
<label style="display: inline-block; margin-left: 30px;"><input type="checkbox" id="checkbox_attackMatrix_showAll" checked><i class="fa fa-filter"></i><?= __('Show all') ?></label>
</div>
<?php endif; ?>
<?php if (isset($eventId)): ?>
<?php if (isset($eventId) && empty($static)): ?>
<div class="hidden">
<?php
$url = sprintf(
@ -111,21 +126,26 @@ foreach($tabs as $tabName => $column):
<div id="matrix_container" class="fixed-table-container-inner" style="" data-picking-mode="<?php echo $pickingMode ? 'true' : 'false'; ?>">
<div class="tab-content">
<?php foreach($tabs as $tabName => $column): ?>
<?php
if (!empty($static) && $tabName != $defaultTabName) {
// We cannot hide other tabs without JS. Only releave the default one for now.
continue;
}
?>
<div class="tab-pane <?php echo $tabName==$defaultTabName ? "active" : ""; ?>" id="tabMatrix-<?php echo h($tabName); ?>">
<div class="header-background"></div>
<div class="fixed-table-container-inner" style="">
<div class="fixed-table-container-inner" style="overflow-y: auto; max-height: 670px;">
<table class="table table-condensed matrix-table">
<thead>
<thead style="background-color: #363636;">
<tr>
<?php
foreach($columnOrders[$tabName] as $co):
$name = str_replace("-", " ", $co);
?>
<th>
<?php echo h(ucfirst($name)); ?>
<div class="th-inner" style="flex-direction: column; align-items: flex-start; padding-top: 3px;">
<?php echo empty($static) ? h(ucfirst($name)) : ''; ?>
<div class="th-inner" style="flex-direction: column; align-items: flex-start; padding-top: 3px; color: white;">
<span><?php echo h(ucfirst($name)); ?></span>
<i style="font-size: smaller;"><?php echo __('(%s items)', isset($column[$co]) ? count($column[$co]) : 0); ?></i>
</div>
</th>
@ -160,7 +180,7 @@ foreach($tabs as $tabName => $column):
$externalId = isset($cell['external_id']) ? $cell['external_id'] : '';
$title = h($externalId);
if (!empty($cell['description'])) {
if (empty($static) && !empty($cell['description'])) {
$shortDescription = $this->Markdown->toText($cell['description']);
if (strlen($shortDescription) > 1000) {
$shortDescription = mb_substr($shortDescription, 0, 1000) . '[...]';

View File

@ -0,0 +1,2 @@
<?php
$this->extend('/Emails/notification_common');

View File

@ -0,0 +1,7 @@
<?php
$this->__vars = [
'event_table_include_basescore' => false,
];
$this->extend('/Emails/notification_common');
$this->assign('detailed-summary-type', ' ');
$this->assign('detailed-summary-tags', ' ');

View File

@ -0,0 +1,5 @@
<?php
$this->__vars = [
'event_table_include_basescore' => true,
];
$this->extend('/Emails/notification_common');

View File

@ -0,0 +1,518 @@
<?php
/**
* Available template block that can be overriden using $this->assign('table-overview', ' ');
* - `prepend-html`
* - `table-overview`
* - `detailed-summary-full`
* - `detailed-summary-mitre-attack`
* - `detailed-summary-type`
* - `detailed-summary-tags`
* - `detailed-summary-events`
* - `aggregated-context`
*
* Additional variables:
* - `event-table-include-basescore`: bool
*/
if (empty($this->__vars)) {
$this->__vars = [];
}
$default_vars = [
'event_table_include_basescore' => true,
'additional_taxonomy_event_list' => [
'PAP' => 'PAP:'
],
];
$vars = array_merge($default_vars, $this->__vars);
$now = new DateTime();
$start_date = new DateTime('7 days ago');
$event_number = count($events);
$attribute_number = 0;
$object_number = 0;
$event_report_number = 0;
$proposal_number = 0;
$attribute_types = [];
$object_types = [];
$all_event_report = [];
$all_tag_amount = [];
$unique_tag_number = 0;
$tag_color_mapping = [];
$mitre_attack_techniques = [];
$mitre_galaxy_tag_prefix = 'misp-galaxy:mitre-attack-pattern="';
foreach ($events as $event) {
$unique_tag_per_event = [];
$attribute_number += count($event['Attribute']);
$object_number += count($event['Object']);
$event_report_number += count($event['EventReport']);
$proposal_number += count($event['ShadowAttribute']);
foreach ($event['EventTag'] as $event_tag) {
$tag = $event_tag['Tag'];
if (!empty($unique_tag_per_event[$tag['name']])) {
continue; // Only one instance of tag per event
}
$unique_tag_per_event[$tag['name']] = true;
if (empty($all_tag_amount[$tag['name']])) {
$all_tag_amount[$tag['name']] = 0;
$tag_color_mapping[$tag['name']] = $tag['colour'];
}
$all_tag_amount[$tag['name']] += 1;
if (!empty($tag['is_galaxy']) && substr($tag['name'], 0, strlen($mitre_galaxy_tag_prefix)) === $mitre_galaxy_tag_prefix) {
$technique = substr($tag['name'], strlen($mitre_galaxy_tag_prefix), strlen($tag['name']) - strlen($mitre_galaxy_tag_prefix) - 1);
$mitre_attack_techniques[$technique] = $event_tag;
}
}
foreach ($event['Attribute'] as $attribute) {
if (empty($attribute_types[$attribute['type']])) {
$attribute_types[$attribute['type']] = 0;
}
$attribute_types[$attribute['type']] += 1;
foreach ($attribute['AttributeTag'] as $attribute_tag) {
$tag = $attribute_tag['Tag'];
if (!empty($unique_tag_per_event[$tag['name']])) {
continue; // Only one instance of tag per event
}
$unique_tag_per_event[$tag['name']] = true;
if (empty($all_tag_amount[$tag['name']])) {
$all_tag_amount[$tag['name']] = 0;
$tag_color_mapping[$tag['name']] = $tag['colour'];
}
$all_tag_amount[$tag['name']] += 1;
if (!empty($tag['is_galaxy']) && substr($tag['name'], 0, strlen($mitre_galaxy_tag_prefix)) === $mitre_galaxy_tag_prefix) {
$technique = substr($tag['name'], strlen($mitre_galaxy_tag_prefix), strlen($tag['name']) - strlen($mitre_galaxy_tag_prefix) - 1);
$mitre_attack_techniques[$technique] = $attribute_tag;
}
}
}
foreach ($event['Object'] as $object) {
if (empty($object_types[$object['name']])) {
$object_types[$object['name']] = 0;
}
$object_types[$object['name']] += 1;
$attribute_number += count($object['Attribute']);
foreach ($object['Attribute'] as $attribute) {
if (empty($attribute_types[$attribute['type']])) {
$attribute_types[$attribute['type']] = 0;
}
$attribute_types[$attribute['type']] += 1;
foreach ($attribute['AttributeTag'] as $attribute_tag) {
$tag = $attribute_tag['Tag'];
if (!empty($unique_tag_per_event[$tag['name']])) {
continue; // Only one instance of tag per event
}
$unique_tag_per_event[$tag['name']] = true;
if (empty($all_tag_amount[$tag['name']])) {
$all_tag_amount[$tag['name']] = 0;
$tag_color_mapping[$tag['name']] = $tag['colour'];
}
$all_tag_amount[$tag['name']] += 1;
if (!empty($tag['is_galaxy']) && substr($tag['name'], 0, strlen($mitre_galaxy_tag_prefix)) === $mitre_galaxy_tag_prefix) {
$technique = substr($tag['name'], strlen($mitre_galaxy_tag_prefix), strlen($tag['name']) - strlen($mitre_galaxy_tag_prefix) - 1);
$mitre_attack_techniques[$technique] = $attribute_tag;
}
}
}
}
foreach ($event['EventReport'] as $event_report) {
$all_event_report[] = [
'uuid' => $event_report['uuid'],
'name' => $event_report['name'],
'event_id' => $event_report['event_id'],
'event_info' => $event['Event']['info'],
];
}
}
if (!function_exists('findAndBuildTag')) {
function findAndBuildTag($tag_list, $tag_prefix, $that)
{
foreach ($tag_list as $tag) {
if (substr($tag['Tag']['name'], 0, strlen($tag_prefix)) == $tag_prefix) {
return $that->element('tag', ['tag' => $tag]);
}
}
return '';
}
}
$unique_tag_number = count(array_keys($all_tag_amount));
arsort($attribute_types);
arsort($object_types);
arsort($all_tag_amount);
array_splice($attribute_types, 10);
array_splice($object_types, 10);
array_splice($all_tag_amount, 10);
?>
<?php if ($this->fetch('prepend-html')) : ?>
<?= $this->fetch('prepend-html') ?>
<?php endif; ?>
<?php if ($this->fetch('table-overview')) : ?>
<?= $this->fetch('table-overview'); ?>
<?php else : ?>
<div class="panel">
<div class="panel-header">
<?= __('Data at a glance') ?>
</div>
<div class="panel-body">
<table class="table table-condensed mw-50">
<tbody>
<tr>
<td><?= __('Summary period') ?></td>
<td><?= h($period) ?></td>
</tr>
<tr>
<td><?= __('Summary for dates') ?></td>
<td>
<?=
sprintf('<strong>%s</strong> (Week %s) ➞ <strong>%s</strong> (Week %s)',
$start_date->format('M d, o'),
$start_date->format('W'),
$now->format('M d, o'),
$now->format('W'),
$start_date->format('M d, o')
)
?>
</td>
</tr>
<tr>
<td><?= __('Generation date') ?></td>
<td><?= date("c"); ?></td>
</tr>
<tr>
<td><?= __('Events #') ?></td>
<td><?= $event_number ?></td>
</tr>
<tr>
<td><?= __('Attributes #') ?></td>
<td><?= $attribute_number ?></td>
</tr>
<tr>
<td><?= __('Objects #') ?></td>
<td><?= $object_number ?></td>
</tr>
<tr>
<td><?= __('Event Report #') ?></td>
<td><?= $event_report_number ?></td>
</tr>
<tr>
<td><?= __('Proposals #') ?></td>
<td><?= $proposal_number ?></td>
</tr>
<tr>
<td><?= __('Unique tags #') ?></td>
<td><?= $unique_tag_number ?></td>
</tr>
</tbody>
</table>
</div>
</div>
<?php endif; ?>
<?php if ($this->fetch('detailed-summary-full')) : ?>
<?= $this->fetch('detailed-summary-full'); ?>
<?php else : ?>
<div class="panel">
<div class="panel-header">
<?= __('Detailed summary') ?>
</div>
<div class="panel-body">
<?php if ($this->fetch('detailed-summary-mitre-attack')) : ?>
<?= $this->fetch('detailed-summary-mitre-attack'); ?>
<?php else : ?>
<?php if (!empty($mitre_attack_techniques)) : ?>
<h4><?= __('Mitre Att&ck techniques') ?></h4>
<ul>
<?php foreach ($mitre_attack_techniques as $technique => $tag) : ?>
<li>
<?php
$tag['Tag']['name'] = $technique;
echo $this->element('tag', ['tag' => $tag])
?>
</li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
<?php endif; ?>
<?php if ($this->fetch('detailed-summary-type')) : ?>
<?= $this->fetch('detailed-summary-type'); ?>
<?php else : ?>
<?php if (!empty($attribute_types)) : ?>
<h4><?= __('Top 10 Attribute types') ?></h4>
<ul>
<?php foreach ($attribute_types as $type => $amount) : ?>
<li><strong><?= h($type) ?></strong>: <?= $amount ?></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
<?php if (!empty($object_types)) : ?>
<h4><?= __('Top 10 MISP Object names') ?></h4>
<ul>
<?php foreach ($object_types as $name => $amount) : ?>
<li><strong><?= h($name) ?></strong>: <?= $amount ?></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
<?php if (!empty($all_event_report)) : ?>
<h4><?= __('All Event Reports') ?></h4>
<ul>
<?php foreach ($all_event_report as $report) : ?>
<li>
<a href="<?= sprintf('%s/eventReports/view/%s', $baseurl, h($report['uuid'])) ?>">
<?= sprintf('%s :: %s', h($report['event_info']), h($report['name'])); ?>
</a>
</li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
<?php endif; ?>
<?php if ($this->fetch('detailed-summary-tags')) : ?>
<?= $this->fetch('detailed-summary-tags'); ?>
<?php else : ?>
<h4><?= __('Top 10 Tags') ?></h4>
<ul>
<?php foreach ($all_tag_amount as $tag_name => $amount) : ?>
<li>
<span style="padding: 2px 9px; margin-right: 5px; border-radius: 9px; font-weight: bold; background-color: #999; color: #fff;">
<?= $amount ?>
</span>
<?= $this->element('tag', ['tag' => ['Tag' => ['name' => $tag_name, 'colour' => $tag_color_mapping[$tag_name]]]]) ?>
</li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
<?php if ($this->fetch('detailed-summary-events')) : ?>
<?= $this->fetch('detailed-summary-events'); ?>
<?php else : ?>
<?php if (!empty($events)) : ?>
<h4><?= __('Event list') ?></h4>
<table class="table table-condensed">
<thead>
<tr>
<th><?= __('Publish date') ?></th>
<th><?= __('Creator Org.') ?></th>
<th><?= __('Distribution') ?></th>
<th><?= __('State') ?></th>
<th><?= __('Threat Level') ?></th>
<?php foreach ($vars['additional_taxonomy_event_list'] as $taxonomy_name => $taxonomy_prefix) : ?>
<th><?= h($taxonomy_name) ?></th>
<?php endforeach; ?>
<?php if (!empty($vars['event_table_include_basescore'])) : ?>
<th><?= __('Decaying Base Score') ?></th>
<?php endif; ?>
<th><?= __('Event Info') ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($events as $event) : ?>
<?php
$workflowTag = findAndBuildTag($event['EventTag'], 'workflow:', $this);
$analysisHtml = !empty($workflowTag) ? $workflowTag : '';
$tlpTag = findAndBuildTag($event['EventTag'], 'tlp:', $this);
$tlpHtml = !empty($tlpTag) ? $tlpTag : '';
?>
<tr>
<td><?= DateTime::createFromFormat('U', h($event['Event']['publish_timestamp']))->format('Y-m-d') ?></td>
<td><?= h($event['Orgc']['name']) ?></td>
<td>
<<?= !empty($tlpHtml) ? 'small' : 'span' ?>><?= h($distributionLevels[$event['Event']['distribution']]) ?></<?= !empty($tlpHtml) ? 'small' : 'span' ?>>
<span style="margin-left: 3px;"><?= $tlpHtml ?></span>
</td>
<td>
<<?= !empty($analysisHtml) ? 'small' : 'span' ?>><?= h($analysisLevels[$event['Event']['analysis']]) ?></<?= !empty($analysisHtml) ? 'small' : 'span' ?>>
<span style="margin-left: 3px;"><?= $analysisHtml ?></span>
</td>
<td><?= h($event['ThreatLevel']['name']); ?></td>
<?php foreach ($vars['additional_taxonomy_event_list'] as $taxonomy_name => $taxonomy_prefix) : ?>
<td><?= findAndBuildTag($event['EventTag'], $taxonomy_prefix, $this) ?></td>
<?php endforeach; ?>
<?php if (!empty($vars['event_table_include_basescore'])) : ?>
<td>
<?php if (isset($event['event_base_score'])) : ?>
<table class="table-xcondensed no-border">
<?php foreach ($event['event_base_score'] as $bs) : ?>
<tr>
<td style="line-height: 14px;"><i class="no-overflow" style="max-width: 12em;" title="<?= h($bs['DecayingModel']['name']); ?>"><?= h($bs['DecayingModel']['name']); ?>:</i></td>
<td style="line-height: 14px;"><b style="color: <?= !empty($bs['decayed']) ? '#b94a48' : '#468847' ?>;"><?= round($bs['base_score'], 2) ?></b></td>
</tr>
<?php endforeach; ?>
</table>
<?php else : ?>
&nbsp;
<?php endif; ?>
</td>
<?php endif; ?>
<td><a href="<?= sprintf('%s/events/view/%s', $baseurl, h($event['Event']['uuid'])) ?>"><?= h($event['Event']['info']) ?></a></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php else : ?>
<p><?= __('No events.') ?></p>
<?php endif; ?>
<?php endif; ?>
</div>
</div>
<?php endif; // detailed-summary-full
?>
<?php if ($this->fetch('trending-summary')) : ?>
<?= $this->fetch('trending-summary'); ?>
<?php else : ?>
<div class="panel">
<div class="panel-header">
<?= __('Tag trendings') ?>
</div>
<div class="panel-body">
<?= $trending_summary; ?>
</div>
</div>
<?php endif; ?>
<?php if ($this->fetch('aggregated-context')) : ?>
<?= $this->fetch('aggregated-context'); ?>
<?php else : ?>
<div class="panel">
<div class="panel-header">
<?= __('Context summary') ?>
</div>
<div class="panel-body">
<?= $aggregated_context; ?>
</div>
</div>
<?php endif; ?>
<?= $this->fetch('content'); ?>
<style>
.mw-50 {
max-width: 50%;
}
.panel {
border: 1px solid #ccc;
border-radius: 3px;
margin-bottom: 20px;
box-shadow: 0px 5px 10px 0 #00000033;
}
.panel-header {
border-bottom: 1px solid #ccc;
padding: 4px 10px;
background-color: #cccccc22;
font-weight: bold;
font-size: 25px;
clear: both;
line-height: 40px;
}
.panel-body {
padding: 15px;
position: relative;
}
.panel h4 {
margin-top: 0.75em;
}
.panel h4::before {
content: '▲';
transform: rotate(90deg);
display: inline-block;
margin-right: 0.25em;
color: #ccc;
text-shadow: 0px 0px #999;
}
.tag {
display: inline-block;
padding: 2px 4px;
font-size: 12px;
font-weight: bold;
line-height: 14px;
margin-right: 2px;
border-radius: 3px;
}
.no-overflow {
display: inline-block;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden
}
.table {
width: 100%;
margin-bottom: 20px;
}
.table.table-condensed td,
.table.table-condensed th {
padding: 4px 5px;
}
.table-xcondensed td,
.table-xcondensed th {
padding: 0px 2px !important;
}
.table th,
.table td {
padding: 8px;
line-height: 20px;
text-align: left;
vertical-align: top;
border-top: 1px solid #dddddd;
}
.table thead th {
vertical-align: bottom;
}
.table caption+thead tr:first-child th,
.table caption+thead tr:first-child td,
.table colgroup+thead tr:first-child th,
.table colgroup+thead tr:first-child td,
.table thead:first-child tr:first-child th,
.table thead:first-child tr:first-child td {
border-top: 0;
}
table.no-border td {
border-top: 0;
}
.table.no-border tbody+tbody {
border-top: 0;
}
</style>

View File

@ -0,0 +1 @@
daily

View File

@ -0,0 +1 @@
monthly

View File

@ -0,0 +1 @@
weekly

View File

@ -392,6 +392,17 @@
echo sprintf('<h3>%s</h3>', $data['title']);
echo sprintf('<p>%s</p>', implode(" ", $data['description']));
echo sprintf("<pre>%s</pre>", implode("\n", $data['url']));
?>
<h3><?= __('Setting up the periodic notification scheduled task.') ?></h3>
<p><?= __('The current recommendation to schedule periodic tasks in MISP is to use CRON jobs.') ?></p>
<p><?= __('The %s functionality takes care of sending daily, weekly and monthly summaries. As this process is resource intensive, it\'s heavily recommended to run it once per day. But more importantly, spamming recipients\' mailbox will undermine their trust and willingness to participate in the community. As such, in order for site-admins to keep running a thriving community, they are advised to make sure their system configuration and behaviours stays inclusive, open, collaborative and enjoyable to all members.', sprintf('<code>%s</code>', 'sendPeriodicSummaryToUsers')) ?></p>
<p><?= __('The command below is a recommendation on how the CRON entry should look like. This entry executes the command each day at 06:00 AM. Daily mails will be sent. Weekly mails are sent on Mondays. Monthly mails are sent on the 1st of each month.') ?></p>
<pre>0 6 * * * /var/www/MISP/app/Console/cake Server sendPeriodicSummaryToUsers >/dev/null 2>&1 # Send daily, weekly and monthly summary when appropriate</pre>
<ul>
<li><?= __('Users can visualize the output that would be generated by accessing %s.', sprintf('<a href="%s">%s</a>', $baseurl . '/users/viewPeriodicSummary/daily', __('their periodic summary'))) ?></li>
<li><?= __('Users can edit their setting by accessing %s.', sprintf('<a href="%s">%s</a>', $baseurl . '/users/notificationSettings', __('their periodic notification settings'))) ?></li>
</ul>
<?php
$data = array(
'title' => __('Administering the background workers via the API.'),
'description' => array(

View File

@ -5,6 +5,9 @@
$md[] = sprintf('## %s', __('Tags and Taxonomies'));
$mdTags = [];
foreach ($tags as $namespace => $entries) {
if (empty($entries[0]['Taxonomy'])) {
continue;
}
$mdTags[] = sprintf('#### %s', h($namespace));
if (!empty($entries[0]['Taxonomy']['description'])) {
$mdTags[] = sprintf('*%s*', h($entries[0]['Taxonomy']['description']));

View File

@ -1,75 +1,84 @@
<div>
<h1><?= __('Aggregated context data') ?></h1>
<h2><?= __('Tags and Taxonomies') ?></h2>
<div>
<?php
$htmlTags = '';
foreach ($tags as $namespace => $entries) {
$htmlTags .= sprintf('<div><h4>%s</h4></div>', h($namespace));
if (!empty($entries[0]['Taxonomy']['description'])) {
$htmlTags .= sprintf('<div><i>%s</i></div>', h($entries[0]['Taxonomy']['description']));
}
$htmlTags .= '<ul>';
foreach ($entries as $entry) {
$taxonomyInfo = '<ul>';
if (!empty($entry['TaxonomyPredicate'])) {
$taxonomyInfo .= sprintf(
'<li><strong>%s</strong>: %s</li>',
h($entry['TaxonomyPredicate']['value']),
h($entry['TaxonomyPredicate']['expanded'])
<?php if (!empty($tags)): ?>
<h2><?= __('Tags and Taxonomies') ?></h2>
<div>
<?php
$htmlTags = '';
$customTagHtml = '';
foreach ($tags as $namespace => $entries) {
if (empty($entries[0]['Taxonomy'])) {
continue;
}
$htmlTags .= sprintf('<div><h4><code>%s</code></h4></div>', h($namespace));
if (!empty($entries[0]['Taxonomy']['description'])) {
$htmlTags .= sprintf('<div><i>%s</i></div>', h($entries[0]['Taxonomy']['description']));
}
$htmlTags .= '<ul>';
foreach ($entries as $entry) {
$taxonomyInfo = '<ul>';
if (!empty($entry['TaxonomyPredicate'])) {
$taxonomyInfo .= sprintf(
'<li><strong>%s</strong>: %s</li>',
h($entry['TaxonomyPredicate']['value']),
h($entry['TaxonomyPredicate']['expanded'])
);
}
if (!empty($entry['TaxonomyEntry'])) {
$taxonomyInfo .= sprintf(
'<li><strong>%s</strong>: %s</li>',
h($entry['TaxonomyEntry']['value']),
h($entry['TaxonomyEntry']['expanded'])
);
}
$taxonomyInfo .= '</ul>';
$htmlTags .= sprintf(
'<li>%s</li>%s',
$this->element('tag', ['tag' => $entry]),
$taxonomyInfo
);
}
if (!empty($entry['TaxonomyEntry'])) {
$taxonomyInfo .= sprintf(
'<li><strong>%s</strong>: %s</li>',
h($entry['TaxonomyEntry']['value']),
h($entry['TaxonomyEntry']['expanded'])
);
}
$taxonomyInfo .= '</ul>';
$htmlTags .= sprintf(
'<li>%s</li>%s',
$this->element('tag', ['tag' => $entry]),
$taxonomyInfo
);
$htmlTags .= '</ul>';
}
$htmlTags .= '</ul>';
}
echo $htmlTags;
?>
</div>
echo $htmlTags;
?>
</div>
<?php endif; ?>
<h2><?= __('Galaxy Clusters') ?></h2>
<div>
<?php
$htmlClusters = '';
foreach ($clusters as $tagname => $entries) {
$htmlClusters .= sprintf(
'<div><h4>%s %s</h4></div>',
sprintf('<i class="%s"></i>', $this->FontAwesome->getClass($entries[0]['Galaxy']['icon'])),
h($entries[0]['Galaxy']['name'])
);
if (!empty($entries[0]['Galaxy']['description'])) {
$htmlClusters .= sprintf('<div><i>%s</i></div>', h($entries[0]['Galaxy']['description']));
}
$htmlClusters .= '<ul>';
foreach ($entries as $cluster) {
<?php if (!empty($clusters)): ?>
<h2><?= __('Galaxy Clusters') ?></h2>
<div>
<?php
$htmlClusters = '';
foreach ($clusters as $tagname => $entries) {
$htmlClusters .= sprintf(
'<li><strong><a href="%s" target="_blank">%s</a></strong></li> %s',
$baseurl . '/galaxy_clusters/view/' . h($cluster['GalaxyCluster']['id']),
h($cluster['GalaxyCluster']['value']),
strlen(h($cluster['GalaxyCluster']['description'])) > 300 ?
(substr(h($cluster['GalaxyCluster']['description']), 0, 300) . '...') : h($cluster['GalaxyCluster']['description'])
'<div><h4>%s %s</h4></div>',
sprintf('<i class="%s"></i>', $this->FontAwesome->getClass($entries[0]['Galaxy']['icon'])),
h($entries[0]['Galaxy']['name'])
);
if (!empty($entries[0]['Galaxy']['description'])) {
$htmlClusters .= sprintf('<div><i>%s</i></div>', h($entries[0]['Galaxy']['description']));
}
$htmlClusters .= '<ul>';
foreach ($entries as $cluster) {
$htmlClusters .= sprintf(
'<li><strong><a href="%s" target="_blank">%s</a></strong></li> %s',
$baseurl . '/galaxy_clusters/view/' . h($cluster['GalaxyCluster']['id']),
h($cluster['GalaxyCluster']['value']),
strlen(h($cluster['GalaxyCluster']['description'])) > 300 ?
(substr(h($cluster['GalaxyCluster']['description']), 0, 300) . '...') : h($cluster['GalaxyCluster']['description'])
);
}
$htmlClusters .= '</ul>';
}
$htmlClusters .= '</ul>';
}
echo $htmlClusters;
?>
</div>
echo $htmlClusters;
?>
</div>
<?php endif; ?>
<h2><?= __('Mitre ATT&CK Matrix') ?></h2>
<div id="attackmatrix_div" style="position: relative; border: solid 1px;" class="statistics_attack_matrix">
<?= $this->element('view_galaxy_matrix', $attackData); ?>
</div>
<?php if (!empty($attackData)): ?>
<h2><?= __('Mitre ATT&CK Matrix') ?></h2>
<div style="position: relative;" class="statistics_attack_matrix">
<?= $this->element('view_galaxy_matrix', $attackData); ?>
</div>
<?php endif; ?>
</div>

View File

@ -96,6 +96,12 @@
echo $this->Form->input('disabled', array('type' => 'checkbox', 'label' => __('Immediately disable this user account')));
echo '</div>';
?>
<h5><?= __('Subscribe to the following notification periods:') ?></h5>
<?php
echo $this->Form->input('notification_daily', array('label' => __('Daily notifications'), 'type' => 'checkbox'));
echo $this->Form->input('notification_weekly', array('label' => __('Weekly notifications'), 'type' => 'checkbox'));
echo $this->Form->input('notification_monthly', array('label' => __('Monthly notifications'), 'type' => 'checkbox'));
?>
</fieldset>
<div style="border-bottom: 1px solid #e5e5e5;width:100%;">&nbsp;</div>
<div class="clear" style="margin-top:10px;">

View File

@ -154,6 +154,20 @@
'data_path' => 'User.contactalert',
'colors' => true,
),
array(
'name' => __('Periodic notif.'),
'element' => 'custom',
'class' => 'short',
'function' => function (array $user) use ($periodic_notifications) {
$period_subscriptions = [];
foreach ($periodic_notifications as $period) {
if (!empty($user['User'][$period])) {
$period_subscriptions[] = substr($period, 13, 1);
}
}
return implode('/', $period_subscriptions);
}
),
array(
'name' => __('PGP Key'),
'element' => 'boolean',

View File

@ -34,6 +34,12 @@
echo $this->Form->input('contactalert', array('label' => __('Receive email alerts from "Contact reporter" requests'), 'type' => 'checkbox'));
echo '</div>';
?>
<h5><?= __('Subscribe to the following notification periods:') ?></h5>
<?php
echo $this->Form->input('notification_daily', array('label' => __('Daily notifications'), 'type' => 'checkbox'));
echo $this->Form->input('notification_weekly', array('label' => __('Weekly notifications'), 'type' => 'checkbox'));
echo $this->Form->input('notification_monthly', array('label' => __('Monthly notifications'), 'type' => 'checkbox'));
?>
</fieldset>
<div style="border-bottom: 1px solid #e5e5e5;width:100%;">&nbsp;</div>
<div class="clear" style="margin-top:10px;">

View File

@ -0,0 +1,93 @@
<?php
echo $this->element('genericElements/Form/genericForm', [
'data' => [
'title' => __('Notification settings'),
'fields' => [
[
'field' => 'notification_daily',
'label' => __('Subscribe to daily notifications'),
'default' => 0,
'type' => 'checkbox'
],
[
'field' => 'notification_weekly',
'label' => __('Subscribe to weekly notifications'),
'default' => 0,
'type' => 'checkbox'
],
[
'field' => 'notification_monthly',
'label' => __('Subscribe to monthly notifications'),
'default' => 0,
'type' => 'checkbox'
],
sprintf('<h4>%s</h4>', __('Notification filters')),
[
'field' => 'periodic_settings.orgc_id',
'label' => __('Creator organisation'),
'class' => 'span6',
'options' => $orgs,
'type' => 'dropdown',
'multiple' => true,
'picker' => true,
],
[
'field' => 'periodic_settings.distribution',
'label' => __('Distribution level'),
'class' => 'input span6',
'options' => [-1 => ' '] + $distributionLevels,
'type' => 'dropdown'
],
[
'field' => 'periodic_settings.sharing_group_id',
'label' => __('Sharing Group'),
'class' => 'input span6',
'options' => $sharingGroups,
'type' => 'dropdown',
'multiple' => true,
],
[
'field' => 'periodic_settings.event_info',
'label' => __('Event info'),
'class' => 'input span6',
'placeholder' => 'Phishing URL',
],
[
'field' => 'periodic_settings.tags',
'label' => __('Event Tags'),
'class' => 'span6',
'type' => 'tagsPicker',
'placeholder' => '["tlp:red"]',
],
sprintf('<h4>%s</h4>', __('Notification filters')),
[
'field' => 'periodic_settings.trending_for_tags',
'label' => __('Generate trends for tag namespaces'),
'class' => 'span6',
'type' => 'textarea',
'placeholder' => '["misp-galaxy:mitre-attack-pattern", "admiralty-scale"]',
],
],
'submit' => [
'action' => $this->request->params['action'],
'ajaxSubmit' => 'submitGenericFormInPlace();'
]
]
]);
if (!$ajax) {
echo $this->element('/genericElements/SideMenu/side_menu', array('menuList' => 'globalActions', 'menuItem' => 'notification_settings'));
}
?>
<script>
$(document).ready(function() {
checkSharingGroup('periodic_settings');
$('#periodic_settingsDistribution').change(function() {
checkSharingGroup('periodic_settings');
});
})
</script>
<style>
</style>

View File

@ -1,4 +1,13 @@
<?php
$periodic_notification_settings_html = '<table>';
foreach ($periodic_notifications as $periodic_notification) {
$active_html = $this->element('genericElements/IndexTable/Fields/boolean', [
'row' => ['active' => !empty($user['User'][$periodic_notification])],
'field' => ['data_path' => 'active', 'colors' => true, ],
]);
$periodic_notification_settings_html .= sprintf('<tr><td>%s</td><td>%s<td></tr>', Inflector::humanize($periodic_notification), $active_html);
}
$periodic_notification_settings_html .= '</table>';
$table_data = array();
$table_data[] = array('key' => __('ID'), 'value' => $user['User']['id']);
$table_data[] = array(
@ -16,6 +25,7 @@
);
$table_data[] = array('key' => __('Role'), 'html' => $this->Html->link($user['Role']['name'], array('controller' => 'roles', 'action' => 'view', $user['Role']['id'])));
$table_data[] = array('key' => __('Event alert enabled'), 'boolean' => $user['User']['autoalert']);
$table_data[] = ['key' => __('Periodic Notifications'), 'html' => $periodic_notification_settings_html];
$table_data[] = array('key' => __('Contact alert enabled'), 'boolean' => $user['User']['contactalert']);
if (!$admin_view && !$user['Role']['perm_auth']) {

View File

@ -0,0 +1,26 @@
<div class="index">
<div class="btn-group">
<a class="btn <?= $period == 'daily' ? 'btn-primary' : 'btn-inverse' ?>" href="<?= $baseurl . '/users/viewPeriodicSummary/daily' ?>"><?= __('Daily') ?></a>
<a class="btn <?= $period == 'weekly' ? 'btn-primary' : 'btn-inverse' ?>" href="<?= $baseurl . '/users/viewPeriodicSummary/weekly' ?>"><?= __('Weekly') ?></a>
<a class="btn <?= $period == 'monthly' ? 'btn-primary' : 'btn-inverse' ?>" href="<?= $baseurl . '/users/viewPeriodicSummary/monthly' ?>"><?= __('Monthly') ?></a>
</div>
<h2><?= __('MISP %s summary', h($period)); ?></h2>
<button type="button" class="btn btn-inverse" data-toggle="collapse" data-target="#summary-filters">
<?= __('Show settings used to generate the summary') ?>
</button>
<div id="summary-filters" class="collapse">
<pre>
<?= JsonTool::encode($periodic_settings, true) ?>
</pre>
</div>
<div class="report-container" style="margin-top: 2em;">
<?= $summary; ?>
</div>
</div>
<?php
echo $this->element('/genericElements/SideMenu/side_menu', array('menuList' => 'event-collection', 'menuItem' => 'viewPeriodicSummary'));
?>

View File

@ -13,7 +13,7 @@
'sort' => 'name',
'data_path' => 'name',
'element' => 'custom',
'class' => 'bold short',
'class' => 'bold shortish',
'function' => function ($row) use ($baseurl) {
if (!empty($row['icon'])) {
return sprintf('<i class="fa-fw %s"></i> %s', $this->FontAwesome->getClass($row['icon']), h($row['name']));

View File

@ -5,6 +5,7 @@ if ($data['module_type'] == 'trigger') {
['element' => 'Workflows/executionPath', 'element_params' => ['workflow' => $data['Workflow']]],
];
}
$append[] = ['element' => 'Workflows/execute_module', 'element_params' => ['module' => $data]];
echo $this->element(
'genericElements/SingleViews/single_view',
[
@ -54,6 +55,7 @@ echo $this->element(
],
[
'key' => __('Module Parameters'),
'class' => 'restrict-height',
'type' => 'json',
'path' => 'params',
],

@ -1 +1 @@
Subproject commit 9b9c8389616b516a2d5cbe41a4407b2e9d7f24d3
Subproject commit 3cf9307b24232b209545261c7cbf075ce4d92a66

@ -1 +1 @@
Subproject commit aa251b6a4006372e66f3bf5e946111f6d5f6a2d6
Subproject commit 7d379245b7a62831cc4b5d32e73e1f0d923f2624

@ -1 +1 @@
Subproject commit faee7c9dff59b61942f53aebee44ee0c59d8ca4f
Subproject commit 6884002f4880278eb4c91a8c3874a5a68d9e2eee

View File

@ -995,6 +995,14 @@ a.proposal_link_red:hover {
border-radius: 3px 0 3px 0;
}
.report-container {
margin: 0 0;
justify-content: center;
padding: 10px 15px;
overflow-y: auto;
box-shadow: 0 2px 6px 6px #eee;
}
.modal-body-long {
max-height: 600px;
}

View File

@ -6198,6 +6198,8 @@ components:
- $ref: "#/components/schemas/OrganisationName"
tags:
$ref: "#/components/schemas/TagsRestSearchFilter"
event_tags:
$ref: "#/components/schemas/TagsRestSearchFilter"
searchall:
description: "Search events by matching any tag names, event descriptions, attribute values or attribute comments"
type: string

View File

@ -1213,6 +1213,13 @@ function deleteSelectedNode() {
editor.removeNodeId(getSelectedNodeID())
}
function getNodeFromContainedHtml(htmlNode) {
var $drawflowNode = $(htmlNode).closest('.drawflow-node')
var nodeStringId = $drawflowNode.attr('id')
var nodeId = nodeStringId ? parseInt(nodeStringId.split('-')[1]) : null
return nodeId !== null ? editor.getNodeFromId(nodeId) : null
}
function deleteSelectedNodes(fromDelKey) {
selection.getSelection().forEach(function(node) {
if (fromDelKey && getSelectedNodeID() !== null && getSelectedNodeID() == node.id) {
@ -1335,6 +1342,7 @@ function genNodeParamHtml(node, forNode = true) {
function afterNodeDrawCallback() {
var $nodes = $drawflow.find('.drawflow-node')
$nodes.find('.start-chosen').chosen()
toggleDisplayOnFields()
}
function afterModalShowCallback() {
@ -1359,6 +1367,34 @@ function afterModalShowCallback() {
})
}
function toggleDisplayOnFields() {
var $nodes = $drawflow.find('.drawflow-node')
$nodes.find('div.node-param-container.display-on').each(function() {
var $container = $(this)
var node = getNodeFromContainedHtml($container)
var param_id = $container.attr('param-id')
var node_param_config = node.data.module_data.params.filter(function(param_config) {
return param_config.id == param_id
})
var showContainer = false
if (node_param_config) {
node_param_config = node_param_config[0]
Object.keys(node_param_config.display_on).forEach(function(target_param_id) {
var target_param_values = node_param_config.display_on[target_param_id]
var node_param_value = node.data.indexed_params[target_param_id]
if (target_param_values == node_param_value) {
showContainer = true
}
});
}
if (showContainer) {
$container.show()
} else {
$container.hide()
}
})
}
function genParameterWarning(options) {
var text = '', text_short = ''
if (options.is_invalid) {
@ -1381,6 +1417,11 @@ function genParameterWarning(options) {
function genSelect(options, forNode = true) {
var $container = $('<div>')
.addClass('node-param-container')
.attr('param-id', options.id)
if (options.display_on) {
$container.addClass('display-on')
}
var $label = $('<label>')
.css({
marginLeft: '0.25em',
@ -1450,6 +1491,8 @@ function genPicker(options, forNode = true) {
function genInput(options, isTextArea, forNode = true) {
var $container = $('<div>')
.addClass('node-param-container')
.data('param-id', options.id)
var $label = $('<label>')
.css({
marginLeft: '0.25em',
@ -1513,6 +1556,8 @@ function genCheckbox(options, forNode = true) {
}
$label.append($input)
var $container = $('<div>')
.addClass('node-param-container')
.data('param-id', options.id)
.addClass('checkbox')
.append($label)
return $container
@ -1520,6 +1565,8 @@ function genCheckbox(options, forNode = true) {
function genRadio(options, forNode = true) {
var $container = $('<div>')
.addClass('node-param-container')
.data('param-id', options.id)
var $rootLabel = $('<label>')
.css({
marginLeft: '0.25em',
@ -1577,6 +1624,7 @@ function handleInputChange(changed) {
var node = getNodeFromNodeInput($input)
var node_data = setParamValueForInput($input, node.data)
editor.updateNodeDataFromId(node.id, node_data)
toggleDisplayOnFields()
invalidateContentCache()
}
@ -1585,6 +1633,7 @@ function handleSelectChange(changed) {
var node = getNodeFromNodeInput($input)
var node_data = setParamValueForInput($input, node.data)
editor.updateNodeDataFromId(node.id, node_data)
toggleDisplayOnFields()
invalidateContentCache()
}

View File

@ -8190,6 +8190,39 @@
"column_type": "int(11)",
"column_default": "0",
"extra": ""
},
{
"column_name": "notification_daily",
"is_nullable": "NO",
"data_type": "tinyint",
"character_maximum_length": null,
"numeric_precision": "3",
"collation_name": null,
"column_type": "tinyint(1)",
"column_default": "0",
"extra": ""
},
{
"column_name": "notification_weekly",
"is_nullable": "NO",
"data_type": "tinyint",
"character_maximum_length": null,
"numeric_precision": "3",
"collation_name": null,
"column_type": "tinyint(1)",
"column_default": "0",
"extra": ""
},
{
"column_name": "notification_monthly",
"is_nullable": "NO",
"data_type": "tinyint",
"character_maximum_length": null,
"numeric_precision": "3",
"collation_name": null,
"column_type": "tinyint(1)",
"column_default": "0",
"extra": ""
}
],
"user_settings": [
@ -9116,5 +9149,5 @@
"uuid": false
}
},
"db_version": "96"
"db_version": "97"
}