Merge branch 'develop' into allow-enrich-objects

pull/9187/head
Luciano Righetti 2023-08-01 09:48:44 +02:00
commit 1461fea281
69 changed files with 2374 additions and 270 deletions

View File

@ -1509,9 +1509,17 @@ coreCAKE () {
${SUDO_WWW} ${RUN_PHP} -- ${CAKE} Admin setSetting "GnuPG.homedir" "${PATH_TO_MISP}/.gnupg"
${SUDO_WWW} ${RUN_PHP} -- ${CAKE} Admin setSetting "GnuPG.password" "${GPG_PASSPHRASE}"
${SUDO_WWW} ${RUN_PHP} -- ${CAKE} Admin setSetting "GnuPG.obscure_subject" true
${SUDO_WWW} ${RUN_PHP} -- ${CAKE} Admin setSetting "GnuPG.key_fetching_disabled" false
# FIXME: what if we have not gpg binary but a gpg2 one?
${SUDO_WWW} ${RUN_PHP} -- ${CAKE} Admin setSetting "GnuPG.binary" "$(which gpg)"
# LinOTP
${SUDO_WWW} ${RUN_PHP} -- ${CAKE} Admin setSetting "LinOTPAuth.enabled" false
${SUDO_WWW} ${RUN_PHP} -- ${CAKE} Admin setSetting "LinOTPAuth.baseUrl" "https://<your-linotp-baseUrl>"
${SUDO_WWW} ${RUN_PHP} -- ${CAKE} Admin setSetting "LinOTPAuth.realm" "lino"
${SUDO_WWW} ${RUN_PHP} -- ${CAKE} Admin setSetting "LinOTPAuth.verifyssl" true
${SUDO_WWW} ${RUN_PHP} -- ${CAKE} Admin setSetting "LinOTPAuth.mixedauth" false
# Enable installer org and tune some configurables
${SUDO_WWW} ${RUN_PHP} -- ${CAKE} Admin setSetting "MISP.host_org_id" 1
${SUDO_WWW} ${RUN_PHP} -- ${CAKE} Admin setSetting "MISP.email" "info@admin.test"
@ -1870,7 +1878,7 @@ mispmodules () {
modulesCAKE () {
# Enable Enrichment, set better timeouts
${SUDO_WWW} ${RUN_PHP} -- ${CAKE} Admin setSetting "Plugin.Enrichment_services_enable" true
${SUDO_WWW} ${RUN_PHP} -- ${CAKE} Admin setSetting "Plugin.Enrichment_hover_enable" true
${SUDO_WWW} ${RUN_PHP} -- ${CAKE} Admin setSetting "Plugin.Enrichment_hover_enable" false
${SUDO_WWW} ${RUN_PHP} -- ${CAKE} Admin setSetting "Plugin.Enrichment_hover_popover_only" false
${SUDO_WWW} ${RUN_PHP} -- ${CAKE} Admin setSetting "Plugin.Enrichment_hover_timeout" 150
${SUDO_WWW} ${RUN_PHP} -- ${CAKE} Admin setSetting "Plugin.Enrichment_timeout" 300
@ -2543,7 +2551,7 @@ apacheConfig_RHEL7 () {
#sudo sed -i "s/SetHandler/\#SetHandler/g" /etc/httpd/conf.d/misp.ssl.conf
sudo rm /etc/httpd/conf.d/ssl.conf
sudo chmod 644 /etc/httpd/conf.d/misp.ssl.conf
sudo sed -i '/Listen 80/a Listen 443' /etc/httpd/conf/httpd.conf
sudo sed -i '/Listen 443/!s/Listen 80/a Listen 443/' /etc/httpd/conf/httpd.conf
# If a valid SSL certificate is not already created for the server, create a self-signed certificate:
echo "The Common Name used below will be: ${OPENSSL_CN}"
@ -2591,7 +2599,7 @@ apacheConfig_RHEL8 () {
#sudo sed -i "s/SetHandler/\#SetHandler/g" /etc/httpd/conf.d/misp.ssl.conf
sudo rm /etc/httpd/conf.d/ssl.conf
sudo chmod 644 /etc/httpd/conf.d/misp.ssl.conf
sudo sed -i '/Listen 80/a Listen 443' /etc/httpd/conf/httpd.conf
sudo sed -i '/Listen 443/!s/Listen 80/a Listen 443/' /etc/httpd/conf/httpd.conf
# If a valid SSL certificate is not already created for the server, create a self-signed certificate:
echo "The Common Name used below will be: ${OPENSSL_CN}"

View File

@ -1,5 +1,5 @@
; Generated by RHash v1.4.2 on 2022-05-23 at 12:45.34
; Generated by RHash v1.4.2 on 2023-07-01 at 17:15.04
; Written by Kravchenko Aleksey (Akademgorodok) - http://rhash.sf.net/
;
; 160126 12:45.34 2022-05-23 INSTALL.sh
INSTALL.sh 4296D40B11B3002DF3FDFD69A508ED5ECACB8C13 D32E5A4B0F37F4C937CD4F85927E998D917BCBE89E4E0E864FFD7EA09E29ADEF BD093D8018C351E3D3722646E269C4B60E6DA19F42150338CE6FD72FEE293B8B89AA69D48A84B19D3EFDDAE25EC9E646 ECACC3071E130058C3DDECC86E1CBF27DD4F11389D10F43B14293B1915F7A24F02D0DA51E299706A38C00F2D2A7505B0FE46E33B705E53594383CE65461F2B08
; 160686 17:15.04 2023-07-01 INSTALL.sh
INSTALL.sh 9576C31EC5BD942E1C9B12413E6408E4623252F7 78B708FE1FC6B39BE081B9F05C6AA5E1478F8762CAF5A8A7671A12EBA4D3C1C5 27991471FB5788F42AF3BBF86FC80A95341AA17AE9487016EEC94961A48437172702EB8E2D6CB300387E87D9E8E0E3E5 C1C21FD491AEFD662C87C3EF62837D769E63E9CF2446B9BD607CCEF8AFD72528824A8F408C6892FD51109390104010EF90DA7F4828950A8671D2986A6B8E216F

View File

@ -1 +1 @@
4296d40b11b3002df3fdfd69a508ed5ecacb8c13 INSTALL.sh
9576c31ec5bd942e1c9b12413e6408e4623252f7 INSTALL.sh

View File

@ -1 +1 @@
d32e5a4b0f37f4c937cd4f85927e998d917bcbe89e4e0e864ffd7ea09e29adef INSTALL.sh
78b708fe1fc6b39be081b9f05c6aa5e1478f8762caf5a8a7671a12eba4d3c1c5 INSTALL.sh

View File

@ -1 +1 @@
bd093d8018c351e3d3722646e269c4b60e6da19f42150338ce6fd72fee293b8b89aa69d48a84b19d3efddae25ec9e646 INSTALL.sh
27991471fb5788f42af3bbf86fc80a95341aa17ae9487016eec94961a48437172702eb8e2d6cb300387e87d9e8e0e3e5 INSTALL.sh

View File

@ -1 +1 @@
ecacc3071e130058c3ddecc86e1cbf27dd4f11389d10f43b14293b1915f7a24f02d0da51e299706a38c00f2d2a7505b0fe46e33b705e53594383ce65461f2b08 INSTALL.sh
c1c21fd491aefd662c87c3ef62837d769e63e9cf2446b9bd607ccef8afd72528824a8f408c6892fd51109390104010ef90da7f4828950a8671d2986a6b8e216f INSTALL.sh

2
PyMISP

@ -1 +1 @@
Subproject commit 7d1d8b6f38f210b28934a206f9c1470542e9da7e
Subproject commit 94983c01ecced6086df28133a38a297111534142

View File

@ -1 +1 @@
{"major":2, "minor":4, "hotfix":172}
{"major":2, "minor":4, "hotfix":174}

View File

@ -112,6 +112,18 @@ class AdminShell extends AppShell
return $parser;
}
public function jobForgot()
{
if (empty($this->args[0])) {
die('Usage: ' . $this->Server->command_line_functions['console_admin_tasks']['data']['Forgot'] . PHP_EOL);
}
$email = $this->args[0];
$ip = empty($this->args[1]) ? null : $this->args[1];
$jobId = empty($this->args[2]) ? null : $this->args[2];
$this->User->forgot($email, $ip, $jobId);
}
public function jobGenerateCorrelation()
{
if (empty($this->args[0])) {

View File

@ -405,7 +405,17 @@ class EventShell extends AppShell
$jobId = $this->args[2];
$userId = $this->args[3];
$user = $this->getUser($userId);
$job = $this->Job->read(null, $jobId);
$job = $this->Job->find('first', [
'recursive' => -1,
'conditions' => [
'Job.id' => $jobId
]
]);
if (empty($job)) {
$log = ClassRegistry::init('Log');
$log->createLogEntry($user, 'publish', 'Event', $id, 'Event (' . $id . '): could not be published - valid job not found.', '');
return true;
}
$this->Event->Behaviors->unload('SysLogLogable.SysLogLogable');
$result = $this->Event->publish($id, $passAlong);
$job['Job']['progress'] = 100;

View File

@ -33,8 +33,8 @@ class AppController extends Controller
public $helpers = array('OrgImg', 'FontAwesome', 'UserName');
private $__queryVersion = '152';
public $pyMispVersion = '2.4.172';
private $__queryVersion = '153';
public $pyMispVersion = '2.4.174';
public $phpmin = '7.2';
public $phprec = '7.4';
public $phptoonew = '8.0';
@ -222,8 +222,10 @@ class AppController extends Controller
!$userLoggedIn &&
(
$controller !== 'users' ||
$action !== 'register' ||
empty(Configure::read('Security.allow_self_registration'))
(
($action !== 'register' || empty(Configure::read('Security.allow_self_registration'))) &&
(!in_array($action, ['forgot', 'password_reset']) || empty(Configure::read('Security.allow_password_forgotten')))
)
)
) {
// REST authentication
@ -314,6 +316,10 @@ class AppController extends Controller
if (!empty(Configure::read('Security.email_otp_enabled'))) {
$preAuthActions[] = 'email_otp';
}
if (!empty(Configure::read('Security.allow_password_forgotten'))) {
$preAuthActions[] = 'forgot';
$preAuthActions[] = 'password_reset';
}
if (!$this->_isControllerAction(['users' => $preAuthActions, 'servers' => ['cspReport']])) {
if ($isAjax) {
$response = $this->RestResponse->throwException(401, "Unauthorized");
@ -1112,7 +1118,7 @@ class AppController extends Controller
$user['User'] = $temp;
if ($user['User']) {
$this->User->updateLoginTimes($user['User']);
$this->Session->renew();
//$this->Session->renew();
$this->Session->write(AuthComponent::$sessionKey, $user['User']);
if (Configure::read('MISP.log_auth')) {
$this->Log = ClassRegistry::init('Log');

View File

@ -1539,6 +1539,7 @@ class AttributesController extends AppController
$user = $this->Auth->user();
$exception = null;
$filters = $this->__getSearchFilters($exception);
$this->set('passedArgsArray', ['results' => $continue]);
if ($this->request->is('post') || !empty($this->request->params['named']['tags'])) {
if ($filters === false) {
return $exception;

View File

@ -613,7 +613,7 @@ class ACLComponent extends Component
),
'sightings' => array(
'add' => array('perm_sighting'),
'restSearch' => array('perm_sighting'),
'restSearch' => array('*'),
'advanced' => array('perm_sighting'),
'delete' => ['AND' => ['perm_sighting', 'perm_modify_org']],
'index' => array('*'),
@ -748,6 +748,7 @@ class ACLComponent extends Component
'downloadTerms' => array('*'),
'edit' => array('self_management_enabled'),
'email_otp' => array('*'),
'forgot' => array('*'),
'otp' => array('*'),
'hotp' => array('*'),
'totp_new' => array('*'),
@ -760,6 +761,7 @@ class ACLComponent extends Component
'logout' => array('*'),
'logout401' => array('*'),
'notificationSettings' => ['*'],
'password_reset' => array('*'),
'register' => array('*'),
'registrations' => array(),
'resetAllSyncAuthKeys' => array(),

View File

@ -78,7 +78,7 @@ class ShadowAttributesController extends AppController
}
if (isset($shadow['proposal_to_delete']) && $shadow['proposal_to_delete']) {
$this->Attribute->delete($activeAttribute['Attribute']['id']);
$this->Attribute->deleteAttribute($activeAttribute['Attribute']['id'], $this->Auth->user(), false);
} else {
// Update the live attribute with the shadow data
$fieldsToUpdate = array('value1', 'value2', 'value', 'type', 'category', 'comment', 'to_ids', 'first_seen', 'last_seen');
@ -906,7 +906,24 @@ class ShadowAttributesController extends AppController
}
$params = array(
'conditions' => $conditions,
'fields' => array('ShadowAttribute.id', 'ShadowAttribute.old_id', 'ShadowAttribute.event_id', 'ShadowAttribute.type', 'ShadowAttribute.category', 'ShadowAttribute.uuid', 'ShadowAttribute.to_ids', 'ShadowAttribute.value', 'ShadowAttribute.comment', 'ShadowAttribute.org_id', 'ShadowAttribute.timestamp', 'ShadowAttribute.first_seen', 'ShadowAttribute.last_seen'),
'fields' => array(
'ShadowAttribute.id',
'ShadowAttribute.old_id',
'ShadowAttribute.event_id',
'ShadowAttribute.type',
'ShadowAttribute.category',
'ShadowAttribute.uuid',
'ShadowAttribute.to_ids',
'ShadowAttribute.value',
'ShadowAttribute.comment',
'ShadowAttribute.org_id',
'ShadowAttribute.timestamp',
'ShadowAttribute.first_seen',
'ShadowAttribute.last_seen',
'ShadowAttribute.deleted',
'ShadowAttribute.proposal_to_delete',
'ShadowAttribute.disable_correlation'
),
'contain' => array(
'Event' => array(
'fields' => array('id', 'org_id', 'info', 'orgc_id', 'uuid'),

View File

@ -66,7 +66,8 @@ class SightingsController extends AppController
$filters = !empty($this->request->data['filters']) ? $this->request->data['filters'] : false;
}
if (!$error) {
$result = $this->Sighting->saveSightings($id, $values, $timestamp, $this->Auth->user(), $type, $source, false, true, false, $filters);
$publish_sighting = !empty(Configure::read('Sightings_enable_realtime_publish'));
$result = $this->Sighting->saveSightings($id, $values, $timestamp, $this->Auth->user(), $type, $source, false, $publish_sighting, false, $filters);
}
if (!is_numeric($result)) {
$error = $result;

View File

@ -30,6 +30,10 @@ class UsersController extends AppController
// what pages are allowed for non-logged-in users
$allowedActions = array('login', 'logout', 'getGpgPublicKey', 'logout401', 'otp');
if (!empty(Configure::read('Security.allow_password_forgotten'))) {
$allowedActions[] = 'forgot';
$allowedActions[] = 'password_reset';
}
if(!empty(Configure::read('Security.email_otp_enabled'))) {
$allowedActions[] = 'email_otp';
}
@ -262,6 +266,77 @@ class UsersController extends AppController
$this->set('canFetchPgpKey', $this->__canFetchPgpKey());
}
private function __pw_change($user, $source, &$abortPost, $token = false)
{
if (!isset($this->request->data['User'])) {
$this->request->data = array('User' => $this->request->data);
}
if (Configure::read('Security.require_password_confirmation')) {
if (!empty($this->request->data['User']['current_password'])) {
$hashed = $this->User->verifyPassword($this->Auth->user('id'), $this->request->data['User']['current_password']);
if (!$hashed) {
$message = __('Invalid password. Please enter your current password to continue.');
if ($this->_isRest()) {
return $this->RestResponse->saveFailResponse('Users', $source, false, $message, $this->response->type());
}
$abortPost = true;
$this->Flash->error($message);
}
unset($this->request->data['User']['current_password']);
} else if (!$this->_isRest()) {
$message = __('Please enter your current password to continue.');
if ($this->_isRest()) {
return $this->RestResponse->saveFailResponse('Users', $source, false, $message, $this->response->type());
}
$abortPost = true;
$this->Flash->info($message);
}
}
$hashed = $this->User->verifyPassword($this->Auth->user('id'), $this->request->data['User']['password']);
if ($hashed) {
$message = __('Submitted new password cannot be the same as the current one');
$abortPost = true;
}
if (!$abortPost) {
// What fields should be saved (allowed to be saved)
$user['User']['change_pw'] = 0;
$user['User']['password'] = $this->request->data['User']['password'];
if ($this->_isRest()) {
$user['User']['confirm_password'] = $this->request->data['User']['password'];
} else {
$user['User']['confirm_password'] = $this->request->data['User']['confirm_password'];
}
$temp = $user['User']['password'];
// Save the data
if ($this->User->save($user)) {
if ($token) {
$this->User->purgeForgetToken($token);
}
$message = __('Password Changed.');
// log as System if the reset comes from an unauthed user using password_reset tokens
$logUser = empty($this->Auth->user()) ? 'SYSTEM' : $this->Auth->user();
$this->User->extralog($logUser, $source, null, null, $user);
if ($this->_isRest()) {
return $this->RestResponse->saveSuccessResponse('User', $source, false, $this->response->type(), $message);
}
$this->Flash->success($message);
$this->redirect(array('action' => 'view', $user['User']['id']));
} else {
$message = __('The password could not be updated. Make sure you meet the minimum password length / complexity requirements.');
if ($this->_isRest()) {
return $this->RestResponse->saveFailResponse('Users', $source, false, $message, $this->response->type());
}
$this->Flash->error($message);
}
} else {
if ($this->_isRest()) {
return $this->RestResponse->saveFailResponse('Users', $source, false, $message, $this->response->type());
} else {
$this->Flash->error($message);
}
}
}
public function change_pw()
{
$id = $this->Auth->user('id');
@ -270,69 +345,8 @@ class UsersController extends AppController
'recursive' => -1
));
if ($this->request->is('post') || $this->request->is('put')) {
if (!isset($this->request->data['User'])) {
$this->request->data = array('User' => $this->request->data);
}
$abortPost = false;
if (Configure::read('Security.require_password_confirmation')) {
if (!empty($this->request->data['User']['current_password'])) {
$hashed = $this->User->verifyPassword($this->Auth->user('id'), $this->request->data['User']['current_password']);
if (!$hashed) {
$message = __('Invalid password. Please enter your current password to continue.');
if ($this->_isRest()) {
return $this->RestResponse->saveFailResponse('Users', 'change_pw', false, $message, $this->response->type());
}
$abortPost = true;
$this->Flash->error($message);
}
unset($this->request->data['User']['current_password']);
} else if (!$this->_isRest()) {
$message = __('Please enter your current password to continue.');
if ($this->_isRest()) {
return $this->RestResponse->saveFailResponse('Users', 'change_pw', false, $message, $this->response->type());
}
$abortPost = true;
$this->Flash->info($message);
}
}
$hashed = $this->User->verifyPassword($this->Auth->user('id'), $this->request->data['User']['password']);
if ($hashed) {
$message = __('Submitted new password cannot be the same as the current one');
$abortPost = true;
}
if (!$abortPost) {
// What fields should be saved (allowed to be saved)
$user['User']['change_pw'] = 0;
$user['User']['password'] = $this->request->data['User']['password'];
if ($this->_isRest()) {
$user['User']['confirm_password'] = $this->request->data['User']['password'];
} else {
$user['User']['confirm_password'] = $this->request->data['User']['confirm_password'];
}
$temp = $user['User']['password'];
// Save the data
if ($this->User->save($user)) {
$message = __('Password Changed.');
$this->User->extralog($this->Auth->user(), "change_pw", null, null, $user);
if ($this->_isRest()) {
return $this->RestResponse->saveSuccessResponse('User', 'change_pw', false, $this->response->type(), $message);
}
$this->Flash->success($message);
$this->redirect(array('action' => 'view', $id));
} else {
$message = __('The password could not be updated. Make sure you meet the minimum password length / complexity requirements.');
if ($this->_isRest()) {
return $this->RestResponse->saveFailResponse('Users', 'change_pw', false, $message, $this->response->type());
}
$this->Flash->error($message);
}
} else {
if ($this->_isRest()) {
return $this->RestResponse->saveFailResponse('Users', 'change_pw', false, $message, $this->response->type());
} else {
$this->Flash->error($message);
}
}
return $this->__pw_change($user, 'change_pw', $abortPost);
}
if ($this->_isRest()) {
return $this->RestResponse->describe('Users', 'change_pw', false, $this->response->type());
@ -1369,6 +1383,7 @@ class UsersController extends AppController
unset($user['User']['password']);
$user['User']['action'] = 'logout';
$this->User->save($user['User'], true, array('id'));
$this->Session->write('otp_secret', null);
$this->redirect($this->Auth->logout());
}
@ -1820,7 +1835,7 @@ class UsersController extends AppController
public function totp_new()
{
if (Configure::read('LinOTPAuth.enabled')) {
$this->Flash->error(__("LinOTP is enabled for this instance. Build-in TOTP should not be used."));
$this->Flash->error(__("LinOTP is enabled for this instance. Built-in TOTP should not be used."));
$this->redirect($this->referer());
}
if (!class_exists('\OTPHP\TOTP') || !class_exists('\BaconQrCode\Writer')) {
@ -1841,17 +1856,23 @@ class UsersController extends AppController
}
// do not allow this page to be accessed if the current already has a TOTP. Just redirect to the users details page with a Flash->error()
if ($user['User']['totp']) {
$this->Flash->error(__("Your account already has an TOTP. Please contact your organisational administrator to change or delete it."));
$this->Flash->error(__("Your account already has a TOTP. Please contact your organisational administrator to change or delete it."));
$this->redirect($this->referer());
}
$secret = $this->Session->read('otp_secret'); // Reload secret from session.
if ($secret) {
$totp = \OTPHP\TOTP::create($secret);
} else {
if ($this->request->is('get')) {
$totp = \OTPHP\TOTP::create();
$secret = $totp->getSecret();
$this->Session->write('otp_secret', $secret); // Store in session, this is to keep the same QR code even if the page refreshes.
$this->Session->write('otp_secret', $secret); // Store in session, we want to create a new secret each time the totp_new() function is queried via a GET (this will not impede incorrect confirmation attempty)
} else {
$secret = $this->Session->read('otp_secret'); // Reload secret from session.
if ($secret) {
$totp = \OTPHP\TOTP::create($secret);
} else {
$totp = \OTPHP\TOTP::create();
$secret = $totp->getSecret();
$this->Session->write('otp_secret', $secret); // Store in session, we want to keep reusing the same QR code until the user correctly enters the generated key on their authenticator
}
}
if ($this->request->is('post') && isset($this->request->data['User']['otp'])) {
if ($totp->verify(trim($this->request->data['User']['otp']))) {
@ -3084,4 +3105,69 @@ class UsersController extends AppController
# To use this, set Plugin.CustomAuth_custom_logout to /users/logout401
$this->response->statusCode(401);
}
public function forgot()
{
if (empty(Configure::read('Security.allow_password_forgotten'))) {
$this->Flash->error(__('This feature is disabled.'));
$this->redirect('/');
}
if (!empty($this->Auth->user()) && !$this->_isRest()) {
$this->Flash->info(__('You are already logged in, no need to ask for a password reset. Log out first.'));
$this->redirect('/');
}
if ($this->request->is('post')) {
if (empty($this->request->data['User'])) {
$this->request->data = ['User' => $this->request->data];
}
if (empty($this->request->data['User']['email'])) {
throw new MethodNotAllowedException(__('No email provided, cannot generate password reset message.'));
}
$user = [
'id' => 0,
'email' => 'SYSTEM',
'Organisation' => [
'name' => 'SYSTEM'
]
];
$this->loadModel('Log');
$this->Log->createLogEntry($user, 'forgot', 'User', 0, 'Password reset requested for: ' . $this->request->data['User']['email']);
$this->User->forgotRouter($this->request->data['User']['email'], $this->_remoteIp());
$message = __('Password reset request submitted. If a valid user is found, you should receive an e-mail with a temporary reset link momentarily. Please be advised that this link is only valid for 10 minutes.');
if ($this->_isRest()) {
return $this->RestResponse->saveSuccessResponse('User', 'forgot', false, $this->response->type(), $message);
}
$this->Flash->info($message);
$this->redirect('/');
}
}
public function password_reset($token)
{
if (empty(Configure::read('Security.allow_password_forgotten'))) {
$this->Flash->error(__('This feature is disabled.'));
$this->redirect('/');
}
$this->loadModel('Server');
$this->set('complexity', !empty(Configure::read('Security.password_policy_complexity')) ? Configure::read('Security.password_policy_complexity') : $this->Server->serverSettings['Security']['password_policy_complexity']['value']);
$this->set('length', !empty(Configure::read('Security.password_policy_length')) ? Configure::read('Security.password_policy_length') : $this->Server->serverSettings['Security']['password_policy_length']['value']);
if (!empty($this->Auth->user()) && !$this->_isRest()) {
$this->redirect('/');
}
$user = $this->User->fetchForgottenPasswordUser($token);
if (empty($user)) {
$message = __('Invalid token, or password request token already expired.');
if ($this->_isRest()) {
throw new MethodNotAllowedException($message);
} else {
$this->Flash->error($message);
$this->redirect('/');
}
}
if ($this->request->is('post') || $this->request->is('put')) {
$abortPost = false;
return $this->__pw_change(['User' => $user], 'password_reset', $abortPost, $token);
}
}
}

View File

@ -0,0 +1,121 @@
<?php
/**
* Org Events widget which reportes the number of events created monthly by each local organizations
*
*/
class OrgEventsWidget
{
public $title = 'Org Events';
public $render = 'MultiLineChart';
public $width = 8;
public $height = 6;
public $description = 'A graph to show the monthly number of events per organisation';
public $cacheLifetime = 10;
public $autoRefreshDelay = false;
public $params = array (
'blocklist_orgs' => 'A list of organisation names to filter out',
'months' => 'Number of past months to consider for the graph',
'logarithmic' => 'Visualize data on logarithmic scale'
);
public $placeholder =
'{
"blocklist_orgs": ["Orgs to filter"],
"months": "6",
"logarithmic": "true"
}';
/*
* Target_month must be from 1 to 12
* Target year must be 4 digits
*/
private function org_events_count($user, $org, $target_month, $target_year) {
$events_count = 0;
$start_date = $target_year.'-'.$target_month.'-01';
if($target_month == 12) {
$end_date = ($target_year+1).'-01-01';
} else {
$end_date = $target_year.'-'.($target_month+1).'-01';
}
$conditions = array('Event.orgc_id' => $org['Organisation']['id'], 'Event.date >=' => $start_date, 'Event.date <' => $end_date);
//This is required to enforce the ACL (not pull directly from the DB)
$eventIds = $this->Event->fetchSimpleEventIds($user, array('conditions' => $conditions));
if(!empty($eventIds)) {
$params = array('Event.id' => $eventIds);
$events = $this->Event->find('all', array('conditions' => array('AND' => $params)));
foreach($events as $event) {
$events_count+= 1;
}
}
return $events_count;
}
private function filter_ghost_orgs(&$data, $orgs){
foreach ($data['data'] as &$item) {
foreach(array_keys($orgs) as $org_name) {
unset($item[$org_name]);
}
}
}
public function handler($user, $options = array())
{
$this->Log = ClassRegistry::init('Log');
$this->Org = ClassRegistry::init('Organisation');
$this->Event = ClassRegistry::init('Event');
$orgs = $this->Org->find('all', array( 'conditions' => array('Organisation.local' => 1)));
$current_month = date('n');
$current_year = date('Y');
$limit = 6; // months
if(!empty($options['months'])) {
$limit = (int) ($options['months']);
}
$offset = 0;
$ghost_orgs = array(); // track orgs without any contribution
// We start by putting all orgs_id in there:
foreach($orgs as $org) {
// We check for blocklisted orgs
if(!empty($options['blocklist_orgs']) && in_array($org['Organisation']['name'], $options['blocklist_orgs'])) {
unset($orgs[$offset]);
} else {
$ghost_orgs[$org['Organisation']['name']] = true;
}
$offset++;
}
$data = array();
$data['data'] = array();
for ($i=0; $i < $limit; $i++) {
$target_month = $current_month - $i;
$target_year = $current_year;
if ($target_month < 1) {
$target_month += 12;
$target_year -= 1;
}
$item = array();
$item ['date'] = $target_year.'-'.$target_month.'-01';
foreach($orgs as $org) {
$count = $this->org_events_count($user, $org, $target_month, $target_year);
if($options['logarithmic'] === "true" || $options['logarithmic'] === "1") {
$item[$org['Organisation']['name']] = (int) round(log($count, 1.1)); // taking the logarithmic view
} else if(empty($options['logarithmic']) || $options['logarithmic'] === "true" || $options['logarithmic'] === "1"){
$item[$org['Organisation']['name']] = $count;
}
// if a positive score is detected at least once it's enough to be
// considered for the graph
if($count > 0) {
unset($ghost_orgs[$org['Organisation']['name']]);
}
}
$data['data'][] = $item;
}
$this->filter_ghost_orgs($data, $ghost_orgs);
return $data;
}
}

View File

@ -83,6 +83,12 @@ abstract class StixExport
if ($this->__empty_file) {
$this->__tmp_file->close();
$this->__tmp_file->delete();
if (empty($this->__filenames)) {
$framing = $this->getFraming();
$tmpFile = new TmpFileTool();
$tmpFile->write($framing['header'] . $framing['footer']);
return $tmpFile;
}
} else {
if (!empty($this->__event_galaxies)) {
$this->__write_event_galaxies();

View File

@ -224,7 +224,6 @@ class AttributeValidationTool
switch ($type) {
case 'md5':
case 'imphash':
case 'telfhash':
case 'sha1':
case 'sha224':
case 'sha256':
@ -255,6 +254,11 @@ class AttributeValidationTool
return true;
}
return __('Checksum has an invalid length or format (expected: at least 35 hexadecimal characters, optionally starting with t1 instead of hexadecimal characters). Please double check the value or select type "other".');
case 'telfhash':
if (self::isTelfhashValid($value)) {
return true;
}
return __('Checksum has an invalid length or format (expected: %s or %s hexadecimal characters). Please double check the value or select type "other".', 70, 72);
case 'pehash':
if (self::isHashValid('pehash', $value)) {
return true;
@ -635,6 +639,15 @@ class AttributeValidationTool
return strlen($value) > 35 && ctype_xdigit($value);
}
/**
* @param string $value
* @return bool
*/
private static function isTelfhashValid($value)
{
return strlen($value) == 70 || strlen($value) == 72;
}
/**
* @param string $type

View File

@ -6,16 +6,18 @@ class GraphUtil
{
public function __construct($graphData)
{
$this->graph = $graphData;
$this->graph = array_filter($graphData, function($i) {
return $i != '_frames';
}, ARRAY_FILTER_USE_KEY);
$this->numberNodes = count($this->graph);
$this->edgeList = $this->_buildEdgeList($graphData);
$this->edgeList = $this->_buildEdgeList($this->graph);
$this->properties = [];
}
private function _buildEdgeList($graphData): array
{
$list = [];
foreach ($graphData as $node) {
foreach ($graphData as $i => $node) {
$list[(int)$node['id']] = [];
foreach (($node['outputs'] ?? []) as $output_id => $outputs) {
foreach ($outputs as $connections) {
@ -356,6 +358,20 @@ class WorkflowRoamingData
class WorkflowGraphTool
{
/**
* cleanGraphData Remove frame nodes from the graph data
*
* @param array $graphData
* @return array
*/
public static function cleanGraphData(array $graphData): array
{
return array_filter($graphData, function($i) {
return $i != '_frames';
}, ARRAY_FILTER_USE_KEY);
}
/**
* extractTriggerFromWorkflow Return the trigger id (or full module) that are specified in the workflow
*
@ -382,8 +398,9 @@ class WorkflowGraphTool
*/
public static function extractTriggersFromWorkflow(array $graphData, bool $fullNode = false): array
{
$graphData = self::cleanGraphData($graphData);
$triggers = [];
foreach ($graphData as $node) {
foreach ($graphData as $i => $node) {
if ($node['data']['module_type'] == 'trigger') {
if (!empty($fullNode)) {
$triggers[] = $node;
@ -404,8 +421,9 @@ class WorkflowGraphTool
*/
public static function extractConcurrentTasksFromWorkflow(array $graphData, bool $fullNode = false): array
{
$graphData = self::cleanGraphData($graphData);
$nodes = [];
foreach ($graphData as $node) {
foreach ($graphData as $i => $node) {
if ($node['data']['module_type'] == 'logic' && $node['data']['id'] == 'concurrent-task') {
if (!empty($fullNode)) {
$nodes[] = $node;
@ -426,8 +444,9 @@ class WorkflowGraphTool
*/
public static function extractFilterNodesFromWorkflow(array $graphData, bool $fullNode = false): array
{
$graphData = self::cleanGraphData($graphData);
$nodes = [];
foreach ($graphData as $node) {
foreach ($graphData as $i => $node) {
if ($node['data']['module_type'] == 'logic' && $node['data']['id'] == 'generic-filter-data') {
if (!empty($fullNode)) {
$nodes[] = $node;
@ -448,8 +467,9 @@ class WorkflowGraphTool
*/
public static function extractResetFilterFromWorkflow(array $graphData, bool $fullNode = false): array
{
$graphData = self::cleanGraphData($graphData);
$nodes = [];
foreach ($graphData as $node) {
foreach ($graphData as $i => $node) {
if ($node['data']['module_type'] == 'logic' && $node['data']['id'] == 'generic-filter-reset') {
if (!empty($fullNode)) {
$nodes[] = $node;

View File

@ -55,6 +55,7 @@ class AuthKey extends AppModel
$this->data['AuthKey']['authkey_raw'] = $authkey;
}
$validAllowedIpFound = false;
if (!empty($this->data['AuthKey']['allowed_ips'])) {
$allowedIps = &$this->data['AuthKey']['allowed_ips'];
if (is_string($allowedIps)) {
@ -70,12 +71,18 @@ class AuthKey extends AppModel
if (!is_array($allowedIps)) {
$this->invalidate('allowed_ips', 'Allowed IPs must be array');
}
foreach ($allowedIps as $cidr) {
if (!CidrTool::validate($cidr)) {
$this->invalidate('allowed_ips', "$cidr is not valid IP range");
} else {
$validAllowedIpFound = true;
}
}
}
if (!empty(Configure::read('Security.mandate_ip_allowlist_advanced_authkeys')) && $validAllowedIpFound === false){
$this->invalidate('allowed_ips', "Setting an ip allowlist is mandatory on this instance.");
}
$creationTime = isset($this->data['AuthKey']['created']) ? $this->data['AuthKey']['created'] : time();
$validity = Configure::read('Security.advanced_authkeys_validity');
@ -219,9 +226,9 @@ class AuthKey extends AppModel
$user['authkey_read_only'] = (bool)$authkey['AuthKey']['read_only'];
if ($authkey['AuthKey']['read_only']) {
// Disable all permissions, keep just `perm_auth` unchanged
// Disable all permissions, keep just `perm_auth` and `perm_audit` unchanged
foreach ($user['Role'] as $key => &$value) {
if (substr($key, 0, 5) === 'perm_' && $key !== 'perm_auth') {
if (substr($key, 0, 5) === 'perm_' && $key !== 'perm_auth' && $key !== 'perm_audit') {
$value = 0;
}
}

View File

@ -540,7 +540,8 @@ class Event extends AppModel
'local' => $local,
'relationship_type' => $relationship,
];
$success = $success || $this->EventTag->attachTagToEvent($event_id, $tag, $nothingToChange);
$attachSuccess = $this->EventTag->attachTagToEvent($event_id, $tag, $nothingToChange);
$success = $success || $attachSuccess;
$touchEvent = $touchEvent || !$nothingToChange;
}
if ($touchEvent) {
@ -562,7 +563,8 @@ class Event extends AppModel
$success = $success || true;
continue;
}
$success = $success || $this->EventTag->detachTagFromEvent($event_id, $tag_id, $local, $nothingToChange);
$detachSuccess = $this->EventTag->detachTagFromEvent($event_id, $tag_id, $local, $nothingToChange);
$success = $success || $detachSuccess;
$touchEvent = $touchEvent || !$nothingToChange;
}
if ($touchEvent) {
@ -2860,10 +2862,35 @@ class Event extends AppModel
return $conditions;
}
/**
* @param string $value
* @return string
*/
private static function compressIpv6($value)
{
if (strpos($value, ':') && $converted = inet_pton($value)) {
return inet_ntop($converted);
}
return $value;
}
public function set_filter_value(&$params, $conditions, $options)
{
if (!empty($params['value'])) {
$params[$options['filter']] = $this->convert_filters($params['value']);
foreach (['OR', 'AND', 'NOT'] as $operand) {
if (!empty($params[$options['filter']][$operand])) {
foreach ($params[$options['filter']][$operand] as $k => $v) {
if ($operand === 'NOT') {
$v = mb_substr($v, 1);
}
if (filter_var($v, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
$v = $this->compressIpv6($v);
}
$params[$options['filter']][$operand][$k] = $operand === 'NOT' ? '!' . $v : $v;
}
}
}
$conditions = $this->generic_add_filter($conditions, $params['value'], ['Attribute.value1', 'Attribute.value2']);
}
@ -4476,7 +4503,7 @@ class Event extends AppModel
/** @var Job $job */
$job = ClassRegistry::init('Job');
$message = empty($sightingUuids) ? __('Publishing sightings.') : __('Publishing %s sightings.', count($sightingUuids));
$jobId = $job->createJob($user, Job::WORKER_PRIO, 'publish_event', "Event ID: $id", $message);
$jobId = $job->createJob($user, Job::WORKER_DEFAULT, 'publish_event', "Event ID: $id", $message);
$args = ['publish_sightings', $id, $passAlong, $jobId, $user['id']];
if (!empty($sightingUuids)) {
@ -4484,7 +4511,7 @@ class Event extends AppModel
}
return $this->getBackgroundJobsTool()->enqueue(
BackgroundJobsTool::PRIO_QUEUE,
BackgroundJobsTool::DEFAULT_QUEUE,
BackgroundJobsTool::CMD_EVENT,
$args,
true,

View File

@ -44,6 +44,7 @@ class Log extends AppModel
'export',
'fetchEvent',
'file_upload',
'forgot',
'galaxy',
'include_formula',
'load_module',
@ -51,6 +52,7 @@ class Log extends AppModel
'login_fail',
'logout',
'merge',
'password_reset',
'pruneUpdateLogs',
'publish',
'publish_sightings',

View File

@ -6373,6 +6373,14 @@ class Server extends AppModel
'type' => 'boolean',
'null' => true
),
'mandate_ip_allowlist_advanced_authkeys' => array(
'level' => 2,
'description' => __('If enabled, setting an ip allowlist will be mandatory when adding or editing an advanced authkey.'),
'value' => false,
'test' => 'testBool',
'type' => 'boolean',
'null' => true
),
'disable_browser_cache' => array(
'level' => 0,
'description' => __('If enabled, HTTP headers that block browser cache will be send. Static files (like images or JavaScripts) will still be cached, but not generated pages.'),
@ -6465,6 +6473,14 @@ class Server extends AppModel
'type' => 'boolean',
'null' => true
),
'allow_password_forgotten' => array(
'level' => 1,
'description' => __('Enabling this setting will allow users to request automated password reset tokens via mail and initiate a reset themselves. Users with no encryption keys will not be able to use this feature.'),
'value' => false,
'test' => 'testBool',
'type' => 'boolean',
'null' => true
),
'self_registration_message' => array(
'level' => 1,
'bigField' => true,
@ -7251,6 +7267,13 @@ class Server extends AppModel
'test' => 'testBool',
'type' => 'boolean'
),
'Sightings_enable_realtime_publish' => array(
'level' => 1,
'description' => __('By default, sightings will not be immediately pushed to connected instances, as this can have a heavy impact on the performance of sighting attributes. Enable realtime publishing to trigger the publishing of sightings immediately as they are added.'),
'value' => false,
'test' => 'testBool',
'type' => 'boolean'
),
'CustomAuth_enable' => array(
'level' => 2,
'description' => __('Enable this functionality if you would like to handle the authentication via an external tool and authenticate with MISP using a custom header.'),

View File

@ -4,6 +4,8 @@ App::uses('EncryptedValue', 'Tools');
App::uses('Folder', 'Utility');
App::uses('File', 'Utility');
App::uses('RandomTool', 'Tools');
App::uses('JSONConverterTool', 'Tools');
App::uses('JsonTool', 'Tools');
class TaxiiServer extends AppModel
{
@ -102,7 +104,11 @@ class TaxiiServer extends AppModel
$this->Job->id = $jobId;
foreach ($result as $event) {
$temporaryFile = $this->temporaryFile($temporaryFolderPath);
$temporaryFile->write(json_encode($event));
$temporaryFile->write(
JsonTool::encode(
JSONConverterTool::convert($event, false, true)
)
);
$temporaryFile->close();
if ($jobId && $i % 10 == 0) {
$this->Job->saveField('progress', intval((100 * $i) / $eventCount));

View File

@ -1229,6 +1229,15 @@ class User extends AppModel
public function extralog($user, $action = null, $description = null, $fieldsResult = null, $modifiedUser = null)
{
if (!is_array($user) && $user === 'SYSTEM') {
$user = [
'id' => 0,
'email' => 'SYSTEM',
'Organisation' => [
'name' => 'SYSTEM'
]
];
}
// new data
$model = 'User';
$modelId = $user['id'];
@ -2007,4 +2016,89 @@ class User extends AppModel
}
return false;
}
public function forgotRouter($email, $ip)
{
if (Configure::read('MISP.background_jobs')) {
/** @var Job $job */
$job = ClassRegistry::init('Job');
$dummyUser = [
'email' => 'SYSTEM',
'org_id' => 0,
'role_id' => 0
];
$jobId = $job->createJob($dummyUser, Job::WORKER_EMAIL, 'forgot_password', $email, 'Sending...');
$args = [
'jobForgot',
$email,
$ip,
$jobId,
];
$this->getBackgroundJobsTool()->enqueue(
BackgroundJobsTool::EMAIL_QUEUE,
BackgroundJobsTool::CMD_ADMIN,
$args,
true,
$jobId
);
return true;
} else {
return $this->forgot($email);
}
}
public function forgot($email, $ip, $jobId = null)
{
$user = $this->find('first', [
'recursive' => -1,
'conditions' => [
'User.email' => $email,
'User.disabled' => 0
]
]);
if (empty($user)) {
return false;
}
$redis = $this->setupRedis();
$token = RandomTool::random_str(true, 40, '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ');
$redis->set('misp:forgot:' . $token, $user['User']['id'], ['nx', 'ex' => 600]);
$baseurl = Configure::check('MISP.external_baseurl') ? Configure::read('MISP.external_baseurl') : Configure::read('MISP.baseurl');
$body = __(
"Dear MISP user,\n\nyou have requested a password reset on the MISP instance at %s. Click the link below to change your password.\n\n%s\n\nThe link above is only valid for 10 minutes, feel free to request a new one if it has expired.\n\nIf you haven't requested a password reset, reach out to your admin team and let them know that someone has attempted it in your stead.\n\nMake sure you keep the contents of this e-mail confidential, do NOT ever forward it as it contains a reset token that is equivalent of a password if acted upon. The IP used to trigger the request was: %s\n\nBest regards,\nYour MISP admin team",
$baseurl,
$baseurl . '/users/password_reset/' . $token,
$ip
);
$bodyNoEnc = __(
"Dear MISP user,\n\nyou have requested a password reset on the MISP instance at %s, however, no valid encryption key was found for your user and thus we cannot deliver your reset token. Please get in touch with your org admin / with an instance site admin to ask for a reset.\n\nThe IP used to trigger the request was: %s\n\nBest regards,\nYour MISP admin team",
$baseurl,
$ip
);
$this->sendEmail($user, $body, $bodyNoEnc, __('MISP password reset'));
return true;
}
public function fetchForgottenPasswordUser($token)
{
if (!ctype_alnum($token)) {
return false;
}
$redis = $this->setupRedis();
$userId = $redis->get('misp:forgot:' . $token);
if (empty($userId)) {
return false;
}
$user = $this->getAuthUser($userId, true);
return $user;
}
public function purgeForgetToken($token)
{
$redis = $this->setupRedis();
$userId = $redis->del('misp:forgot:' . $token);
return true;
}
}

View File

@ -13,7 +13,7 @@ class Workflow extends AppModel
public $recursive = -1;
public $actsAs = [
'AuditLog',
// 'AuditLog',
'Containable',
'SysLogLogable.SysLogLogable' => [
'roleModel' => 'Role',
@ -518,15 +518,14 @@ class Workflow extends AppModel
{
$this->Log = ClassRegistry::init('Log');
$message = __('Started executing workflow for trigger `%s` (%s)', $triggerModule->id, $workflow['Workflow']['id']);
$this->Log->createLogEntry('SYSTEM', 'execute_workflow', 'Workflow', $workflow['Workflow']['id'], $message);
$this->__logToFile($workflow, $message);
$this->logExecutionIfDebug($workflow, $message);
$workflow = $this->__incrementWorkflowExecutionCount($workflow);
$walkResult = [];
$debugData = ['original' => $data];
$data = $this->__normalizeDataForTrigger($triggerModule, $data);
$debugData['normalized'] = $data;
$for_path = !empty($triggerModule->blocking) ? GraphWalker::PATH_TYPE_BLOCKING : GraphWalker::PATH_TYPE_NON_BLOCKING;
$this->sendRequestToDebugEndpoint($workflow, [], '/init?type=' . $for_path, $debugData);
$this->sendRequestToDebugEndpointIfDebug($workflow, [], '/init?type=' . $for_path, $debugData);
$blockingPathExecutionSuccess = $this->walkGraph($workflow, $startNodeID, $for_path, $data, $blockingErrors, $walkResult);
$executionStoppedByStopModule = in_array('stop-execution', Hash::extract($walkResult, 'blocking_nodes.{n}.data.id'));
@ -542,9 +541,8 @@ class Workflow extends AppModel
}
$message = __('Finished executing workflow for trigger `%s` (%s). Outcome: %s', $triggerModule->id, $workflow['Workflow']['id'], $outcomeText);
$this->Log->createLogEntry('SYSTEM', 'execute_workflow', 'Workflow', $workflow['Workflow']['id'], $message);
$this->__logToFile($workflow, $message);
$this->sendRequestToDebugEndpoint($workflow, [], '/end?outcome=' . $outcomeText, $walkResult);
$this->logExecutionIfDebug($workflow, $message);
$this->sendRequestToDebugEndpointIfDebug($workflow, [], '/end?outcome=' . $outcomeText, $walkResult);
return [
'outcomeText' => $outcomeText,
'walkResult' => $walkResult,
@ -661,7 +659,7 @@ class Workflow extends AppModel
$message = __('Could not execute disabled module `%s`.', $node['data']['id']);
$this->logExecutionError($roamingData->getWorkflow(), $message);
$errors[] = $message;
$this->sendRequestToDebugEndpoint($roamingData->getWorkflow(), $node, sprintf('/exec/%s?result=%s', $moduleClass->id, 'disabled_module'), $roamingData->getData());
$this->sendRequestToDebugEndpointIfDebug($roamingData->getWorkflow(), $node, sprintf('/exec/%s?result=%s', $moduleClass->id, 'disabled_module'), $roamingData->getData());
return false;
}
if (!is_null($moduleClass)) {
@ -671,17 +669,25 @@ class Workflow extends AppModel
$message = __('Error while executing module %s. Error: %s', $node['data']['id'], $e->getMessage());
$this->logExecutionError($roamingData->getWorkflow(), $message);
$errors[] = $message;
$this->sendRequestToDebugEndpoint($roamingData->getWorkflow(), $node, sprintf('/exec/%s?result=%s&message=%s', $moduleClass->id, 'error', $e->getMessage()), $roamingData->getData());
$this->sendRequestToDebugEndpointIfDebug($roamingData->getWorkflow(), $node, sprintf('/exec/%s?result=%s&message=%s', $moduleClass->id, 'error', $e->getMessage()), $roamingData->getData());
return false;
}
} else {
$message = sprintf(__('Could not load class for module: %s'), $node['data']['id']);
$this->logExecutionError($roamingData->getWorkflow(), $message);
$errors[] = $message;
$this->sendRequestToDebugEndpoint($roamingData->getWorkflow(), $node, sprintf('/exec/%s?result=%s', $node['data']['id'], 'loading_error'), $roamingData->getData());
$this->sendRequestToDebugEndpointIfDebug($roamingData->getWorkflow(), $node, sprintf('/exec/%s?result=%s', $node['data']['id'], 'loading_error'), $roamingData->getData());
return false;
}
$this->sendRequestToDebugEndpoint($roamingData->getWorkflow(), $node, sprintf('/exec/%s?result=%s', $moduleClass->id, 'success'), $roamingData->getData());
$message = __('Executed node `%s`' . PHP_EOL . 'Node `%s` (%s) from Workflow `%s` (%s) executed successfully',
$node['data']['id'],
$node['data']['id'],
$node['id'],
$roamingData->getWorkflow()['Workflow']['name'],
$roamingData->getWorkflow()['Workflow']['id']
);
$this->logExecutionIfDebug($roamingData->getWorkflow(), $message);
$this->sendRequestToDebugEndpointIfDebug($roamingData->getWorkflow(), $node, sprintf('/exec/%s?result=%s', $moduleClass->id, 'success'), $roamingData->getData());
return $success;
}
@ -993,6 +999,14 @@ class Workflow extends AppModel
$this->__logToFile($workflow, $message);
}
public function logExecutionIfDebug(array $workflow, $message): void
{
if ($workflow['Workflow']['debug_enabled']) {
$this->Log->createLogEntry('SYSTEM', 'execute_workflow', 'Workflow', $workflow['Workflow']['id'], $message);
$this->__logToFile($workflow, $message);
}
}
/**
* __logToFile Log to file
*
@ -1321,19 +1335,32 @@ class Workflow extends AppModel
$node = $graphNode['node'];
$nodeID = $node['id'];
$parsedPathList = GraphWalker::parsePathList($graphNode['path_list']);
if (!empty($parsedPathList)) {
$lastNodeInPath = $parsedPathList[count($parsedPathList)-1];
$previousNodeId = $lastNodeInPath['source_id'];
$connections[$nodeID][$previousNodeId] = [];
}
foreach ($parsedPathList as $pathEntry) {
if (!empty($filterNodeIDToLabel[$pathEntry['source_id']])) {
$connections[$nodeID][] = $filterNodeIDToLabel[$pathEntry['source_id']];
$connections[$nodeID][$previousNodeId][] = $filterNodeIDToLabel[$pathEntry['source_id']];
}
if (!empty($resetFilterNodeIDToLabel[$pathEntry['source_id']])) {
if ($resetFilterNodeIDToLabel[$pathEntry['source_id']] == 'all') {
$connections[$nodeID] = [];
$connections[$nodeID][$previousNodeId] = [];
} else {
$connections[$nodeID] = array_values(array_diff($connections[$nodeID], [$resetFilterNodeIDToLabel[$pathEntry['source_id']]]));
$connections[$nodeID][$previousNodeId] = array_values(array_diff($connections[$nodeID][$previousNodeId], [$resetFilterNodeIDToLabel[$pathEntry['source_id']]]));
}
}
}
}
$connections = array_filter($connections, function($connection) {
foreach ($connection as $labels) {
if (!empty($labels)) {
return true;
}
}
return false;
});
return $connections;
}
@ -1346,6 +1373,9 @@ class Workflow extends AppModel
}
$labelsByNodes = $this->getLabelsForConnections($workflow, $trigger_id);
foreach ($graphData as $i => $node) {
if ($i == '_frames') {
continue;
}
if (!empty($labelsByNodes[$node['id']])) {
foreach ($node['inputs'] as $inputName => $inputs) {
foreach ($inputs['connections'] as $j => $connection) {
@ -1355,7 +1385,7 @@ class Workflow extends AppModel
'name' => $label,
'variant' => 'info',
];
}, $labelsByNodes[$node['id']]);
}, $labelsByNodes[$node['id']][$connection['node']]);
}
}
}
@ -1408,6 +1438,7 @@ class Workflow extends AppModel
'indexed_params' => $indexed_params,
'saved_filters' => $module_config['saved_filters'],
'module_data' => $module_config,
'expect_misp_core_format' => $module_config['expect_misp_core_format'],
],
'inputs' => [],
'outputs' => [],
@ -1464,12 +1495,16 @@ class Workflow extends AppModel
return $saveSuccess;
}
public function sendRequestToDebugEndpointIfDebug(array $workflow, array $node, $path='/', array $data=[])
{
if ($workflow['Workflow']['debug_enabled']) {
$this->sendRequestToDebugEndpoint($workflow, $node, $path, $data);
}
}
public function sendRequestToDebugEndpoint(array $workflow, array $node, $path='/', array $data=[])
{
$debug_url = Configure::read('Plugin.Workflow_debug_url');
if (empty($workflow['Workflow']['debug_enabled'])) {
return;
}
App::uses('HttpSocket', 'Network/Http');
$socket = new HttpSocket([
'timeout' => 5

View File

@ -23,7 +23,7 @@ class WorkflowBaseModule
];
public $params = [];
private $Event;
private $Workflow;
/** @var PubSubTool */
private static $loadedPubSubTool;
@ -32,6 +32,16 @@ class WorkflowBaseModule
{
}
public function debug(array $node, WorkflowRoamingData $roamingData, array $data=[]): void
{
if (!isset($this->Workflow)) {
$this->Workflow = ClassRegistry::init('Workflow');
}
$workflow = $roamingData->getWorkflow();
$path = sprintf('/debug/%s', $node['data']['id'] ?? '');
$this->Workflow->sendRequestToDebugEndpoint($workflow, $node, $path, $data);
}
protected function mergeNodeConfigIntoParameters($node): array
{
$fullIndexedParams = [];
@ -213,11 +223,13 @@ class WorkflowBaseModule
public function getItemsMatchingCondition($items, $value, $operator, $path)
{
foreach ($items as $i => $item) {
$subItem = $this->extractData($item, $path, $operator);
$subItem = $this->extractData($item, $path);
if (in_array($operator, ['equals', 'not_equals'])) {
$subItem = !empty($subItem) ? $subItem[0] : $subItem;
}
if (!$this->evaluateCondition($subItem, $operator, $value)) {
if ($operator == 'any_value' && !empty($subItem)) {
continue;
} else if (!$this->evaluateCondition($subItem, $operator, $value)) {
unset($items[$i]);
}
}
@ -296,6 +308,49 @@ class WorkflowBaseLogicModule extends WorkflowBaseModule
class WorkflowBaseActionModule extends WorkflowBaseModule
{
protected $fastLookupArrayMispFormat = [];
protected $fastLookupArrayFlattened = [];
public function exec(array $node, WorkflowRoamingData $roamingData, array &$errors = []): bool
{
$rData = $roamingData->getData();
$this->_buildFastLookupForRoamingData($rData);
return true;
}
protected function _buildFastLookupForRoamingData($rData): void
{
if (!empty($rData['Event']['Attribute'])) {
foreach ($rData['Event']['Attribute'] as $i => $attribute) {
$this->fastLookupArrayMispFormat[$attribute['id']] = $i;
}
}
if (!empty($rData['Event']['Object'])) {
foreach ($rData['Event']['Object'] as $j => $object) {
foreach ($object['Attribute'] as $i => $attribute) {
$this->fastLookupArrayMispFormat[$attribute['id']] = [$j, $i];
}
}
}
foreach ($rData['Event']['_AttributeFlattened'] as $i => $attribute) {
$this->fastLookupArrayFlattened[$attribute['id']] = $i;
}
}
protected function _overrideAttribute(array $oldAttribute, array $newAttribute, array $rData): array
{
$attributeID = $oldAttribute['id'];
$rData['Event']['_AttributeFlattened'][$this->fastLookupArrayFlattened[$attributeID]] = $newAttribute;
if (is_array($this->fastLookupArrayMispFormat[$attributeID])) {
$objectID = $this->fastLookupArrayMispFormat[$attributeID][0];
$attributeID = $this->fastLookupArrayMispFormat[$attributeID][1];
$rData['Event']['Object'][$objectID]['Attribute'][$attributeID] = $newAttribute;
} else {
$attributeID = $this->fastLookupArrayMispFormat[$attributeID];
$rData['Event']['Attribute'][$attributeID] = $newAttribute;
}
return $rData;
}
}
class WorkflowFilteringLogicModule extends WorkflowBaseLogicModule

View File

@ -0,0 +1,152 @@
<?php
include_once APP . 'Model/WorkflowModules/action/Module_tag_operation.php';
class Module_assign_country_from_enrichment extends Module_tag_operation
{
public $version = '0.2';
public $blocking = false;
public $id = 'assign_country';
public $name = 'Assign country';
public $description = 'Add or remove country Galaxy Cluster based on provided data';
public $icon = 'globe';
public $inputs = 1;
public $outputs = 1;
public $support_filters = true;
public $expect_misp_core_format = true;
public $params = [];
private $Galaxy;
private $countryClusters;
public function __construct()
{
parent::__construct();
$this->params = [
[
'id' => 'scope',
'label' => __('Scope'),
'type' => 'select',
'options' => [
'event' => __('Event'),
'attribute' => __('Attributes'),
],
'default' => 'event',
],
[
'id' => 'hash_path',
'label' => 'Country Hash path',
'type' => 'hashpath',
'placeholder' => 'enrichment.{n}.{n}.values.0',
'default' => 'enrichment.{n}.{n}.values.0'
],
[
'id' => 'locality',
'label' => __('Tag Locality'),
'type' => 'select',
'options' => [
'local' => __('Local'),
'global' => __('Global'),
],
'default' => 'local',
],
[
'id' => 'galaxy_name',
'label' => __('Galaxy Name'),
'type' => 'select',
'options' => [
'country' => 'country',
],
'placeholder' => __('Pick a galaxy name'),
],
[
'id' => 'relationship_type',
'label' => __('Relationship Type'),
'type' => 'input',
'display_on' => [
'action' => 'add',
],
],
];
$this->Galaxy = ClassRegistry::init('Galaxy');
$this->countryClusters = $this->_fetchCountryGalaxyClusters();
}
public function exec(array $node, WorkflowRoamingData $roamingData, array &$errors = []): bool
{
$params = $this->getParamsWithValues($node);
$rData = $roamingData->getData();
$user = $roamingData->getUser();
$countryExtractionPath = $params['hash_path']['value'];
if ($this->filtersEnabled($node)) {
$filters = $this->getFilters($node);
$extracted = $this->extractData($rData, $filters['selector']);
if ($extracted === false) {
return false;
}
$matchingItems = $this->getItemsMatchingCondition($extracted, $filters['value'], $filters['operator'], $filters['path']);
} else {
$matchingItems = $rData;
}
$matchingAttributes = Hash::extract($matchingItems, 'Event._AttributeFlattened.{n}');
if ($params['scope']['value'] == 'event') {
if (substr($countryExtractionPath, 0, 4) !== '{n}.') {
$countryExtractionPath = '{n}.' . $countryExtractionPath;
}
}
$result = false;
if ($params['scope']['value'] == 'event') {
$extractedCountries = array_unique(Hash::extract($matchingAttributes, $countryExtractionPath));
$guessedCountryTags = $this->guessTagFromPath($extractedCountries);
$options = [
'tags' => $guessedCountryTags,
'local' => ($params['locality']['value'] == 'local' ? true : false),
'relationship_type' => $params['relationship_type']['value'],
];
$result = $this->__addTagsToEvent($matchingItems, $options, $user);
} else {
foreach ($matchingAttributes as $attribute) {
$extractedCountries = Hash::extract($attribute, $countryExtractionPath);
$guessedCountryTags = $this->guessTagFromPath($extractedCountries);
$options = [
'tags' => $guessedCountryTags,
'local' => ($params['locality']['value'] == 'local' ? true : false),
'relationship_type' => $params['relationship_type']['value'],
];
$result = $this->__addTagsToAttributes([$attribute], $options, $user);
}
}
return $result;
}
protected function _fetchCountryGalaxyClusters(): array
{
$clusters = $this->Galaxy->find('first', [
'recursive' => -1,
'conditions' => [
'name' => 'Country',
],
'contain' => [
'GalaxyCluster' => ['fields' => ['id', 'uuid', 'value', 'tag_name']],
],
]);
return $clusters['GalaxyCluster'];
}
protected function guessTagFromPath($countries)
{
$matchingTags = [];
foreach ($countries as $country) {
foreach ($this->countryClusters as $countryCluster) {
if (strtolower($countryCluster['value']) == strtolower($country)) {
$matchingTags[] = $countryCluster['tag_name'];
}
}
}
return $matchingTags;
}
}

View File

@ -5,6 +5,7 @@ class Module_attach_enrichment extends WorkflowBaseActionModule
{
public $id = 'attach-enrichment';
public $name = 'Attach enrichment';
public $version = '0.3';
public $description = 'Attach selected enrichment result to Attributes.';
public $icon = 'asterisk';
public $inputs = 1;
@ -14,8 +15,7 @@ class Module_attach_enrichment extends WorkflowBaseActionModule
public $params = [];
private $Module;
private $fastLookupArrayMispFormat = [];
private $fastLookupArrayFlattened = [];
private $allModulesByName = [];
public function __construct()
@ -23,19 +23,30 @@ class Module_attach_enrichment extends WorkflowBaseActionModule
parent::__construct();
$this->Module = ClassRegistry::init('Module');
$modules = $this->Module->getModules('Enrichment');
$moduleOptions = [];
if (is_array($modules)) {
$this->allModulesByName = Hash::combine($modules, '{n}.name', '{n}');
}
$moduleOptions = [];
$pickerOptions = [];
$enrichmentAvailable = false;
if (!empty($modules) && is_array($modules)) {
$enrichmentAvailable = true;
$moduleOptions = array_merge([''], Hash::combine($modules, '{n}.name', '{n}.name'));
} else {
$moduleOptions[] = $modules;
$pickerOptions = [
'placeholder_text_multiple' => __('No enrichment module available'),
];
}
sort($moduleOptions);
$this->params = [
[
'id' => 'modules',
'label' => 'Modules',
'type' => 'select',
'type' => 'picker',
'multiple' => true,
'disabled' => !$enrichmentAvailable,
'options' => $moduleOptions,
'picker_options' => $pickerOptions,
],
];
}
@ -47,14 +58,17 @@ class Module_attach_enrichment extends WorkflowBaseActionModule
if (empty($params['modules']['value'])) {
$errors[] = __('No enrichmnent module selected');
return false;
} else if (is_string($params['modules']['value'])) {
$params['modules']['value'] = [$params['modules']['value']];
}
$selectedModules = array_filter($params['modules']['value'], function($module) {
return $module !== '';
});
$rData = $roamingData->getData();
$event_id = $rData['Event']['id'];
$options = [
'user' => $roamingData->getUser(),
'event_id' => $event_id,
'module' => $params['modules']['value'],
'config' => ['_' => '_'], // avoid casting empty associative array in to empty list
];
$matchingItems = $this->getMatchingItemsForAttributes($node, $rData);
@ -64,10 +78,22 @@ class Module_attach_enrichment extends WorkflowBaseActionModule
$this->_buildFastLookupForRoamingData($rData);
foreach ($matchingItems as $attribute) {
$moduleData = $options;
$moduleData['attribute'] = $attribute;
$queryResult = $this->_queryModules($moduleData, $attribute, $rData);
$rData = $this->_attachEnrichmentData($attribute, $queryResult, $rData);
foreach ($selectedModules as $selectedModule) {
$moduleConfig = $this->allModulesByName[$selectedModule];
$moduleData = $options;
$moduleData['config'] = $this->getModuleOptions($moduleConfig);
$moduleData['module'] = $selectedModule;
$moduleData['attribute'] = $attribute;
if (!$this->_checkIfInputSupported($attribute, $moduleConfig)) { // Queried module doesn't support the Attribute's type
continue;
}
if (empty($moduleConfig['mispattributes']['format'])) { // Adapt payload if modules doesn't support the misp-format
$moduleData = $this->_convertPayloadToModuleFormat($moduleData, $moduleConfig);
}
$queryResult = $this->_queryModules($moduleData, $attribute, $rData);
$queryResult = $this->_handleModuleResult($queryResult, $moduleConfig);
$rData = $this->_attachEnrichmentData($attribute, $queryResult, $rData);
}
}
$roamingData->setData($rData);
return true;
@ -82,19 +108,31 @@ class Module_attach_enrichment extends WorkflowBaseActionModule
return $result;
}
protected function _buildFastLookupForRoamingData($rData): void
protected function _convertPayloadToModuleFormat(array $options, array $moduleConfig): array
{
foreach ($rData['Event']['Attribute'] as $i => $attribute) {
$this->fastLookupArrayMispFormat[$attribute['id']] = $i;
}
foreach ($rData['Event']['Object'] as $j => $object) {
foreach ($object['Attribute'] as $i => $attribute) {
$this->fastLookupArrayMispFormat[$attribute['id']] = [$j, $i];
$attribute = $options['attribute'];
unset($options['attribute']);
foreach ($moduleConfig['mispattributes']['input'] as $supportedAttributeType) {
if ($supportedAttributeType == $attribute['type']) {
$options[$supportedAttributeType] = $attribute['value'];
}
}
foreach ($rData['Event']['_AttributeFlattened'] as $i => $attribute) {
$this->fastLookupArrayFlattened[$attribute['id']] = $i;
return $options;
}
protected function _checkIfInputSupported(array $attribute, array $moduleConfig): bool
{
foreach ($moduleConfig['mispattributes']['input'] as $supportedAttributeType) {
if ($supportedAttributeType == $attribute['type']) {
return true;
}
}
return false;
}
protected function _handleModuleResult(array $queryResult, array $moduleConfig): array
{
return $queryResult;
}
protected function _attachEnrichmentData(array $attribute, array $queryResult, array $rData): array
@ -111,4 +149,16 @@ class Module_attach_enrichment extends WorkflowBaseActionModule
}
return $rData;
}
protected function getModuleOptions(array $moduleConfig): array
{
$type = 'Enrichment';
$options = [];
if (isset($moduleConfig['meta']['config'])) {
foreach ($moduleConfig['meta']['config'] as $conf) {
$options[$conf] = Configure::read('Plugin.' . $type . '_' . $moduleConfig['name'] . '_' . $conf);
}
}
return !empty($options) ? $options : ['_' => '_']; // avoid casting empty associative array in to empty list
}
}

View File

@ -17,8 +17,6 @@ class Module_attach_warninglist extends WorkflowBaseActionModule
/** @var Warninglist */
private $Warninglist;
private $warninglists;
private $fastLookupArrayMispFormat = [];
private $fastLookupArrayFlattened = [];
public function __construct()
@ -54,7 +52,6 @@ class Module_attach_warninglist extends WorkflowBaseActionModule
if ($matchingItems === false) {
return true;
}
$this->_buildFastLookupForRoamingData($rData);
$warninglists = [];
if ($params['warninglists']['value'] == 'ALL') {
@ -84,34 +81,4 @@ class Module_attach_warninglist extends WorkflowBaseActionModule
$roamingData->setData($rData);
return true;
}
protected function _buildFastLookupForRoamingData($rData): void
{
foreach ($rData['Event']['Attribute'] as $i => $attribute) {
$this->fastLookupArrayMispFormat[$attribute['id']] = $i;
}
foreach ($rData['Event']['Object'] as $j => $object) {
foreach ($object['Attribute'] as $i => $attribute) {
$this->fastLookupArrayMispFormat[$attribute['id']] = [$j, $i];
}
}
foreach ($rData['Event']['_AttributeFlattened'] as $i => $attribute) {
$this->fastLookupArrayFlattened[$attribute['id']] = $i;
}
}
protected function _overrideAttribute(array $oldAttribute, array $newAttribute, array $rData): array
{
$attributeID = $oldAttribute['id'];
$rData['Event']['_AttributeFlattened'][$this->fastLookupArrayFlattened[$attributeID]] = $newAttribute;
if (is_array($this->fastLookupArrayMispFormat[$attributeID])) {
$objectID = $this->fastLookupArrayMispFormat[$attributeID][0];
$attributeID = $this->fastLookupArrayMispFormat[$attributeID][1];
$rData['Event']['Object'][$objectID]['Attribute'][$attributeID] = $newAttribute;
} else {
$attributeID = $this->fastLookupArrayMispFormat[$attributeID];
$rData['Event']['Attribute'][$attributeID] = $newAttribute;
}
return $rData;
}
}

View File

@ -0,0 +1,61 @@
<?php
include_once APP . 'Model/WorkflowModules/action/Module_attribute_edition_operation.php';
class Module_attribute_comment_operation extends Module_attribute_edition_operation
{
public $version = '0.1';
public $blocking = false;
public $id = 'Module_attribute_comment_operation';
public $name = 'Attribute comment operation';
public $description = 'Set the Attribute\'s comment to the selected value';
public $icon = 'edit';
public $inputs = 1;
public $outputs = 1;
public $support_filters = true;
public $expect_misp_core_format = true;
public $params = [];
public function __construct()
{
parent::__construct();
$this->params = [
[
'id' => 'comment',
'label' => __('Comment'),
'type' => 'textarea',
'placeholder' => 'Comment to be set',
],
];
}
public function exec(array $node, WorkflowRoamingData $roamingData, array &$errors = []): bool
{
parent::exec($node, $roamingData, $errors);
$params = $this->getParamsWithValues($node);
$rData = $roamingData->getData();
$user = $roamingData->getUser();
$matchingItems = $this->getMatchingItemsForAttributes($node, $rData);
if ($matchingItems === false) {
return true;
}
$result = $this->__saveAttributes($matchingItems, $rData, $params, $user);
$success = $result['success'];
$updatedRData = $result['updated_rData'];
$roamingData->setData($updatedRData);
return $success;
}
protected function _editAttribute(array $attribute, array $rData, array $params): array
{
$currentRData = $rData;
$currentRData['__currentAttribute'] = $attribute;
$renderedComment = $this->render_jinja_template($params['comment']['value'], $currentRData);
if ($attribute['comment'] !== $params['comment']['value']) {
$attribute['comment'] = $renderedComment;
}
return $attribute;
}
}

View File

@ -36,15 +36,21 @@ class Module_attribute_edition_operation extends WorkflowBaseActionModule
return $attribute;
}
protected function __saveAttribute(array $attributes, array $rData, array $params, array $user): bool
protected function __saveAttributes(array $attributes, array $rData, array $params, array $user): array
{
$success = false;
foreach ($attributes as $attribute) {
$attribute = $this->_editAttribute($attribute, $rData, $params);
unset($attribute['timestamp']);
$saveSuccess = $this->Attribute->editAttribute($attribute, $rData, $user, $attribute['object_id']);
$newAttribute = $this->_editAttribute($attribute, $rData, $params);
unset($newAttribute['timestamp']);
$saveSuccess = $this->Attribute->editAttribute($newAttribute, $rData, $user, $newAttribute['object_id']);
if ($saveSuccess) {
$rData = $this->_overrideAttribute($attribute, $newAttribute, $rData);
}
$success = $success || !empty($saveSuccess);
}
return $success;
return [
'success' => $success,
'updated_rData' => $rData,
];
}
}

View File

@ -45,8 +45,11 @@ class Module_attribute_ids_flag_operation extends Module_attribute_edition_opera
if ($matchingItems === false) {
return true;
}
$result = $this->__saveAttribute($matchingItems, $rData, $params, $user);
return $result;
$result = $this->__saveAttributes($matchingItems, $rData, $params, $user);
$success = $result['success'];
$updatedRData = $result['updated_rData'];
$roamingData->setData($updatedRData);
return $success;
}
protected function _editAttribute(array $attribute, array $rData, array $params): array

View File

@ -5,7 +5,7 @@ class Module_ms_teams_webhook extends Module_webhook
{
public $id = 'ms-teams-webhook';
public $name = 'MS Teams Webhook';
public $version = '0.3';
public $version = '0.4';
public $description = 'Perform callbacks to the MS Teams webhook provided by the "Incoming Webhook" connector';
public $icon_path = 'MS_Teams.png';
@ -31,7 +31,7 @@ class Module_ms_teams_webhook extends Module_webhook
[
'id' => 'data_extraction_path',
'label' => 'Data extraction path',
'type' => 'input',
'type' => 'hashpath',
'default' => '',
'placeholder' => 'Attribute.{n}.AttributeTag.{n}.Tag.name',
],

View File

@ -6,6 +6,7 @@ class Module_push_zmq extends WorkflowBaseActionModule
public $blocking = false;
public $id = 'push-zmq';
public $name = 'Push to ZMQ';
public $version = '0.2';
public $description = 'Push to the ZMQ channel';
public $icon_path = 'zeromq.png';
public $inputs = 1;
@ -19,7 +20,7 @@ class Module_push_zmq extends WorkflowBaseActionModule
[
'id' => 'data_extraction_path',
'label' => 'Data extraction path',
'type' => 'input',
'type' => 'hashpath',
'default' => '',
'placeholder' => 'Attribute.{n}.AttributeTag.{n}.Tag.name',
],

View File

@ -130,7 +130,7 @@ class Module_tag_operation extends WorkflowBaseActionModule
return $result;
}
private function __addTagsToAttributes(array $attributes, array $options, array $user): bool
protected function __addTagsToAttributes(array $attributes, array $options, array $user): bool
{
$success = false;
foreach ($attributes as $attribute) {
@ -140,7 +140,7 @@ class Module_tag_operation extends WorkflowBaseActionModule
return $success;
}
private function __removeTagsFromAttributes(array $attributes, array $options): bool
protected function __removeTagsFromAttributes(array $attributes, array $options): bool
{
$success = false;
foreach ($attributes as $attribute) {
@ -150,12 +150,12 @@ class Module_tag_operation extends WorkflowBaseActionModule
return $success;
}
private function __addTagsToEvent(array $event, array $options, array $user): bool
protected function __addTagsToEvent(array $event, array $options, array $user): bool
{
return !empty($this->Event->attachTagsToEventAndTouch($event['Event']['id'], $options, $user));
}
private function __removeTagsFromEvent(array $event, array $options): bool
protected function __removeTagsFromEvent(array $event, array $options): bool
{
return !empty($this->Event->detachTagsFromEventAndTouch($event['Event']['id'], $options));
}

View File

@ -0,0 +1,184 @@
<?php
include_once APP . 'Model/WorkflowModules/action/Module_tag_operation.php';
class Module_tag_replacement_generic extends Module_tag_operation
{
public $version = '0.1';
public $blocking = false;
public $id = 'tag_replacement_generic';
public $name = 'Tag Replacement Generic';
public $description = 'Toggle or remove the IDS flag on selected attributes.';
public $icon = 'tags';
public $inputs = 1;
public $outputs = 1;
public $support_filters = true;
public $expect_misp_core_format = true;
public $params = [];
public $searchRegex = '';
public $substitutionTemplate = ''; // Format the template using CakeText::insert where variables are surround by `{{ var }}`
public function __construct()
{
parent::__construct();
$this->params = [
[
'id' => 'scope',
'label' => __('Scope'),
'type' => 'select',
'options' => [
'event' => __('Event'),
'attribute' => __('Attributes'),
'all' => __('All'),
],
'default' => 'event',
],
[
'id' => 'remove_substituted',
'label' => 'Removed substituted tag',
'type' => 'select',
'default' => '1',
'options' => [
'no' => __('No'),
'yes' => __('Yes'),
],
],
[
'id' => 'locality',
'label' => __('Tag Locality'),
'type' => 'select',
'options' => [
'local' => __('Local'),
'global' => __('Global'),
],
'default' => 'local',
],
[
'id' => 'relationship_type',
'label' => __('Relationship Type'),
'type' => 'input',
'display_on' => [
'action' => 'add',
],
],
];
}
public function exec(array $node, WorkflowRoamingData $roamingData, array &$errors = []): bool
{
$params = $this->getParamsWithValues($node);
$rData = $roamingData->getData();
$user = $roamingData->getUser();
if ($this->filtersEnabled($node)) {
$filters = $this->getFilters($node);
$extracted = $this->extractData($rData, $filters['selector']);
if ($extracted === false) {
return false;
}
$matchingItems = $this->getItemsMatchingCondition($extracted, $filters['value'], $filters['operator'], $filters['path']);
} else {
$matchingItems = $rData;
}
$matchingEvent = $matchingItems;
$matchingAttributes = [];
if ($params['scope']['value'] == 'attribute' || $params['scope']['value'] == 'all') {
$matchingAttributes = Hash::extract($matchingItems, 'Event._AttributeFlattened.{n}');
}
if (empty($matchingItems)) {
return true;
}
$result = false;
$optionsRemove = [
'local' => [0, 1],
];
$optionsAdd = [
'local' => $params['locality']['value'] == 'local' ? true : false,
'relationship_type' => $params['relationship_type']['value'],
];
if ($params['scope']['value'] == 'event' || $params['scope']['value'] == 'all') {
$result = $this->replaceOnEvent($matchingEvent, $params, $user, $optionsRemove, $optionsAdd);
}
if ($params['scope']['value'] == 'attribute' || $params['scope']['value'] == 'all') {
$result = $this->replaceOnAttribute($matchingAttributes, $params, $user, $optionsRemove, $optionsAdd);
}
return $result;
}
protected function replaceOnEvent(array $matchingItems, array $params, array $user, array $optionsRemove, array $optionsAdd): bool
{
$result = true;
$extractedTags = Hash::extract($matchingItems['Event']['Tag'], '{n}.name');
$options = $this->getReplacementOptions($extractedTags);
$optionsRemove['tags'] = $options['remove'];
$optionsAdd['tags'] = $options['add'];
if ($params['remove_substituted']['value'] == 'yes' && !empty($optionsRemove['tags'])) {
$result = $this->__removeTagsFromEvent($matchingItems, $optionsRemove);
}
if (!empty($optionsAdd['tags'])) {
$result = $this->__addTagsToEvent($matchingItems, $optionsAdd, $user);
}
return $result;
}
protected function replaceOnAttribute(array $matchingItems, array $params, array $user, array $optionsRemove, array $optionsAdd): bool
{
$result = true;
foreach ($matchingItems as $attribute) {
$extractedTags = Hash::extract($attribute['Tag'], '{n}.name');
$options = $this->getReplacementOptions($extractedTags);
$optionsRemove['tags'] = $options['remove'];
$optionsAdd['tags'] = $options['add'];
if ($params['remove_substituted']['value'] == 'yes' && !empty($optionsRemove['tags'])) {
$result = $this->__removeTagsFromAttributes([$attribute], $optionsRemove);
}
if (!empty($optionsAdd['tags'])) {
$result = $this->__addTagsToAttributes([$attribute], $optionsAdd, $user);
}
}
return $result;
}
protected function isAMatch($matches): bool
{
return !empty($matches);
}
protected function searchAndReplaceTag(array $tags): array
{
$toReturn = [];
foreach ($tags as $tag) {
$matches = [];
preg_match($this->searchRegex, $tag, $matches);
if ($this->isAMatch($matches)) {
$toReturn[] = [
'matched' => $tag,
'substitution' => $this->formatSubstitution($matches),
];
}
}
return $toReturn;
}
protected function getReplacementOptions(array $extractedTags)
{
$substitutionResult = $this->searchAndReplaceTag($extractedTags);
$tagsToRemove = Hash::extract($substitutionResult, '{n}.matched');
$tagsToAdd = Hash::extract($substitutionResult, '{n}.substitution');
return [
'remove' => $tagsToRemove,
'add' => $tagsToAdd,
];
}
protected function formatSubstitution($matches)
{
return CakeText::insert($this->substitutionTemplate, $matches, ['before' => '{{', 'after' => '}}']);
}
}

View File

@ -0,0 +1,36 @@
<?php
include_once APP . 'Model/WorkflowModules/action/Module_tag_replacement_generic.php';
class Module_tag_replacement_pap extends Module_tag_replacement_generic
{
public $version = '0.1';
public $blocking = false;
public $id = 'tag_replacement_pap';
public $name = 'Tag Replacement - PAP';
public $description = 'Attach a tag (or substitue) a tag by another for the PAP taxonomy';
public $icon = 'tags';
public $inputs = 1;
public $outputs = 1;
public $support_filters = true;
public $expect_misp_core_format = true;
public $params = [];
public $searchRegex = '/(\w*pap\w*|permissible\W+actions\W+protocol)([:\s\-=]{1,2})"?(?P<predicate>white|clear|green|amber|red)"?/i';
protected function isAMatch($matches): bool
{
$namespace = 'pap';
$predicates = ['white', 'clear', 'green', 'amber', 'red'];
if (empty($matches)) {
return false;
}
return strtolower($matches[1]) == $namespace && in_array(strtolower($matches['predicate']), $predicates);
}
protected function formatSubstitution($matches)
{
return sprintf('PAP:%s', strtoupper($matches['predicate']));
}
}

View File

@ -0,0 +1,36 @@
<?php
include_once APP . 'Model/WorkflowModules/action/Module_tag_replacement_generic.php';
class Module_tag_replacement_tlp extends Module_tag_replacement_generic
{
public $version = '0.1';
public $blocking = false;
public $id = 'tag_replacement_tlp';
public $name = 'Tag Replacement - TLP';
public $description = 'Attach a tag (or substitue) a tag by another for the TLP taxonomy';
public $icon = 'tags';
public $inputs = 1;
public $outputs = 1;
public $support_filters = true;
public $expect_misp_core_format = true;
public $params = [];
public $searchRegex = '/(\w*tlp\w*|traffic\W+light\W+protocol)([:\s\-=]{1,2})"?(?P<predicate>white|clear|green|amber|amber\+strict|red)"?/i';
protected function isAMatch($matches): bool
{
$namespace = 'tlp';
$predicates = ['white', 'clear', 'green', 'amber', 'amber+strict', 'red'];
if (empty($matches)) {
return false;
}
return strtolower($matches[1]) == $namespace && in_array(strtolower($matches['predicate']), $predicates);
}
protected function formatSubstitution($matches)
{
return sprintf('tlp:%s', strtolower($matches['predicate']));
}
}

View File

@ -8,7 +8,7 @@ class Module_webhook extends WorkflowBaseActionModule
{
public $id = 'webhook';
public $name = 'Webhook';
public $version = '0.3';
public $version = '0.4';
public $description = 'Allow to perform custom callbacks to the provided URL';
public $icon_path = 'webhook.png';
public $inputs = 1;
@ -42,7 +42,7 @@ class Module_webhook extends WorkflowBaseActionModule
[
'id' => 'data_extraction_path',
'label' => __('Data extraction path'),
'type' => 'input',
'type' => 'hashpath',
'default' => '',
'placeholder' => 'Attribute.{n}.AttributeTag.{n}.Tag.name',
],

View File

@ -5,6 +5,7 @@ class Module_generic_filter_data extends WorkflowFilteringLogicModule
{
public $id = 'generic-filter-data';
public $name = 'Filter :: Generic';
public $version = '0.2';
public $description = 'Generic data filtering block. The module filters incoming data and forward the matching data to its output.';
public $icon = 'filter';
public $inputs = 1;
@ -16,6 +17,8 @@ class Module_generic_filter_data extends WorkflowFilteringLogicModule
'not_in' => 'Not in',
'equals' => 'Equals',
'not_equals' => 'Not equals',
'any_value' => 'Any value',
'in_or' => 'Any value from',
];
public function __construct()
@ -27,6 +30,7 @@ class Module_generic_filter_data extends WorkflowFilteringLogicModule
'label' => __('Filtering Label'),
'type' => 'select',
'options' => $this->_genFilteringLabels(),
'default' => array_keys($this->_genFilteringLabels())[0],
],
[
'id' => 'selector',
@ -39,6 +43,19 @@ class Module_generic_filter_data extends WorkflowFilteringLogicModule
'label' => __('Value'),
'type' => 'input',
'placeholder' => 'tlp:red',
'display_on' => [
'operator' => ['in', 'not_in', 'equals', 'not_equals',],
],
],
[
'id' => 'value_list',
'label' => __('Value list'),
'type' => 'picker',
'picker_create_new' => true,
'placeholder' => '[\'ip-src\', \'ip-dst\']',
'display_on' => [
'operator' => 'in_or',
],
],
[
'id' => 'operator',
@ -50,7 +67,7 @@ class Module_generic_filter_data extends WorkflowFilteringLogicModule
[
'id' => 'hash_path',
'label' => __('Hash path'),
'type' => 'input',
'type' => 'hashpath',
'placeholder' => 'Tag.name',
],
];
@ -64,6 +81,8 @@ class Module_generic_filter_data extends WorkflowFilteringLogicModule
$path = $params['hash_path']['value'];
$operator = $params['operator']['value'];
$value = $params['value']['value'];
$value_list = $params['value_list']['value'];
$valueToEvaluate = $operator == 'in_or' ? $value_list : $value;
$filteringLabel = $params['filtering-label']['value'];
$rData = $roamingData->getData();
@ -75,7 +94,7 @@ class Module_generic_filter_data extends WorkflowFilteringLogicModule
'selector' => $selector,
'path' => $path,
'operator' => $operator,
'value' => $value,
'value' => $valueToEvaluate,
];
$roamingData->setData($newRData);

View File

@ -5,6 +5,7 @@ class Module_generic_if extends WorkflowBaseLogicModule
{
public $id = 'generic-if';
public $name = 'IF :: Generic';
public $version = '0.2';
public $description = 'Generic 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;
@ -17,6 +18,8 @@ class Module_generic_if extends WorkflowBaseLogicModule
'not_in' => 'Not in',
'equals' => 'Equals',
'not_equals' => 'Not equals',
'any_value' => 'Any value',
'in_or' => 'Any value from',
];
public function __construct()
@ -28,6 +31,19 @@ class Module_generic_if extends WorkflowBaseLogicModule
'label' => 'Value',
'type' => 'input',
'placeholder' => 'tlp:red',
'display_on' => [
'operator' => ['in', 'not_in', 'equals', 'not_equals',],
],
],
[
'id' => 'value_list',
'label' => __('Value list'),
'type' => 'picker',
'picker_create_new' => true,
'placeholder' => '[\'ip-src\', \'ip-dst\']',
'display_on' => [
'operator' => 'in_or',
],
],
[
'id' => 'operator',
@ -39,7 +55,7 @@ class Module_generic_if extends WorkflowBaseLogicModule
[
'id' => 'hash_path',
'label' => 'Hash path',
'type' => 'input',
'type' => 'hashpath',
'placeholder' => 'Attribute.{n}.Tag',
],
];
@ -52,6 +68,8 @@ class Module_generic_if extends WorkflowBaseLogicModule
$path = $params['hash_path']['value'];
$operator = $params['operator']['value'];
$value = $params['value']['value'];
$value_list = $params['value_list']['value'];
$valueToEvaluate = $operator == 'in_or' ? $value_list : $value;
$data = $roamingData->getData();
$extracted = [];
if ($operator == 'equals' || $operator == 'not_equals') {
@ -59,7 +77,10 @@ class Module_generic_if extends WorkflowBaseLogicModule
} else {
$extracted = Hash::extract($data, $path);
}
$eval = $this->evaluateCondition($extracted, $operator, $value);
if ($operator == 'any_value' && !empty($extracted)) {
return true;
}
$eval = $this->evaluateCondition($extracted, $operator, $valueToEvaluate);
return !empty($eval);
}
}

View File

@ -250,7 +250,8 @@ echo $this->element('/genericElements/IndexTable/index_table', [
return $this->Acl->canModifyEvent($object) && empty($object['Event']['publish_timestamp']);
},
]
]
],
'persistUrlParams' => ['results']
]
]);

View File

@ -133,7 +133,7 @@ $data_passed_to_if_module = [
</ul>
</ul>
<p><strong><?= __('Sample:') ?></strong></p>
<pre>
<pre id="misp-core-format-sample">
{
"Event": {
"id": "64",

View File

@ -40,6 +40,9 @@
} else {
$params['class'] = '';
}
if (!empty($fieldData['autofocus'])) {
$params['autofocus'] = 1;
}
if (empty($fieldData['type']) || $fieldData['type'] !== 'checkbox' ) {
$params['class'] .= ' form-control';
}

View File

@ -465,7 +465,7 @@
array(
'text' => __('Audit Logs'),
'url' => $baseurl . '/admin/audit_logs/index',
'requirement' => Configure::read('MISP.log_new_audit') && $isAdmin,
'requirement' => Configure::read('MISP.log_new_audit') && $this->Acl->canAccess('auditLogs', 'admin_index'),
),
array(
'text' => __('Access Logs'),
@ -475,7 +475,7 @@
array(
'text' => __('Search Logs'),
'url' => $baseurl . '/admin/logs/search',
'requirement' => $isAdmin
'requirement' => $this->Acl->canAccess('logs', 'admin_search')
)
)
),

View File

@ -229,8 +229,8 @@
}
?>
</td>
<td class="short">
<input type="text" class="ObjectComment" style="padding:0;height:20px;margin-bottom:0;" placeholder="<?php echo h($importComment); ?>"<?php if (!empty($object['comment'])) echo ' value="' . h($object['comment']) . '"';?>>
<td class="bitwider">
<textarea class="ObjectComment inline-input" cols="30" rows="6" placeholder="<?php echo h($importComment); ?>" oninput="autoresize(this)"><?php if (!empty($object['comment'])) echo h($object['comment']);?></textarea>
</td>
<td style="width:60px;text-align:center;">
<select class="ObjectDistribution" style="padding:0;height:20px;margin-bottom:0;">
@ -302,8 +302,8 @@
<td class="short" style="width:40px;text-align:center;">
<input type="checkbox" class="AttributeDisableCorrelation"<?php if (!empty($attribute['disable_correlation'])) echo ' checked'; ?>>
</td>
<td class="short">
<input type="text" class="AttributeComment" style="padding:0;height:20px;margin-bottom:0;"<?php if (!empty($attribute['comment'])) echo ' value="' . h($attribute['comment']) . '"';?>>
<td class="bitwider">
<textarea class="AttributeComment inline-input" rows="1" oninput="autoresize(this)"><?php if (!empty($attribute['comment'])) echo h($attribute['comment']);?></textarea>
</td>
<td class="short" style="width:40px;text-align:center;">
<select class="AttributeDistribution" style="padding:0;height:20px;margin-bottom:0;">
@ -412,8 +412,8 @@
<td class="short" style="width:40px;text-align:center;">
<input type="checkbox" class="AttributeDisableCorrelation"<?php if (isset($attribute['disable_correlation']) && $attribute['disable_correlation']) echo ' checked'; ?>>
</td>
<td class="short">
<input type="text" class="AttributeComment" style="padding:0;height:20px;margin-bottom:0;" placeholder="<?php echo h($importComment); ?>"<?php if (!empty($attribute['comment'])) echo ' value="' . h($attribute['comment']) . '"';?>>
<td class="bitwider">
<textarea class="AttributeComment inline-input" rows="1" oninput="autoresize(this)" placeholder="<?php echo h($importComment); ?>"><?php if (!empty($attribute['comment'])) echo h($attribute['comment']);?></textarea>
</td>
<td class="short" style="width:40px;text-align:center;">
<select class="AttributeDistribution" style="padding:0;height:20px;margin-bottom:0;">

View File

@ -13,6 +13,11 @@ class LightPaginatorHelper extends PaginatorHelper
return '';
}
public function last($last = 'last >>', $options = array())
{
return '';
}
public function hasNext($model = null)
{
$model = $this->defaultModel();

18
app/View/Users/forgot.ctp Normal file
View File

@ -0,0 +1,18 @@
<?php
echo $this->element('genericElements/Form/genericForm', [
'data' => [
'description' => __("If you already are enrolled on this instance, but forgot your password, you can request a new password below.") . '<br />' . __("An e-mail containing URL with an embedded token will be sent to you that you can use to reset the password within 10 minutes."),
'model' => 'User',
'title' => __('Forgotten password'),
'fields' => [
[
'field' => 'email',
'class' => 'span6'
],
],
'submit' => [
'action' => $this->request->params['action'],
'ajaxSubmit' => 'submitGenericFormInPlace();'
]
]
]);

View File

@ -59,6 +59,15 @@
__('No account yet? Register now!')
);
?>
<div class="clear">
<?php
echo empty(Configure::read('Security.allow_password_forgotten')) ? '' : sprintf(
'<a href="%s/users/forgot" title="%s">%s</a>',
$baseurl,
__('Initiate a password reset.'),
__('I have forgotten my password')
);
?>
</div>
<?= $this->Form->button(__('Login'), array('class' => 'btn btn-primary')); ?>
<?php

View File

@ -19,6 +19,7 @@ echo $this->element('/genericElements/Form/genericForm', array(
"label" => $label,
"type" => "text",
"placeholder" => __("Enter your OTP here"),
"autofocus" => 1
)
),
"submit" => array (

View File

@ -0,0 +1,25 @@
<div class="users form">
<?php echo $this->Form->create('User');?>
<fieldset>
<legend><?php echo __('Reset password'); ?></legend>
<?php
$passwordPopover = '<span class="blue bold">' . __('Minimal length') . '</span>: ' . h($length) . '<br>';
$passwordPopover .= '<span class="blue bold">' . __('Complexity') . '</span>: ' . h($complexity);
echo $this->Form->input('password', array(
'label' => __('New password') . ' <span id="PasswordPopover" data-content="' . h($passwordPopover) . '" class="fas fa-info-circle"></span>', 'autofocus'
));
echo $this->Form->input('confirm_password', [
'type' => 'password',
'label' => __('Confirm new password'),
'div' => array('class' => 'input password required'),
]);
?>
</fieldset>
<div style="border-bottom: 1px solid #e5e5e5;width:100%;">&nbsp;</div>
<div class="clear" style="margin-top:10px;">
</div>
<?php
echo $this->Form->button(__('Submit'), array('class' => 'btn btn-primary'));
echo $this->Form->end();
?>
</div>

View File

@ -71,6 +71,12 @@ $debugEnabled = !empty($selectedWorkflow['Workflow']['debug_enabled']);
<div class="main-container">
<div class="sidebar">
<div class="side-panel">
<span class="sidebar-minimize-button">
<i class="<?= $this->FontAwesome->getClass('angle-double-left') ?>"></i>
</span>
<span class="sidebar-maximize-button">
<i class="<?= $this->FontAwesome->getClass('angle-double-right') ?>"></i>
</span>
<ul class="nav nav-tabs" id="block-tabs">
<li class="active">
<a href="#container-actions">
@ -173,6 +179,9 @@ $debugEnabled = !empty($selectedWorkflow['Workflow']['debug_enabled']);
<button id="control-duplicate" class="btn btn-small btn-primary disabled" type="button" title="<?= __('Duplicate') ?>">
<i class="fa-fw <?= $this->FontAwesome->getClass('clone') ?>"></i> <?= __('Duplicate') ?>
</button>
<button id="control-frame-node" class="btn btn-small btn-primary disabled" type="button" title="<?= __('Create frame node') ?>">
<i class="fa-fw <?= $this->FontAwesome->getClass('object-group') ?>"></i> <?= __('Frame') ?>
</button>
<button id="control-delete" class="btn btn-small btn-danger disabled" type="button" title="<?= __('Delete') ?>">
<i class="fa-fw <?= $this->FontAwesome->getClass('trash') ?>"></i> <?= __('Delete') ?>
</button>
@ -184,7 +193,7 @@ $debugEnabled = !empty($selectedWorkflow['Workflow']['debug_enabled']);
<a href="#"><i class="fa-fw <?= $this->FontAwesome->getClass('file-import') ?>"></i> <?= __('Import blueprint') ?></a>
<ul class="dropdown-menu pull-right">
<?php if (empty($workflowBlueprints)) : ?>
<li><a href="#"><?= _('No workflow blueprints saved') ?></a></li>
<li><a href="#"><?= __('No workflow blueprints saved') ?></a></li>
<?php endif; ?>
<?php foreach ($workflowBlueprints as $workflowBlueprint) : ?>
<li>
@ -202,7 +211,7 @@ $debugEnabled = !empty($selectedWorkflow['Workflow']['debug_enabled']);
<a href="#"><i class="fa-fw <?= $this->FontAwesome->getClass('edit') ?>"></i> <?= __('Edit existing blueprint') ?></a>
<ul class="dropdown-menu pull-right disabled">
<?php if (empty($workflowBlueprints)) : ?>
<li><a href="#"><?= _('No workflow blueprints saved') ?></a></li>
<li><a href="#"><?= __('No workflow blueprints saved') ?></a></li>
<?php endif; ?>
<?php foreach ($workflowBlueprints as $workflowBlueprint) : ?>
<li class="control-edit-bp-blocks">
@ -303,6 +312,7 @@ echo $this->element('genericElements/assetLoader', [
var $blockNotificationModal = $('#block-notifications-modal')
var $blockFilteringModal = $('#block-filtering-modal')
var $controlDuplicateButton = $('.control-buttons #control-duplicate')
var $controlFrameNodeButton = $('.control-buttons #control-frame-node')
var $controlDeleteButton = $('.control-buttons #control-delete')
var $controlExportBlocksLi = $('.control-buttons #control-export-blocks')
var $controlSaveBlocksLi = $('.control-buttons #control-save-blocks')
@ -335,5 +345,11 @@ echo $this->element('genericElements/assetLoader', [
$(document).ready(function() {
initDrawflow()
$('.sidebar-minimize-button').click(function() {
$(this).closest('.sidebar').addClass('minimized')
})
$('.sidebar-maximize-button').click(function() {
$(this).closest('.sidebar').removeClass('minimized')
})
})
</script>

View File

@ -113,7 +113,7 @@
'element' => 'checkbox_action',
'onclick' => "enableWorkflowDebugMode(%s, %s)",
'onclick_params_data_path' => ['Workflow.id', 'Workflow.debug_enabled'],
'title' => __('Put the workflow in debug mode. Each nodes will send data to the provided debug URL')
'title' => __('Set the workflow in debug mode. Each nodes will send data to the provided debug URL')
],
[
'name' => __('Enabled'),

View File

@ -168,7 +168,7 @@
"uuid": "53a830e6-8d07-4dd8-98d5-18cc4e66fc8c",
"org_uuid": "",
"org_name": "",
"description": "The ultimate goal of the project is to develop a NATO capability, available to all NATO nations, through which nations commit to sharing their information. This community is only open for official government entities, sponsored by their nation representative in the NATO Multinational MISP Steering Board.",
"description": "The ultimate goal of the project is to develop a NATO capability, available to all NATO nations, through which nations commit to sharing their information. This community is only open for official government cyber defense related entities, sponsored by their nation representative in the NATO Multinational MISP Steering Board.",
"url": "https://misp.ncirc.nato.int",
"sector": "Governmental",
"nationality": "International",

View File

@ -1663,5 +1663,31 @@
"exportable": true,
"hide_tag": false
}
},
{
"Feed": {
"name": "James Brine Bruteforce IPs",
"provider": "jamesbrine.com.au",
"url": "https://jamesbrine.com.au/csv",
"rules": "{\"tags\":{\"OR\":[],\"NOT\":[]},\"orgs\":{\"OR\":[],\"NOT\":[]},\"type_attributes\":{\"NOT\":[]},\"type_objects\":{\"NOT\":[]},\"url_params\":\"\"}",
"enabled": true,
"distribution": "3",
"default": false,
"source_format": "csv",
"fixed_event": true,
"delta_merge": false,
"publish": false,
"override_ids": false,
"settings": "{\"disable_correlation\":\"0\",\"csv\":{\"value\":\"1\",\"delimiter\":\"\"},\"common\":{\"excluderegex\":\"\"}}",
"input_source": "network",
"delete_local_file": false,
"lookup_visible": false
},
"Tag": {
"name": "osint:source-type=\"block-or-filter-list\"",
"colour": "#004f89",
"exportable": true,
"hide_tag": false
}
}
]

@ -1 +1 @@
Subproject commit 734d57edf5e76900cd0c8d5d48d6f5910e29b84e
Subproject commit f5729ac23a7dcb8fc9dc3194a3e125484c515742

@ -1 +1 @@
Subproject commit 2ca2667d7668067f906e9601e0c97a79d4c7ccf1
Subproject commit 4da05293d723ad6f9db4a3e349e140daa5d2a28d

@ -1 +1 @@
Subproject commit 0b5455752686408bd4e1c3e7c22359cf2460db04
Subproject commit a12d40a4ff8a19c0a6b9f4d719c9549e805108c5

@ -1 +1 @@
Subproject commit bb5d823ee4d4cfce445bfbf89de8e013c169a3d2
Subproject commit 59ec473a5f7a44755a6098890a1ee290487bfc53

@ -1 +1 @@
Subproject commit 911aafb91a38a68bbf6f5474c06e77a039469c93
Subproject commit 11101527c0e55810613d3d753f2e770219895c39

View File

@ -41,6 +41,11 @@
flex-direction: column;
position: absolute;
top: var(--topbar-height)px;
left: 0;
transition: left 0.15s ease-out;
}
.sidebar.minimized {
left: calc(-1 * var(--editor-sidepanel-width));
}
.side-panel {
@ -63,6 +68,31 @@
top: 0;
}
.sidebar-minimize-button {
position: absolute;
top: 3px;
right: 5px;
cursor: pointer;
}
.sidebar-maximize-button {
position: absolute;
font-size: 1.25rem;
top: 3px;
right: -32px;
cursor: pointer;
background-color: #fff;
padding: 5px;
border-radius: 3px;
border: 1px #e3e3e3 solid;
display: none;
opacity: 0;
transition: opacity 0.15s ease-out;
}
.sidebar.minimized .sidebar-maximize-button {
display: inline-block;
opacity: 1;
}
.side-panel #block-tabs {
margin-bottom: 10px;
}
@ -345,7 +375,76 @@
border: 3px solid #e35651;
}
.drawflow svg.connection > path.link-hover-for-insertion {
stroke: #7210cd;
filter: drop-shadow(0px 0px 6px #7210cddd);
}
#core-format-picker {
padding: 5px;
border-radius: 5px;
background-color: #f9f9f9;
border: 1px solid #e1e1e1;
}
#core-format-picker .selectable-key {
font-weight: bold;
padding: 2px 3px;
}
#core-format-picker .selectable-key:hover {
box-shadow: 0 0 5px #00000077;
cursor: pointer;
}
#core-format-picker .selectable-value {
padding: 2px 3px;
}
#core-format-picker .selectable-value:hover {
box-shadow: 0 0 5px #00000077;
font-weight: bold;
cursor: pointer;
}
#core-format-picker .children-counter {
background-color: #6b6b6b;
color: #f2f2f2;
padding: 2px 5px;
margin: 0px 0.25rem;
font-size: smaller;
border-radius: 3px;
}
#core-format-picker .collaspe-button {
cursor: pointer;
}
#core-format-picker .collaspe-button:hover {
color: #292929;
cursor: pointer;
}
/* Custom input/output CSS */
/* .drawflow .drawflow-node.expect-misp-core-format>.inputs>.input {
background-color: #2fa1db;
} */
.drawflow .drawflow-framenode {
position: absolute;
z-index: -1;
text-align: center;
background-color: #b5b5b570;
border: 1px solid #333333aa;
border-radius: 5px;
}
.drawflow .drawflow-framenode.selected {
box-shadow: 0 0 5px 2px #33333366;
}
.drawflow .drawflow-framenode > .drawflow-framenode-text {
line-height: 1.5em;
color: #505050;
font-size: 1.5em;
font-weight: bold;
cursor: text;
}
.drawflow .drawflow-node > .inputs > .input::before {
line-height: var(--dfInputHeight);
vertical-align: top;

View File

@ -6,6 +6,7 @@ class Drawflow {
this.nodeId = 1;
this.ele_selected = null;
this.node_selected = null;
this.framenode_selected = null;
this.drag = false;
this.reroute = false;
this.reroute_fix_curvature = false;
@ -32,10 +33,12 @@ class Drawflow {
this.draggable_inputs = true;
this.useuuid = false;
this.parent = parent;
this.frame_to_nodes_registry = {};
this.nodes_to_frames_registry = {};
this.noderegister = {};
this.render = render;
this.drawflow = { "drawflow": { "Home": { "data": {} } } };
this.drawflow = { "drawflow": { "Home": { "data": { "_frames": {} } } } };
// Configurable options
this.module = 'Home';
this.editor_mode = 'edit';
@ -44,6 +47,7 @@ class Drawflow {
this.zoom_min = 0.5;
this.zoom_value = 0.1;
this.zoom_last_value = 1;
this.frameNodePadding = { "top": 100, "left": 30, "right": 30, "bottom": 20 }
// Mobile
this.evCache = new Array();
@ -139,7 +143,9 @@ class Drawflow {
/* End Mobile Zoom */
load() {
for (var key in this.drawflow.drawflow[this.module].data) {
this.addNodeImport(this.drawflow.drawflow[this.module].data[key], this.precanvas);
if (!key.startsWith('_')) {
this.addNodeImport(this.drawflow.drawflow[this.module].data[key], this.precanvas);
}
}
if (this.reroute) {
@ -212,6 +218,10 @@ class Drawflow {
this.removeReouteConnectionSelected();
this.connection_selected = null;
}
if (this.framenode_selected != null) {
this.framenode_selected.classList.remove("selected");
this.framenode_selected = null
}
if (this.node_selected != this.ele_selected) {
this.dispatch('nodeSelected', this.ele_selected.id.slice(5));
}
@ -239,8 +249,30 @@ class Drawflow {
this.removeReouteConnectionSelected();
this.connection_selected = null;
}
if (this.framenode_selected != null) {
this.framenode_selected.classList.remove("selected");
this.framenode_selected = null
}
this.drawConnection(e.target);
break;
case 'drawflow-framenode':
if (this.node_selected != null) {
this.node_selected.classList.remove("selected");
this.node_selected = null
this.dispatch('nodeUnselected', true);
}
if (this.connection_selected != null) {
this.connection_selected.classList.remove("selected");
this.removeReouteConnectionSelected();
this.connection_selected = null;
}
if (this.framenode_selected != null) {
this.framenode_selected.classList.remove("selected");
this.framenode_selected = null
}
this.framenode_selected = this.ele_selected;
this.framenode_selected.classList.add("selected");
break;
case 'parent-drawflow':
if (this.node_selected != null) {
this.node_selected.classList.remove("selected");
@ -252,6 +284,10 @@ class Drawflow {
this.removeReouteConnectionSelected();
this.connection_selected = null;
}
if (this.framenode_selected != null) {
this.framenode_selected.classList.remove("selected");
this.framenode_selected = null
}
this.editor_selected = true;
break;
case 'drawflow':
@ -265,6 +301,10 @@ class Drawflow {
this.removeReouteConnectionSelected();
this.connection_selected = null;
}
if (this.framenode_selected != null) {
this.framenode_selected.classList.remove("selected");
this.framenode_selected = null
}
this.editor_selected = true;
break;
case 'main-path':
@ -278,6 +318,10 @@ class Drawflow {
this.removeReouteConnectionSelected();
this.connection_selected = null;
}
if (this.framenode_selected != null) {
this.framenode_selected.classList.remove("selected");
this.framenode_selected = null
}
this.connection_selected = this.ele_selected;
this.connection_selected.classList.add("selected");
const listclassConnection = this.connection_selected.parentElement.classList;
@ -303,6 +347,10 @@ class Drawflow {
this.removeConnection();
}
if (this.framenode_selected) {
this.removeFrameNodeId(this.framenode_selected.id);
}
if (this.node_selected != null) {
this.node_selected.classList.remove("selected");
this.node_selected = null;
@ -313,6 +361,10 @@ class Drawflow {
this.removeReouteConnectionSelected();
this.connection_selected = null;
}
if (this.framenode_selected != null) {
this.framenode_selected.classList.remove("selected");
this.framenode_selected = null
}
break;
default:
@ -543,6 +595,21 @@ class Drawflow {
}
} else {
if (e.target.classList.contains('drawflow-framenode') || e.target.classList.contains('drawflow-framenode-text')) {
const frameNode = e.target.classList.contains('drawflow-framenode') ? e.target : e.target.parentElement
const deletebox = document.createElement('div');
deletebox.classList.add("drawflow-delete");
deletebox.innerHTML = "x";
const frameNodeBCR = frameNode.getBoundingClientRect()
const pos_y = frameNodeBCR.top - 30 // roughly include margin and node size
const pos_x = frameNodeBCR.left + frameNodeBCR.width
deletebox.style.top = pos_y * (this.precanvas.clientHeight / (this.precanvas.clientHeight * this.zoom)) - (this.precanvas.getBoundingClientRect().y * (this.precanvas.clientHeight / (this.precanvas.clientHeight * this.zoom))) + "px";
deletebox.style.left = pos_x * (this.precanvas.clientWidth / (this.precanvas.clientWidth * this.zoom)) - (this.precanvas.getBoundingClientRect().x * (this.precanvas.clientWidth / (this.precanvas.clientWidth * this.zoom))) + "px";
this.precanvas.appendChild(deletebox);
}
}
}
@ -1092,6 +1159,16 @@ class Drawflow {
}
})
var nodeIdInt = parseInt(id.slice(5));
if (this.nodes_to_frames_registry[nodeIdInt] !== undefined && this.nodes_to_frames_registry[nodeIdInt].length > 0) {
var _this = this;
this.nodes_to_frames_registry[nodeIdInt].forEach((frameNodeUuid) => {
const containedNodeIDs = _this.frame_to_nodes_registry[frameNodeUuid]
const frameNode = _this.container.querySelector('#framenode-' + frameNodeUuid)
_this.updateFrameNodePosition(frameNode, containedNodeIDs);
})
}
}
dblclick(e) {
@ -1516,6 +1593,116 @@ class Drawflow {
});
}
addFrameNode(frameNodeConfig) {
frameNodeConfig.nodes = [...new Set(frameNodeConfig.nodes)]
if (frameNodeConfig.nodes.length == 0) {
return
}
var newNodeId = this.getUuid();
this.frame_to_nodes_registry[newNodeId] = frameNodeConfig.nodes
frameNodeConfig.nodes.forEach(nodeId => {
if (this.nodes_to_frames_registry[nodeId] === undefined) {
this.nodes_to_frames_registry[nodeId] = []
}
this.nodes_to_frames_registry[nodeId].push(newNodeId)
});
const parent = document.createElement('div');
parent.classList.add(...["parent-framenode"]);
const frameNode = document.createElement('div');
frameNode.innerHTML = '';
frameNode.setAttribute('id', 'framenode-' + newNodeId);
frameNode.classList.add(...['drawflow-framenode']);
if (frameNodeConfig.class !== undefined && frameNodeConfig.class != '') {
frameNode.classList.add(...frameNodeConfig.class.split(' '));
}
this.updateFrameNodePosition(frameNode, frameNodeConfig.nodes)
const frameTextNode = document.createElement('span');
frameTextNode.innerText = frameNodeConfig.text;
frameTextNode.classList.add('drawflow-framenode-text')
frameNode.appendChild(frameTextNode);
parent.appendChild(frameNode);
this.precanvas.appendChild(parent);
var json = {
id: newNodeId,
text: frameNodeConfig.text,
nodes: frameNodeConfig.nodes,
class: frameNodeConfig.class,
}
this.drawflow.drawflow[this.module].data._frames[newNodeId] = json;
this.dispatch('frameNodeCreated', newNodeId);
}
updateFrameNodePosition(frameNode, nodeIDs) {
if (!frameNode) {
return
}
var _this = this
var nodesHtml = nodeIDs.map(function (id) {
return _this.container.querySelector('#node-' + id)
})
function getFrameCoordinatesFromNodes(nodesHtml) {
var topLeft = { x: Number.MAX_SAFE_INTEGER, y: Number.MAX_SAFE_INTEGER }
var bottomRight = { x: Number.MIN_SAFE_INTEGER, y: Number.MIN_SAFE_INTEGER }
nodesHtml.forEach(function (nodeHtml) {
var bcr = nodeHtml.getBoundingClientRect()
topLeft.x = Math.min(topLeft.x, bcr.left)
topLeft.y = Math.min(topLeft.y, bcr.top)
bottomRight.x = Math.max(bottomRight.x, bcr.left + bcr.width)
bottomRight.y = Math.max(bottomRight.y, bcr.top + bcr.height)
})
return { topLeft: topLeft, bottomRight: bottomRight }
}
var frameCoordinates = getFrameCoordinatesFromNodes(nodesHtml)
var pos_x = frameCoordinates.topLeft.x - this.frameNodePadding.left / 2
var pos_y = frameCoordinates.topLeft.y - this.frameNodePadding.top / 2
var frameNodeHeight = (frameCoordinates.bottomRight.y - frameCoordinates.topLeft.y) + (this.frameNodePadding.top / 2 + this.frameNodePadding.bottom)
var frameNodeWidth = (frameCoordinates.bottomRight.x - frameCoordinates.topLeft.x) + (this.frameNodePadding.left / 2 + this.frameNodePadding.right)
var transpositionFactorX = (this.precanvas.clientWidth / (this.precanvas.clientWidth * this.zoom))
var transpositionOffsetX = -(this.precanvas.getBoundingClientRect().x * (this.precanvas.clientWidth / (this.precanvas.clientWidth * this.zoom)))
var transpositionFactorY = (this.precanvas.clientHeight / (this.precanvas.clientHeight * this.zoom))
var transpositionOffsetY = -(this.precanvas.getBoundingClientRect().y * (this.precanvas.clientHeight / (this.precanvas.clientHeight * this.zoom)))
pos_x = pos_x * transpositionFactorX + transpositionOffsetX
pos_y = pos_y * transpositionFactorY + transpositionOffsetY
frameNodeWidth = frameNodeWidth * transpositionFactorX
frameNodeHeight = frameNodeHeight * transpositionFactorY
frameNode.style.top = pos_y + 'px';
frameNode.style.left = pos_x + 'px';
frameNode.style.height = frameNodeHeight + 'px';
frameNode.style.width = frameNodeWidth + 'px';
}
updateFramenode(frameNodeUuid, frameNodeConfig) {
const frameNode = document.getElementById(frameNodeUuid);
frameNode.classList.remove(...frameNode.classList)
frameNode.classList.add(...['drawflow-framenode']);
if (frameNodeConfig.class !== undefined && frameNodeConfig.class != '') {
frameNode.classList.add(...frameNodeConfig.class.split(' '));
}
const frameTextNode = frameNode.querySelector('.drawflow-framenode-text')
frameTextNode.innerText = frameNodeConfig.text
var oldJson = this.drawflow.drawflow[this.module].data._frames[frameNodeUuid.slice(10)]
oldJson.text = frameNodeConfig.text
oldJson.class = frameNodeConfig.class
this.drawflow.drawflow[this.module].data._frames[frameNodeUuid.slice(10)] = oldJson;
this.dispatch('frameNodeUpdated', frameNodeUuid.slice(10));
}
updateNodeValue(event) {
var attr = event.target.attributes
for (var i = 0; i < attr.length; i++) {
@ -1769,6 +1956,25 @@ class Drawflow {
if (this.module === moduleName) {
this.container.querySelector(`#${id}`).remove();
}
var _this = this
var new_frame_to_nodes_registry = []
if (this.nodes_to_frames_registry[id.slice(5)] !== undefined) {
this.nodes_to_frames_registry[id.slice(5)].forEach((frameUuid) => {
new_frame_to_nodes_registry = _this.frame_to_nodes_registry[frameUuid].filter((nodeId) => {
return id.slice(5) != nodeId;
})
if (new_frame_to_nodes_registry.length == 0) {
_this.removeFrameNodeId('framenode-' + frameUuid)
} else {
const frameNode = _this.container.querySelector('#framenode-' + frameUuid)
_this.frame_to_nodes_registry[frameUuid] = new_frame_to_nodes_registry
_this.updateFrameNodePosition(frameNode, _this.frame_to_nodes_registry[frameUuid]);
}
})
delete this.nodes_to_frames_registry[id.slice(5)]
}
delete this.drawflow.drawflow[moduleName].data[id.slice(5)];
this.dispatch('nodeRemoved', id.slice(5));
}
@ -1874,6 +2080,21 @@ class Drawflow {
}
}
removeFrameNodeId(fullID) {
const frameUuid = fullID.slice(10);
const moduleName = this.getModuleFromNodeId(this.frame_to_nodes_registry[frameUuid][0])
this.container.querySelector(`#${fullID}`).remove();
var _this = this;
this.frame_to_nodes_registry[frameUuid].forEach((nodeID) => {
_this.nodes_to_frames_registry[nodeID] = _this.nodes_to_frames_registry[nodeID].filter((frameNodeUuid) => {
return frameNodeUuid != frameUuid;
})
})
delete this.frame_to_nodes_registry[frameUuid];
delete this.drawflow.drawflow[moduleName].data._frames[frameUuid];
this.dispatch('frameNodeRemoved', frameUuid);
}
getModuleFromNodeId(id) {
var nameModule;
const editor = this.drawflow.drawflow
@ -1888,7 +2109,7 @@ class Drawflow {
}
addModule(name) {
this.drawflow.drawflow[name] = { "data": {} };
this.drawflow.drawflow[name] = { "data": { "_frames": {} } };
this.dispatch('moduleCreated', name);
}
changeModule(name) {
@ -1917,12 +2138,16 @@ class Drawflow {
clearModuleSelected() {
this.precanvas.innerHTML = "";
this.drawflow.drawflow[this.module] = { "data": {} };
this.drawflow.drawflow[this.module] = { "data": { "_frames": {}} };
this.frame_to_nodes_registry = {};
this.nodes_to_frames_registry = {};
}
clear() {
this.precanvas.innerHTML = "";
this.drawflow = { "drawflow": { "Home": { "data": {} } } };
this.drawflow = { "drawflow": { "Home": { "data": { "_frames": {} } } } };
this.frame_to_nodes_registry = {};
this.nodes_to_frames_registry = {};
}
export() {
const dataExport = JSON.parse(JSON.stringify(this.drawflow));

View File

@ -1998,9 +1998,9 @@ function choicePopup(legend, list) {
openPopup("#popover_form");
}
function openModal(heading, body, footer, modal_option, css_container, css_body) {
function openModal(heading, body, footer, modal_option, css_container, css_body, class_container) {
var modal_id = 'dynamic_modal_' + new Date().getTime();
var modal_html = '<div id="' + modal_id + '" class="modal hide fade" style="' + (css_container !== undefined ? css_container : '') + '" tabindex="-1" role="dialog" aria-hidden="true">';
var modal_html = '<div id="' + modal_id + '" class="modal hide fade ' + (class_container !== undefined ? class_container : '') + '" style="' + (css_container !== undefined ? css_container : '') + '" tabindex="-1" role="dialog" aria-hidden="true">';
if (heading !== undefined && heading !== '') {
modal_html += '<div class="modal-header">'
+ '<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>'

View File

@ -143,6 +143,11 @@ var iconBySeverity = {
'error': 'fa-exclamation-circle',
}
var severities = ['info', 'warning', 'error']
var haspathQuickPickMenu = [
{ 'name': 'All Attributes', 'path': 'Event._AttributeFlattened.{n}' },
{ 'name': 'All tags attached to all Attributes', 'path': 'Event._AttributeFlattened.{n}.Tag.{n}.name' },
{ 'name': 'All tags attached to the Event', 'path': 'Event.Tag.{n}.name' },
]
var workflow_id = 0
var contentChanged = false
@ -170,6 +175,21 @@ function initDrawflow() {
editor.on('nodeRemoved', function () {
invalidateContentCache()
})
editor.on('frameNodeCreated', function (framenodeUuid) {
invalidateContentCache()
var frameNodeFullUuid = 'framenode-' + framenodeUuid
$('#' + frameNodeFullUuid).find('.drawflow-framenode-text').dblclick(function() {
var existingText = $(this).text()
var newText = prompt('Edit frame node text', existingText)
editor.updateFramenode(frameNodeFullUuid, {text: newText})
})
})
editor.on('frameNodeUpdated', function () {
invalidateContentCache()
})
editor.on('frameNodeRemoved', function () {
invalidateContentCache()
})
editor.on('nodeDataChanged', invalidateContentCache)
editor.on('nodeMoved', invalidateContentCache)
editor.on('connectionCreated', function() {
@ -199,6 +219,9 @@ function initDrawflow() {
duplicateSelection()
evt.preventDefault()
}
if (evt.keyCode == 70 && $drawflow.is(evt.target)) {
createFrameNodeForSelected()
}
})
editor.translate_to = function (x, y) {
this.canvas_x = x;
@ -304,9 +327,33 @@ function initDrawflow() {
if (ui.draggable.data('blueprint')) {
addWorkflowBlueprint(ui.draggable.data('blueprint').WorkflowBlueprint.id, ui.position)
} else {
addNode(ui.draggable.data('module'), ui.position)
var node = addNode(ui.draggable.data('module'), ui.position)
var fakeUi = {
draggable: ui.helper,
position: { left: ui.helper[0].getBoundingClientRect().left, top: ui.helper[0].getBoundingClientRect().top }
}
var link = getLinkUnderElement(fakeUi)
if (link !== undefined) {
insertNodeOnLink(node, link)
}
}
},
activate: function (event, ui) {
var $pathsWithClass = $()
ui.helper.mousemove(function (event) {
var fakeUi = {
draggable: $(this),
position: {left: this.getBoundingClientRect().left, top: this.getBoundingClientRect().top}
}
var links = getLinkUnderElement(fakeUi)
$pathsWithClass.removeClass('link-hover-for-insertion')
if (links) {
$paths = $(links).find('path')
$paths.addClass('link-hover-for-insertion')
$pathsWithClass = $paths
}
})
},
});
graphPooler = new TaskScheduler(checkGraphProperties, {
@ -386,6 +433,7 @@ function initDrawflow() {
})
editor.on('nodeSelected', function(node_id) {
$controlDuplicateButton.removeClass('disabled')
$controlFrameNodeButton.removeClass('disabled')
$controlDeleteButton.removeClass('disabled')
$controlSaveBlocksLi.removeClass('disabled')
$controlEditBlocksLiContainer.removeClass('disabled').find('.dropdown-menu')
@ -397,6 +445,7 @@ function initDrawflow() {
})
selection.clearSelection()
$controlDuplicateButton.addClass('disabled')
$controlFrameNodeButton.addClass('disabled')
$controlDeleteButton.addClass('disabled')
$controlSaveBlocksLi.addClass('disabled')
$controlEditBlocksLiContainer.addClass('disabled').find('.dropdown-menu')
@ -444,6 +493,9 @@ function initDrawflow() {
$controlDuplicateButton.click(function() {
duplicateSelection()
})
$controlFrameNodeButton.click(function() {
createFrameNodeForSelected()
})
$controlDeleteButton.click(function() {
deleteSelectedNodes(false)
})
@ -545,6 +597,26 @@ function duplicateSelection() {
selection.select(newNodes)
}
function createFrameNodeForSelected() {
var text = prompt('Enter text for the frame node')
var selectedNodesHtml = selection.getSelection()
var selectedIDs = selectedNodesHtml.map(function(nodeHtml) {
return parseInt(nodeHtml.id.slice(5))
})
createFrameForNodes(selectedIDs, text)
selection.clearSelection()
invalidateContentCache()
}
function createFrameForNodes(nodesIDs, text) {
const frameNode = {
nodes: nodesIDs,
text: text,
class: "",
}
editor.addFrameNode(frameNode)
}
function buildModalForBlock(node_id, node) {
var html = genNodeParamHtml(node, false)
$blockModal
@ -652,6 +724,9 @@ function addNode(block, position, additionalData={}) {
if (newNode.data.module_data.module_type == 'logic') {
blockClass.push('block-type-logic')
}
if (newNode.data.module_data.expect_misp_core_format) {
blockClass.push('expect-misp-core-format')
}
editor.addNode(
newNode.name,
module.inputs === undefined ? 1 : module.inputs,
@ -663,11 +738,14 @@ function addNode(block, position, additionalData={}) {
html
)
afterNodeDrawCallback()
return editor.getNodeFromId(editor.nodeId-1)
}
function getEditorData(cleanNodes) {
var data = {} // Make sure nodes are index by their internal IDs
var editorExport = editor.export().drawflow.Home.data
var frameNodes = editorExport._frames !== undefined ? editorExport._frames : {}
delete editorExport._frames
editorExport = Array.isArray(editorExport) ? editorExport : Object.values(editorExport)
editorExport.forEach(function(node) {
if (node !== null) { // for some reason, the editor create null nodes
@ -692,6 +770,7 @@ function getEditorData(cleanNodes) {
data[node.id] = node
}
})
data._frames = frameNodes
return data
}
@ -723,7 +802,12 @@ function loadWorkflow(workflow) {
}
// We cannot rely on the editor's import function as it recreates the nodes with the saved HTML instead of rebuilding them
// We have to manually add the nodes and their connections
Object.values(workflow.data).forEach(function (node) {
Object.entries(workflow.data).forEach(function (entry) {
var i = entry[0]
var node = entry[1]
if (i == '_frames') {
return
}
var module = all_modules_by_id[node.data.id] || all_triggers_by_id[node.data.id]
if (!module) {
console.error('Tried to add node for unknown module ' + node.data.id + ' (' + node.id + ')')
@ -754,6 +838,9 @@ function loadWorkflow(workflow) {
if (newNode.data.module_data.module_type == 'logic') {
nodeClass.push('block-type-logic')
}
if (newNode.data.module_data.expect_misp_core_format) {
nodeClass.push('expect-misp-core-format')
}
if (newNode.data.module_data.disabled) {
nodeClass.push('disabled')
}
@ -782,6 +869,10 @@ function loadWorkflow(workflow) {
})
}
})
var frameNodes = workflow.data._frames || {}
Object.values(frameNodes).forEach(function (frameNode) {
editor.addFrameNode(frameNode)
})
}
function filterModules(clicked) {
@ -844,7 +935,8 @@ function duplicateNodesFromHtml(currentSelection) {
oldNewIDMapping[node_id],
oldNewIDMapping[connection.node],
outputName,
connection.output
connection.output,
[]
)
}
});
@ -910,7 +1002,8 @@ function addNodesFromBlueprint(workflowBlueprint, cursorPosition) {
oldNewIDMapping[node.id],
oldNewIDMapping[connection.node],
outputName,
connection.output
connection.output,
[]
)
}
});
@ -1161,41 +1254,43 @@ function runWorkflow() {
container: 'body',
template: '<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"><div class="data-content"></div></div></div>'
}
$runWorkflowButton
.popover(popoverOptions)
.on('shown.bs.popover', function () {
var $popover = $runWorkflowButton.data('popover').tip()
$popover.find('button').click(function() {
var url = baseurl + "/workflows/executeWorkflow/" + workflow.Workflow.id
fetchFormDataAjax(url, function (formHTML) {
$('body').append($('<div id="temp" style="display: none"/>').html(formHTML))
var $tmpForm = $('#temp form')
var formUrl = $tmpForm.attr('action')
data = $popover.find('textarea').val()
$tmpForm.find('[name="data[Workflow][data]"]').val(data)
$.ajax({
data: $tmpForm.serialize(),
beforeSend: function() {
$popover.find('pre').empty()
$popover.find('button i').removeClass('hidden')
},
success: function (data) {
$popover.find('pre').text(data)
},
error: xhrFailCallback,
complete: function () {
$('#temp').remove();
$popover.find('button i').addClass('hidden')
},
type: 'post',
cache: false,
url: formUrl,
if ($runWorkflowButton.data().popover === undefined) {
$runWorkflowButton
.popover(popoverOptions)
.on('shown.bs.popover', function () {
var $popover = $runWorkflowButton.data('popover').tip()
$popover.find('button').click(function() {
var url = baseurl + "/workflows/executeWorkflow/" + workflow.Workflow.id
fetchFormDataAjax(url, function (formHTML) {
$('body').append($('<div id="temp" style="display: none"/>').html(formHTML))
var $tmpForm = $('#temp form')
var formUrl = $tmpForm.attr('action')
data = $popover.find('textarea').val()
$tmpForm.find('[name="data[Workflow][data]"]').val(data)
$.ajax({
data: $tmpForm.serialize(),
beforeSend: function() {
$popover.find('pre').empty()
$popover.find('button i').removeClass('hidden')
},
success: function (data) {
$popover.find('pre').text(data)
},
error: xhrFailCallback,
complete: function () {
$('#temp').remove();
$popover.find('button i').addClass('hidden')
},
type: 'post',
cache: false,
url: formUrl,
})
})
})
})
})
$runWorkflowButton.popover('show')
.popover('show')
}
}
function getSelectedNodeID() {
@ -1217,7 +1312,16 @@ function getSelectedNode() {
}
function deleteSelectedNode() {
editor.removeNodeId(getSelectedNodeID())
deleteNodeByID(getSelectedNodeID())
}
function deleteNodeByID(nodeId) {
nodeIdInt = nodeId.slice(5)
editorData = getEditorData()
if (all_triggers_by_id[editorData[nodeIdInt].data.id]) {
return
}
editor.removeNodeId(nodeId)
}
function getNodeFromContainedHtml(htmlNode) {
@ -1232,7 +1336,7 @@ function deleteSelectedNodes(fromDelKey) {
if (fromDelKey && getSelectedNodeID() !== null && getSelectedNodeID() == node.id) {
return // This node will be removed by drawflow delete callback
}
editor.removeNodeId(node.id)
deleteNodeByID(node.id)
})
editor.dispatch('nodeUnselected')
}
@ -1254,10 +1358,94 @@ function addWorkflowBlueprint(blueprintId, cursorPosition) {
if (newNodes.length > 0) {
selection.clearSelection()
selection.select(newNodes)
var newNodeIDs = newNodes.map(function(node) {
return node.id.slice(5)
})
createFrameForNodes(newNodeIDs, workflowBlueprint.WorkflowBlueprint.name)
editor.dispatch('nodeSelected', newNodes[0].id);
}
}
function getLinkUnderElement(ui) {
var orig_pos_x = ui.position.left
var orig_pos_y = ui.position.top
var elementHeight = ui.draggable[0].clientHeight
var elementCenterY = ui.position.top + ui.draggable[0].clientHeight / 2
// Credit: Drawflow example page
var transpositionFactorX = (editor.precanvas.clientWidth / (editor.precanvas.clientWidth * editor.zoom))
var transpositionOffsetX = -(editor.precanvas.getBoundingClientRect().x * (editor.precanvas.clientWidth / (editor.precanvas.clientWidth * editor.zoom)))
var transpositionFactorY = (editor.precanvas.clientHeight / (editor.precanvas.clientHeight * editor.zoom))
var transpositionOffsetY = -(editor.precanvas.getBoundingClientRect().y * (editor.precanvas.clientHeight / (editor.precanvas.clientHeight * editor.zoom)))
var pos_x = orig_pos_x * transpositionFactorX + transpositionOffsetX
var pos_y = orig_pos_y * transpositionFactorY + transpositionOffsetY
var transposedHeight = (orig_pos_y + elementHeight) * transpositionFactorY + transpositionOffsetY
var transposedCenterY = elementCenterY * transpositionFactorY + transpositionOffsetY
var $allConnectionMiddlePoint = $drawflow.find('svg.connection > foreignObject')
var allValidLinks = []
$allConnectionMiddlePoint.each(function() {
var middlePointY = this.y.baseVal.value
if (pos_y <= middlePointY && middlePointY <= transposedHeight) {
var $svg = this.parentElement.childNodes[0]
var svgBR = $svg.getBoundingClientRect()
var linkTransposedLeftPosition = svgBR.x * transpositionFactorX + transpositionOffsetX
var linkTransposedRightPosition = (svgBR.x + svgBR.width) * transpositionFactorX + transpositionOffsetX
if (linkTransposedLeftPosition <= pos_x && pos_x <= linkTransposedRightPosition) {
allValidLinks.push(this.parentElement)
}
}
})
if (allValidLinks.length == 1) {
return allValidLinks[0]
} else if (allValidLinks.length > 1) {
// sort based on distance between link middle point and element input
function calcDistance(pt1, pt2) {
return Math.sqrt((pt2.x - pt1.x)**2 + (pt2.y - pt1.y)**2)
}
var elementInputPosition = { x: pos_x, y: transposedCenterY }
var allValidLinksSorted = allValidLinks.sort(function (link1, link2) {
var middlePoint1 = {
x: $(link1).find('foreignObject')[0].x.baseVal.value,
y: $(link1).find('foreignObject')[0].y.baseVal.value,
}
var middlePoint2 = {
x: $(link2).find('foreignObject')[0].x.baseVal.value,
y: $(link2).find('foreignObject')[0].y.baseVal.value,
}
var distance1 = calcDistance(elementInputPosition, middlePoint1)
var distance2 = calcDistance(elementInputPosition, middlePoint2)
return distance1 <= distance2 ? -1 : 1
});
return allValidLinksSorted[0]
}
return
}
function insertNodeOnLink(newNode, link) {
var defaultConnectionName = 'output_1'
var ids = getIDsFromSvgLink(link)
var nodeIn = ids.nodeIn
var inConnectionName = ids.inConnectionName
var nodeOut = ids.nodeOut
var outConnectionName = ids.outConnectionName
editor.addConnection(nodeOut, newNode.id, outConnectionName, inConnectionName, [])
editor.addConnection(newNode.id, nodeIn, defaultConnectionName, inConnectionName, [])
editor.removeSingleConnection(nodeOut, nodeIn, outConnectionName, inConnectionName)
}
function getIDsFromSvgLink(link) {
return {
'nodeIn': parseInt(Array.from(link.classList).filter(c => c.startsWith('node_in_node-'))[0].replace('node_in_node-', '')),
'inConnectionName': Array.from(link.classList).filter(c => c.startsWith('input_'))[0],
'nodeOut': parseInt(Array.from(link.classList).filter(c => c.startsWith('node_out_node-'))[0].replace('node_out_node-', '')),
'outConnectionName': Array.from(link.classList).filter(c => c.startsWith('output_'))[0],
}
}
/* UI Utils */
function toggleSaveButton(enabled) {
$saveWorkflowButton
@ -1323,6 +1511,9 @@ function genNodeParamHtml(node, forNode = true) {
case 'input':
paramHtml = genInput(param, false, forNode)[0].outerHTML
break;
case 'hashpath':
paramHtml = genHashpathInput(param, false, forNode)[0].outerHTML
break;
case 'textarea':
paramHtml = genInput(param, true, forNode)[0].outerHTML
break;
@ -1348,12 +1539,20 @@ function genNodeParamHtml(node, forNode = true) {
function afterNodeDrawCallback() {
var $nodes = $drawflow.find('.drawflow-node')
$nodes.find('.start-chosen').chosen()
$nodes.find('.start-chosen').each(function() {
var chosenOptions = $(this).data('chosen_options')
$(this).chosen(chosenOptions)
})
toggleDisplayOnFields()
enablePickerCreateNewOptions()
enableHashpathPicker()
}
function afterModalShowCallback() {
$blockModal.find('.start-chosen').chosen()
$blockModal.find('.start-chosen').each(function() {
var chosenOptions = $(this).data('chosen_options')
$(this).chosen(chosenOptions)
})
var cmOptions = {
theme: 'default',
lineNumbers: true,
@ -1389,7 +1588,9 @@ function toggleDisplayOnFields() {
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) {
if (Array.isArray(target_param_values) && target_param_values.includes(node_param_value)) {
showContainer = true
} else if (target_param_values == node_param_value) {
showContainer = true
}
});
@ -1402,6 +1603,89 @@ function toggleDisplayOnFields() {
})
}
function enablePickerCreateNewOptions() {
var $nodes = $drawflow.find('.drawflow-node')
$nodes.find('.start-chosen[picker_create_new="1"]').each(function () {
var $select = $(this)
var $input = $select.parent().find('.chosen-search-input')
$input.on('keydown', function(evt) {
if (evt.which == 13) { // <ENTER>
var newVal = $input.val()
var optionExists = $select.find('option').filter(function () {
return $(this).val() == newVal
}).length > 0
if (!optionExists) {
var $newOption = $('<option>')
.val(newVal)
.text(newVal)
$select.append($newOption);
$select.trigger('chosen:updated');
}
}
})
})
}
function enableHashpathPicker() {
var $nodes = $drawflow.find('.drawflow-node')
$nodes.find('.hashpath-picker-container').each(function () {
$(this).find('.hashpath-quick-picker').click(function () {
$(this).closest('.input-append').find('input')
.val($(this).data('hashpath'))
.trigger('input')
})
$(this).find('.hashpath-format-picker').click(function() {
toggleCoreFormatPicker(this)
})
})
}
function toggleCoreFormatPicker(btn) {
var associatedParamId = $(btn).closest('.input-append').find('input').data('paramid')
var sample = JSON.parse($('#misp-core-format-sample').text())
var UIPicker = generateCoreFormatUI(sample, associatedParamId)
var $selectedPath = $('<input>')
.attr({
id: 'selected-hashpath-input',
type: 'text',
placeholder: 'Click on a elemet on the JSON to show the path',
onchange: 'setValueOnAssociatedInput(this.value, "' + associatedParamId + '")',
})
.css({ margin: '0 0.75em 0 0', 'flex-grow': 2 })
var $selectedPathOperators = $('<select>')
.attr({
id: 'selected-hashpath-operator',
onchange: 'setHashpathOnInput(this, "' + associatedParamId + '")',
})
.css({margin: '0', 'max-width': '15%'})
var pathOperators = [
{ name: 'Has key []', stringToFormat: '[{$key}]' },
{ name: 'Match [=]', stringToFormat: '[{$key}={$value}]', default: true },
{ name: 'Match [!=]', stringToFormat: '[{$key}!={$value}]' },
{ name: 'Match [>]', stringToFormat: '[{$key}>{$value}]' },
{ name: 'Match [>=]', stringToFormat: '[{$key}>={$value}]' },
{ name: 'Match [<]', stringToFormat: '[{$key}<{$value}]' },
{ name: 'Match [<=]', stringToFormat: '[{$key}<={$value}]' },
{ name: 'Regex match [/.../]', stringToFormat: '[{$key}=/\S*\{$value}\S*/]' },
]
pathOperators.forEach((opt) => {
var $option = $('<option>')
.val(opt.stringToFormat)
.text(opt.name)
if (opt.default === true) {
$option.attr('selected', 'selected')
}
$selectedPathOperators.append($option)
})
$pathGroup = $('<span>').css({'display': 'flex', 'width': '100%'})
.append($selectedPathOperators, $selectedPath)
var $closeButton = $('<div>').append($('<a href="#" class="btn" data-dismiss="modal">Close</a>'))
var $footer = $('<div>').css({display: 'flex'}).append($pathGroup, $closeButton)
openModal('Pick Hash path', UIPicker[0].outerHTML, $footer[0].outerHTML, undefined, undefined, 'max-height: 70vh;', 'modal-lg')
}
function genParameterWarning(options) {
var text = '', text_short = ''
if (options.is_invalid) {
@ -1445,6 +1729,24 @@ function genSelect(options, forNode = true) {
$select.prop('multiple', true)
$select.attr('size', 1)
}
if (options.picker_create_new) {
options.multiple = true
$select.attr('picker_create_new', 1)
$select.prop('multiple', true)
if (!options.options) {
options.options = []
}
if (options.value) {
if (Array.isArray(options.value)) {
options.options = options.options.concat(options.value)
} else {
options.options.push(options.value)
}
}
}
if (options.disabled !== undefined) {
$select.prop('disabled', options.disabled == true)
}
var selectOptions = options.options
if (!Array.isArray(selectOptions)) {
selectOptions = Object.keys(options.options).map((k) => { return { name: options.options[k], value: k } })
@ -1493,6 +1795,10 @@ function genPicker(options, forNode = true) {
var $container = genSelect(options)
var $select = $container.find('select')
$select.addClass('start-chosen')
if (options.picker_options) {
// $select.data('chosen_options', options.picker_options)
$select.attr('data-chosen_options', JSON.stringify(options.picker_options))
}
return $container
}
@ -1537,11 +1843,91 @@ function genInput(options, isTextArea, forNode = true) {
if (options.placeholder !== undefined) {
$input.attr('placeholder', options.placeholder)
}
if (options.disabled !== undefined) {
$input.prop('disabled', options.disabled == true)
}
$label.append($input)
$container.append($label)
return $container
}
function genHashpathInput(options, forNode = true) {
function hashPathGenDropdownMenu() {
var $divider = $('<li>').addClass('divider')
var $dropdownMenu = $('<ul>').addClass('dropdown-menu pull-right')
var $liPicker = $('<li>').append(
$('<a>')
.attr({
'tabindex': '-1',
'href': '#',
})
.addClass('hashpath-format-picker')
.text('Show picker')
)
$dropdownMenu.append($liPicker)
$dropdownMenu.append($divider)
haspathQuickPickMenu.forEach((entry) => {
var $li = $('<li>').append(
$('<a>')
.attr({
'tabindex': '-1',
'href': '#',
'data-hashpath': entry.path
})
.addClass('hashpath-quick-picker')
.text(entry.name)
)
$dropdownMenu.append($li)
})
return $dropdownMenu
}
var $container = $('<div>')
.addClass('node-param-container')
.attr('param-id', options.id)
if (options.display_on) {
$container.addClass('display-on')
}
var $addonContainer = $('<div>')
.addClass('input-append')
.css({'display': 'flex'})
var $label = $('<label>')
.css({
marginLeft: '0.25em',
marginBbottom: 0,
})
.append(
$('<span>').text(options.label),
genParameterWarning(options)
)
var $input = $('<input>').attr('type', 'text').css({ height: '30px' })
$input.css({
'flex-grow': '1',
'box-sizing': 'border-box',
})
$input
.attr('oninput', 'handleInputChange(this)')
.attr('data-paramid', options.param_id)
$input.attr('value', options.value !== undefined ? options.value : options.default)
if (options.placeholder !== undefined) {
$input.attr('placeholder', options.placeholder)
}
if (options.disabled !== undefined) {
$input.prop('disabled', options.disabled == true)
}
var $dropdownContainer = $('<div>').addClass(['btn-group', 'hashpath-picker-container'])
var $dropdownButton = $('<button>')
.addClass(['btn', 'dropdown-toggle'])
.attr('data-toggle', 'dropdown')
.text('Pick ')
.append($('<span>').addClass('caret'))
var $dropdownMenu = hashPathGenDropdownMenu()
$dropdownContainer.append($dropdownButton, $dropdownMenu)
$addonContainer.append($input, $dropdownContainer)
$container.append($label, $addonContainer)
return $container
}
function genCheckbox(options, forNode = true) {
var $label = $('<label>')
.css({
@ -1564,6 +1950,9 @@ function genCheckbox(options, forNode = true) {
} else if (options.default) {
$input.attr('checked', '')
}
if (options.disabled !== undefined) {
$input.prop('disabled', options.disabled == true)
}
$label.append($input)
var $container = $('<div>')
.addClass('node-param-container')
@ -1620,6 +2009,9 @@ function genRadio(options, forNode = true) {
} else if (options.default) {
$input.attr('checked', '')
}
if (options.disabled !== undefined) {
$input.prop('disabled', options.disabled == true)
}
var $label = $('<label>')
.addClass('radio')
.css({
@ -1947,4 +2339,200 @@ function highlightGraphIssues(graphProperties) {
highlightAcyclic(graphProperties.is_acyclic)
highlightMultipleOutputConnection(graphProperties.multiple_output_connection)
highlightPathWarning(graphProperties.path_warnings)
}
function setHashpathOnInput(clicked, associatedParamId, isValue) {
var path = $(clicked).data('hashpath')
if (isValue === true) {
var key = $(clicked).data('hashpath-key')
var value = $(clicked).data('hashpath-value')
path += getPathFiltering(key, value)
}
$('#selected-hashpath-input').val(path)
setValueOnAssociatedInput(associatedParamId, path)
}
function setValueOnAssociatedInput(associatedParamId, value) {
var $associatedInput = $drawflow.find('input[data-paramid="' + associatedParamId + '"]')
$associatedInput.val(value)
$associatedInput.trigger('input')
}
function getPathFiltering(key, value) {
var stringToFormat = $('#selected-hashpath-operator').val()
var filter = stringToFormat
.replace('{$key}', key)
.replace('{$value}', value)
return filter
}
function generateCoreFormatUI(event, associatedParamId) {
var indent_size = 'calc(1.25em)'
var color_key = '#005cd5'
var color_index = '#727272'
var color_string = '#cf5900'
var color_null = '#747474'
var color_bool = '#004bad'
var color_brace = '#727272'
var color_column = '#727272'
var color_collapse = '#727272'
var defaultCollapseList = ['Org', 'Orgc']
function generate(item, depth, path, forceObjectCollaspe) {
if (Array.isArray(item)) {
var $container = $('<span>').append(
depth == 1 ? '' : braceOpen(true),
depth == 1 ? '' : childrenCount(item),
genArray(item, depth, path),
depth == 1 ? '' : braceClose(true),
)
if (depth > 2) {
$container.children("div").toggleClass("hidden")
}
} else if (typeof item === 'object' && item !== null) {
var $container = $('<span>').append(
depth == 1 ? '' : braceOpen(),
depth == 1 ? '' : childrenCount(item),
genObject(item, depth, path),
depth == 1 ? '' : braceClose(),
)
if (forceObjectCollaspe === true) {
$container.children("div").toggleClass("hidden")
}
} else {
var $container = genValue(item, path)
}
return $container
}
function genArray(arr, depth, path) {
var $container = $('<div>')
arr.forEach(function (v, i) {
var nextPath = path + '.{n}'
var $index = genIndex(i, nextPath)
var $value = generate(v, depth + 1, nextPath)
var $div = $('<div>')
$div.append($index, column(), $value)
$container.append($div)
})
setDepth($container, depth, path)
return $container
}
function genObject(obj, depth, path) {
var $container = $('<div>')
Object.keys(obj).forEach(function (k) {
var nextPath = path + '.' + k
var v = obj[k]
var forceCollaspe = defaultCollapseList.includes(k)
var $key = genKey(k, nextPath)
var $value = generate(v, depth+1, nextPath, forceCollaspe)
var $div = $('<div>')
var $collase = ''
if (isIterable(v)) {
$collase = collapseIcon()
if (depth > 1 && (Array.isArray(v) || forceCollaspe)) {
$collase.addClass('fa-rotate-270')
}
}
$div.append($collase, $key, column(), $value)
$container.append($div)
})
setDepth($container, depth)
return $container
}
function genValue(val, path) {
var exploded_path = path.split('.')
var path_without_last_key = exploded_path.slice(0, -1).join('.')
var key = exploded_path.pop()
var path_filtered_by_value = path_without_last_key
var $value
if (val === null) {
$value = $('<span>').text('null').css({'color': color_null })
} else if (typeof val === 'boolean') {
$value = $('<span>').text(val).css({'color': color_bool })
} else {
$value = $('<span>').text(val).css({'color': color_string })
}
$value
.addClass('selectable-value')
.attr('data-hashpath-key', key)
.attr('data-hashpath-value', val)
.attr('data-hashpath', path_filtered_by_value.slice(1))
.attr('onclick', 'setHashpathOnInput(this, "' + associatedParamId + '", true)')
return $value
}
function genKey(key, path) {
return $('<span>')
.text(key)
.css({ 'color': color_key })
.addClass('selectable-key')
.attr('data-hashpath', path.slice(1))
.attr('onclick', 'setHashpathOnInput(this, "' + associatedParamId + '")')
}
function genIndex(i, path) {
return $('<span>')
.text(i)
.addClass('selectable-key')
.css({ 'color': color_index })
.attr('data-hashpath', path.slice(1))
.attr('onclick', 'setHashpathOnInput(this, "' + associatedParamId + '")')
}
function header() {
return $('<div>').append(braceOpen())
}
function footer() {
return $('<div>').append(braceClose())
}
function collapseIcon() {
return $('<i>')
.addClass(['fas fa-caret-down', 'collaspe-button'])
.css({ 'color': color_collapse, 'margin-right': '0.25rem', 'font-size': '1.25em' })
.attr('onclick', '$(this).toggleClass("fa-rotate-270").parent().children().last().children("div").toggleClass("hidden")')
}
function childrenCount(iterable) {
var count = getChildrenCount(iterable)
var $span = $('<span>').text(count).addClass('children-counter')
if (count === 0) {
$span.css('background-color', '#a3a3a3')
}
return $span
}
function braceOpen(isArray) {
return $('<span>').text(isArray ? '[' : '{').css({ 'color': color_brace, margin: '0 0.25em' })
}
function braceClose(isArray) {
return $('<span>').text(isArray ? ']' : '}').css({ 'color': color_brace, margin: '0 0.25em' })
}
function column() {
return $('<span>').text(':').css({ 'color': color_column, margin: '0 0.25em' })
}
function setDepth($obj) {
$obj.css('margin-left', 'calc( ' + indent_size + ' )')
}
function isIterable(obj) {
return typeof obj === 'object' && obj !== null
}
function getChildrenCount(iterable) {
var count = 0
if (Array.isArray(iterable)) {
count = iterable.length
} else if (typeof iterable === 'object') {
count = Object.keys(iterable).length
}
return count
}
var $mainContainer = $('<div id="core-format-picker">')
$mainContainer.append(header(), generate(event, 1, ''), footer())
return $mainContainer
}

View File

@ -9549,5 +9549,5 @@
"uuid": false
}
},
"db_version": "113"
"db_version": "114"
}

View File

@ -411,7 +411,7 @@ apacheConfig_RHEL7 () {
#sudo sed -i "s/SetHandler/\#SetHandler/g" /etc/httpd/conf.d/misp.ssl.conf
sudo rm /etc/httpd/conf.d/ssl.conf
sudo chmod 644 /etc/httpd/conf.d/misp.ssl.conf
sudo sed -i '/Listen 80/a Listen 443' /etc/httpd/conf/httpd.conf
sudo sed -i '/Listen 443/!s/Listen 80/a Listen 443/' /etc/httpd/conf/httpd.conf
# If a valid SSL certificate is not already created for the server, create a self-signed certificate:
echo "The Common Name used below will be: ${OPENSSL_CN}"

View File

@ -452,7 +452,7 @@ apacheConfig_RHEL8 () {
#sudo sed -i "s/SetHandler/\#SetHandler/g" /etc/httpd/conf.d/misp.ssl.conf
sudo rm /etc/httpd/conf.d/ssl.conf
sudo chmod 644 /etc/httpd/conf.d/misp.ssl.conf
sudo sed -i '/Listen 80/a Listen 443' /etc/httpd/conf/httpd.conf
sudo sed -i '/Listen 443/!s/Listen 80/a Listen 443/' /etc/httpd/conf/httpd.conf
# If a valid SSL certificate is not already created for the server, create a self-signed certificate:
echo "The Common Name used below will be: ${OPENSSL_CN}"