diff --git a/INSTALL/INSTALL.sh b/INSTALL/INSTALL.sh index e34c926c5..933675b57 100755 --- a/INSTALL/INSTALL.sh +++ b/INSTALL/INSTALL.sh @@ -117,7 +117,8 @@ MISPvars () { # MISP configuration variables PATH_TO_MISP="${PATH_TO_MISP:-/var/www/MISP}" PATH_TO_MISP_SCRIPTS="${PATH_TO_MISP}/app/files/scripts" - + ## For future use + # TMPDIR="${TMPDIR:-$PATH_TO_MISP/app/tmp}" FQDN="${FQDN:-misp.local}" @@ -1541,6 +1542,9 @@ coreCAKE () { ${SUDO_WWW} ${RUN_PHP} -- ${CAKE} Admin setSetting "Session.autoRegenerate" 0 ${SUDO_WWW} ${RUN_PHP} -- ${CAKE} Admin setSetting "Session.timeout" 600 ${SUDO_WWW} ${RUN_PHP} -- ${CAKE} Admin setSetting "Session.cookieTimeout" 3600 + + # Set the default temp dir + ${SUDO_WWW} ${RUN_PHP} -- ${CAKE} Admin setSetting "MISP.tmpdir" "${PATH_TO_MISP}/app/tmp" # Change base url, either with this CLI command or in the UI [[ ! -z ${MISP_BASEURL} ]] && ${SUDO_WWW} ${RUN_PHP} -- ${CAKE} Baseurl $MISP_BASEURL @@ -1562,7 +1566,7 @@ coreCAKE () { # 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" - ${SUDO_WWW} ${RUN_PHP} -- ${CAKE} Admin setSetting "MISP.disable_emailing" true + ${SUDO_WWW} ${RUN_PHP} -- ${CAKE} Admin setSetting "MISP.disable_emailing" true --force ${SUDO_WWW} ${RUN_PHP} -- ${CAKE} Admin setSetting "MISP.contact" "info@admin.test" ${SUDO_WWW} ${RUN_PHP} -- ${CAKE} Admin setSetting "MISP.disablerestalert" true ${SUDO_WWW} ${RUN_PHP} -- ${CAKE} Admin setSetting "MISP.showCorrelationsOnIndex" true @@ -1573,7 +1577,7 @@ coreCAKE () { ${SUDO_WWW} ${RUN_PHP} -- ${CAKE} Admin setSetting "Plugin.Cortex_services_url" "http://127.0.0.1" ${SUDO_WWW} ${RUN_PHP} -- ${CAKE} Admin setSetting "Plugin.Cortex_services_port" 9000 ${SUDO_WWW} ${RUN_PHP} -- ${CAKE} Admin setSetting "Plugin.Cortex_timeout" 120 - ${SUDO_WWW} ${RUN_PHP} -- ${CAKE} Admin setSetting "Plugin.Cortex_authkey" "" + ${SUDO_WWW} ${RUN_PHP} -- ${CAKE} Admin setSetting "Plugin.Cortex_authkey" false ${SUDO_WWW} ${RUN_PHP} -- ${CAKE} Admin setSetting "Plugin.Cortex_ssl_verify_peer" false ${SUDO_WWW} ${RUN_PHP} -- ${CAKE} Admin setSetting "Plugin.Cortex_ssl_verify_host" false ${SUDO_WWW} ${RUN_PHP} -- ${CAKE} Admin setSetting "Plugin.Cortex_ssl_allow_self_signed" true @@ -1632,7 +1636,7 @@ coreCAKE () { Plugin.ElasticSearch_logging_enable Plugin.S3_enable) for PLUG in "${PLUGS[@]}"; do - ${SUDO_WWW} ${RUN_PHP} -- ${CAKE} Admin setSetting ${PLUG} false + ${SUDO_WWW} ${RUN_PHP} -- ${CAKE} Admin setSetting ${PLUG} false 2> /dev/null done # Plugin CustomAuth tuneable @@ -1648,7 +1652,7 @@ coreCAKE () { ${SUDO_WWW} ${RUN_PHP} -- ${CAKE} Admin setSetting "Plugin.RPZ_minimum_ttl" "1h" ${SUDO_WWW} ${RUN_PHP} -- ${CAKE} Admin setSetting "Plugin.RPZ_ttl" "1w" ${SUDO_WWW} ${RUN_PHP} -- ${CAKE} Admin setSetting "Plugin.RPZ_ns" "localhost." - ${SUDO_WWW} ${RUN_PHP} -- ${CAKE} Admin setSetting "Plugin.RPZ_ns_alt" "" + ${SUDO_WWW} ${RUN_PHP} -- ${CAKE} Admin setSetting "Plugin.RPZ_ns_alt" false ${SUDO_WWW} ${RUN_PHP} -- ${CAKE} Admin setSetting "Plugin.RPZ_email" "root.localhost" # Kafka settings @@ -1899,6 +1903,7 @@ mispmodules () { # If you build an egg, the user you build it as need write permissions in the CWD sudo chgrp $WWW_USER . sudo chmod og+w . + $SUDO_WWW ${PATH_TO_MISP}/venv/bin/pip install pillow $SUDO_WWW ${PATH_TO_MISP}/venv/bin/pip install -I -r REQUIREMENTS sudo chgrp staff . $SUDO_WWW ${PATH_TO_MISP}/venv/bin/pip install -I . @@ -3045,10 +3050,6 @@ installSupported () { echo "Proceeding with the installation of MISP core" space - # Set Base URL - functionLocation('generic/supportFunctions.md') - [[ -n $CORE ]] || [[ -n $ALL ]] && setBaseURL - progress 4 - # Check if sudo is installed and etckeeper - functionLocation('generic/sudo_etckeeper.md') [[ -n $CORE ]] || [[ -n $ALL ]] && checkSudoKeeper [[ ! -z ${MISP_USER} ]] && [[ ! -f /etc/sudoers.d/misp ]] && echo "%${MISP_USER} ALL=(ALL:ALL) NOPASSWD:ALL" |sudo tee /etc/sudoers.d/misp diff --git a/INSTALL/INSTALL.sh.sfv b/INSTALL/INSTALL.sh.sfv index f3899ddd5..d0ec619bb 100644 --- a/INSTALL/INSTALL.sh.sfv +++ b/INSTALL/INSTALL.sh.sfv @@ -1,5 +1,5 @@ -; Generated by RHash v1.3.9 on 2021-10-18 at 10:56.53 +; Generated by RHash v1.4.2 on 2021-11-04 at 15:44.11 ; Written by Kravchenko Aleksey (Akademgorodok) - http://rhash.sf.net/ ; -; 160201 10:56.53 2021-10-18 INSTALL.sh -INSTALL.sh 8F59974F7AE69DFBF7B1C492E35F0B421AAC10C1 6F9E9C2C24880D2E69E04AB6AE490F72D8B5CBE5BB98596F4FA50C1CFEAA632F CBCFBA692B57E027A9861C4D4FB1D4808511A23148516946802B0364D428638E60087AD6EA7E2F016B2F65CD216DE288 7221893A49C924974F7D28C094C6CB27FC8ACA6E07FECD7B8DE4D55D283C9D6A5FF63409F55EEC110BF6612E8578BD1373E39B83A7986A6369ACF32A6A92F538 +; 160342 15:44.11 2021-11-04 INSTALL.sh +INSTALL.sh E10075FB44DD06A1C4248264085BDC8217B900CC 30E5EDCE721AF81B18744CA7B2062147BCF873FB5FE71798B8543EBA52F4FB4C 1E68603F4304D5B4EAA456A6B8A9A79C2CE86C48D595C9DCCD341A0D8959C52A7A9EEF0B3ABDB1C3534023350BC18B64 FAFAE6A7E6BD81C87AA1C90CD52721BF314BAD6BB41B33CF3E1E8070E5DDCA786761A6205AD104BF565DE68E4FF100EC7D55837D4F9CAD60A72825BCFFBE5D65 diff --git a/INSTALL/INSTALL.sh.sha1 b/INSTALL/INSTALL.sh.sha1 index aa6840830..5fb875a9b 100644 --- a/INSTALL/INSTALL.sh.sha1 +++ b/INSTALL/INSTALL.sh.sha1 @@ -1 +1 @@ -8f59974f7ae69dfbf7b1c492e35f0b421aac10c1 INSTALL.sh +e10075fb44dd06a1c4248264085bdc8217b900cc INSTALL.sh diff --git a/INSTALL/INSTALL.sh.sha256 b/INSTALL/INSTALL.sh.sha256 index d44026cb3..bcadaefe7 100644 --- a/INSTALL/INSTALL.sh.sha256 +++ b/INSTALL/INSTALL.sh.sha256 @@ -1 +1 @@ -6f9e9c2c24880d2e69e04ab6ae490f72d8b5cbe5bb98596f4fa50c1cfeaa632f INSTALL.sh +30e5edce721af81b18744ca7b2062147bcf873fb5fe71798b8543eba52f4fb4c INSTALL.sh diff --git a/INSTALL/INSTALL.sh.sha384 b/INSTALL/INSTALL.sh.sha384 index 808f8420b..d2328815a 100644 --- a/INSTALL/INSTALL.sh.sha384 +++ b/INSTALL/INSTALL.sh.sha384 @@ -1 +1 @@ -cbcfba692b57e027a9861c4d4fb1d4808511a23148516946802b0364d428638e60087ad6ea7e2f016b2f65cd216de288 INSTALL.sh +1e68603f4304d5b4eaa456a6b8a9a79c2ce86c48d595c9dccd341a0d8959c52a7a9eef0b3abdb1c3534023350bc18b64 INSTALL.sh diff --git a/INSTALL/INSTALL.sh.sha512 b/INSTALL/INSTALL.sh.sha512 index 8d3437126..9cf5651e2 100644 --- a/INSTALL/INSTALL.sh.sha512 +++ b/INSTALL/INSTALL.sh.sha512 @@ -1 +1 @@ -7221893a49c924974f7d28c094c6cb27fc8aca6e07fecd7b8de4d55d283c9d6a5ff63409f55eec110bf6612e8578bd1373e39b83a7986a6369acf32a6a92f538 INSTALL.sh +fafae6a7e6bd81c87aa1c90cd52721bf314bad6bb41b33cf3e1e8070e5ddca786761a6205ad104bf565de68e4ff100ec7d55837d4f9cad60a72825bcffbe5d65 INSTALL.sh diff --git a/INSTALL/INSTALL.tpl.sh b/INSTALL/INSTALL.tpl.sh index 8369623c8..9a970ce84 100755 --- a/INSTALL/INSTALL.tpl.sh +++ b/INSTALL/INSTALL.tpl.sh @@ -260,10 +260,6 @@ installSupported () { echo "Proceeding with the installation of MISP core" space - # Set Base URL - functionLocation('generic/supportFunctions.md') - [[ -n $CORE ]] || [[ -n $ALL ]] && setBaseURL - progress 4 - # Check if sudo is installed and etckeeper - functionLocation('generic/sudo_etckeeper.md') [[ -n $CORE ]] || [[ -n $ALL ]] && checkSudoKeeper [[ ! -z ${MISP_USER} ]] && [[ ! -f /etc/sudoers.d/misp ]] && echo "%${MISP_USER} ALL=(ALL:ALL) NOPASSWD:ALL" |sudo tee /etc/sudoers.d/misp diff --git a/app/Config/config.default.php b/app/Config/config.default.php index 3a397d135..4f105d218 100644 --- a/app/Config/config.default.php +++ b/app/Config/config.default.php @@ -51,7 +51,6 @@ $config = array( 'unpublishedprivate' => false, 'disable_emailing' => false, 'manage_workers' => true, - 'Attributes_Values_Filter_In_Event' => 'id, uuid, value, comment, type, category, Tag.name', 'python_bin' => null, 'external_baseurl' => '', 'forceHTTPSforPreLoginRequestedURL' => false, diff --git a/app/Console/AppShell.php b/app/Console/AppShell.php deleted file mode 100644 index 76a6f1fbc..000000000 --- a/app/Console/AppShell.php +++ /dev/null @@ -1,34 +0,0 @@ -initialize(); - $this->{array_shift($this->args)}(); - } -} diff --git a/app/Console/Command/AdminShell.php b/app/Console/Command/AdminShell.php index e5c3ea3b0..d520c5de0 100644 --- a/app/Console/Command/AdminShell.php +++ b/app/Console/Command/AdminShell.php @@ -22,12 +22,18 @@ class AdminShell extends AppShell 'value' => ['help' => __('Setting value'), 'required' => true], ], 'options' => [ - 'force' => array( + 'force' => [ 'short' => 'f', 'help' => 'Force the command.', 'default' => false, 'boolean' => true - ) + ], + 'null' => [ + 'short' => 'n', + 'help' => 'Set the value to null.', + 'default' => false, + 'boolean' => true + ], ] ], ]); @@ -375,14 +381,13 @@ class AdminShell extends AppShell public function getSetting() { - $this->ConfigLoad->execute(); $param = empty($this->args[0]) ? 'all' : $this->args[0]; $settings = $this->Server->serverSettingsRead(); $result = $settings; - if ($param != 'all') { + if ($param !== 'all') { $result = 'No valid setting found for ' . $param; foreach ($settings as $setting) { - if ($setting['setting'] == $param) { + if ($setting['setting'] === $param) { $result = $setting; break; } @@ -393,15 +398,17 @@ class AdminShell extends AppShell public function setSetting() { - $setting_name = !isset($this->args[0]) ? null : $this->args[0]; - $value = !isset($this->args[1]) ? null : $this->args[1]; + list($setting_name, $value) = $this->args; if ($value === 'false') { $value = 0; } elseif ($value === 'true') { $value = 1; } + if ($this->params['null']) { + $value = null; + } $cli_user = array('id' => 0, 'email' => 'SYSTEM', 'Organisation' => array('name' => 'SYSTEM')); - if (empty($setting_name) || $value === null) { + if (empty($setting_name) || ($value === null && !$this->params['null'])) { die('Usage: ' . $this->Server->command_line_functions['console_admin_tasks']['data']['Set setting'] . PHP_EOL); } $setting = $this->Server->getSettingData($setting_name); @@ -411,7 +418,7 @@ class AdminShell extends AppShell } $result = $this->Server->serverSettingsEditValue($cli_user, $setting, $value, $this->params['force']); if ($result === true) { - echo 'Setting "' . $setting_name . '" changed to ' . $value . PHP_EOL; + $this->out(__('Setting "%s" changed to %s', $setting_name, is_string($value) ? '"' . $value . '"' : (string)$value)); } else { $message = __("The setting change was rejected. MISP considers the requested setting value as invalid and would lead to the following error:\n\n\"%s\"\n\nIf you still want to force this change, please supply the --force argument.\n", $result); $this->error(__('Setting change rejected.'), $message); diff --git a/app/Console/Command/AppShell.php b/app/Console/Command/AppShell.php index b691646fa..1687122cd 100644 --- a/app/Console/Command/AppShell.php +++ b/app/Console/Command/AppShell.php @@ -30,10 +30,10 @@ class AppShell extends Shell { public $tasks = array('ConfigLoad'); - public function perform() + public function initialize() { - $this->initialize(); - $this->{array_shift($this->args)}(); + parent::initialize(); + $this->ConfigLoad->execute(); } protected function _welcome() diff --git a/app/Console/Command/Task/ConfigLoadTask.php b/app/Console/Command/Task/ConfigLoadTask.php index 78e506803..b624dc573 100644 --- a/app/Console/Command/Task/ConfigLoadTask.php +++ b/app/Console/Command/Task/ConfigLoadTask.php @@ -1,7 +1,13 @@ +} diff --git a/app/Controller/AppController.php b/app/Controller/AppController.php index 6d6be801b..c9c454a1a 100755 --- a/app/Controller/AppController.php +++ b/app/Controller/AppController.php @@ -31,7 +31,7 @@ class AppController extends Controller */ public $defaultModel = ''; - public $helpers = array('OrgImg', 'FontAwesome', 'UserName', 'DataPathCollector'); + public $helpers = array('OrgImg', 'FontAwesome', 'UserName'); private $__queryVersion = '131'; public $pyMispVersion = '2.4.148'; @@ -69,34 +69,39 @@ class AppController extends Controller } public $components = array( - 'Session', - 'Auth' => array( - 'authError' => 'Unauthorised access.', - 'authenticate' => array( - 'Form' => array( - 'passwordHasher' => 'BlowfishConstant', - 'fields' => array( - 'username' => 'email' - ) + 'Session', + 'Auth' => array( + 'authError' => 'Unauthorised access.', + 'authenticate' => array( + 'Form' => array( + 'passwordHasher' => 'BlowfishConstant', + 'fields' => array( + 'username' => 'email' ) ) - ), - 'Security', - 'ACL', - 'CompressedRequestHandler', - 'RestResponse', - 'Flash', - 'Toolbox', - 'RateLimit', - 'IndexFilter', - 'Deprecation', - 'RestSearch', - 'CRUD' - //,'DebugKit.Toolbar' + ) + ), + 'Security', + 'ACL', + 'CompressedRequestHandler', + 'RestResponse', + 'Flash', + 'Toolbox', + 'RateLimit', + 'IndexFilter', + 'Deprecation', + 'RestSearch', + 'CRUD' + //,'DebugKit.Toolbar' ); public function beforeFilter() { + if (Configure::read('MISP.system_setting_db')) { + App::uses('SystemSetting', 'Model'); + SystemSetting::setGlobalSetting(); + } + $this->_setupBaseurl(); $this->Auth->loginRedirect = $this->baseurl . '/users/routeafterlogin'; @@ -342,7 +347,7 @@ class AppController extends Controller $this->User->Server->updateDatabase('cleanSessionTable'); } } - if (Configure::read('site_admin_debug') && (Configure::read('debug') < 2)) { + if (Configure::read('site_admin_debug') && Configure::read('debug') < 2) { Configure::write('debug', 1); } } @@ -373,7 +378,7 @@ class AppController extends Controller if (!empty($homepage)) { $this->set('homepage', $homepage); } - if (version_compare(phpversion(), '8.0') >= 0) { + if (PHP_MAJOR_VERSION >= 8) { $this->Flash->error(__('WARNING: MISP is currently running under PHP 8.0, which is unsupported. Background jobs will fail, so please contact your administrator to run a supported PHP version (such as 7.4)')); } } diff --git a/app/Controller/AttributesController.php b/app/Controller/AttributesController.php index d1d7eb329..7a019d06d 100644 --- a/app/Controller/AttributesController.php +++ b/app/Controller/AttributesController.php @@ -11,12 +11,23 @@ class AttributesController extends AppController { public $components = array('Security', 'RequestHandler'); - public $paginate = array( - 'limit' => 60, - 'maxLimit' => 9999, - 'conditions' => array('AND' => array('Attribute.deleted' => 0)), - 'order' => 'Attribute.event_id DESC' - ); + public $paginate = [ + 'limit' => 60, + 'maxLimit' => 9999, + 'conditions' => array('AND' => array('Attribute.deleted' => 0)), + 'order' => 'Attribute.event_id DESC', + 'recursive' => -1, + 'contain' => array( + 'Event' => array( + 'fields' => array('Event.id', 'Event.orgc_id', 'Event.org_id', 'Event.info', 'Event.user_id', 'Event.date'), + ), + 'AttributeTag', + 'Object' => array( + 'fields' => array('Object.id', 'Object.distribution', 'Object.sharing_group_id') + ), + 'SharingGroup' => ['fields' => ['SharingGroup.name']], + ), + ]; public function beforeFilter() { @@ -53,36 +64,19 @@ class AttributesController extends AppController $this->params->addParams(array('pass' => array($id))); // FIXME find better way to change id variable if uuid is found. params->url and params->here is not modified accordingly now } } - // do not show private to other orgs - if (!$this->_isSiteAdmin()) { - $this->paginate = Set::merge($this->paginate, array('conditions' => $this->Attribute->buildConditions($this->Auth->user()))); - } } public function index() { - $this->Attribute->recursive = -1; - $this->paginate['recursive'] = -1; - $this->paginate['contain'] = array( - 'Event' => array( - 'fields' => array('Event.id', 'Event.orgc_id', 'Event.org_id', 'Event.info', 'Event.user_id', 'Event.date'), - ), - 'AttributeTag' => array('Tag'), - 'Object' => array( - 'fields' => array('Object.id', 'Object.distribution', 'Object.sharing_group_id') - ), - 'SharingGroup' => ['fields' => ['SharingGroup.name']], - ); - $this->Attribute->contain(array('AttributeTag' => array('Tag'))); - $this->set('isSearch', 0); + $this->paginate['conditions']['AND'][] = $this->Attribute->buildConditions($this->Auth->user()); $attributes = $this->paginate(); + if ($this->_isRest()) { - foreach ($attributes as $k => $attribute) { - $attributes[$k] = $attribute['Attribute']; - } + $attributes = array_column($attributes, 'Attribute'); return $this->RestResponse->viewData($attributes, $this->response->type()); } + $this->Attribute->attachTagsToAttributes($attributes, ['includeAllTags' => true]); $orgTable = $this->Attribute->Event->Orgc->find('all', [ 'fields' => ['Orgc.id', 'Orgc.name', 'Orgc.uuid'], ]); @@ -94,6 +88,7 @@ class AttributesController extends AppController } list($attributes, $sightingsData) = $this->__searchUI($attributes); + $this->set('isSearch', 0); $this->set('sightingsData', $sightingsData); $this->set('orgTable', array_column($orgTable, 'name', 'id')); $this->set('shortDist', $this->Attribute->shortDist); @@ -920,28 +915,25 @@ class AttributesController extends AppController if (empty($attribute)) { return new CakeResponse(array('body'=> json_encode(array('fail' => false, 'errors' => 'Invalid attribute')), 'status' => 200, 'type' => 'json')); } - $this->Attribute->data = $attribute; - $this->Attribute->id = $attribute['Attribute']['id']; if (!$this->__canModifyEvent($attribute)) { return new CakeResponse(array('body' => json_encode(array('fail' => false, 'errors' => 'You do not have permission to do that')), 'status' => 200, 'type' => 'json')); } if (!$this->_isRest()) { $this->Attribute->Event->insertLock($this->Auth->user(), $attribute['Attribute']['event_id']); } - $validFields = array('value', 'category', 'type', 'comment', 'to_ids', 'distribution', 'first_seen', 'last_seen'); - $changed = false; if (empty($this->request->data['Attribute'])) { $this->request->data = array('Attribute' => $this->request->data); if (empty($this->request->data['Attribute'])) { throw new MethodNotAllowedException(__('Invalid input.')); } } + $validFields = array('value', 'category', 'type', 'comment', 'to_ids', 'distribution', 'first_seen', 'last_seen'); + $changed = false; foreach ($this->request->data['Attribute'] as $changedKey => $changedField) { - if (!in_array($changedKey, $validFields)) { + if (!in_array($changedKey, $validFields, true)) { throw new MethodNotAllowedException(__('Invalid field.')); } if ($attribute['Attribute'][$changedKey] == $changedField) { - $this->autoRender = false; return new CakeResponse(array('body'=> json_encode(array('errors'=> array('value' => 'nochange'))), 'status'=>200, 'type' => 'json')); } $attribute['Attribute'][$changedKey] = $changedField; @@ -952,16 +944,23 @@ class AttributesController extends AppController } $date = new DateTime(); $attribute['Attribute']['timestamp'] = $date->getTimestamp(); - if ($this->Attribute->save($attribute)) { - $this->Attribute->Event->unpublishEvent($attribute['Attribute']['event_id']); + + $fieldsToSave = ['timestamp']; + if ($changedKey === 'value') { + $fieldsToSave[] = 'value1'; + $fieldsToSave[] = 'value2'; + } else { + $fieldsToSave[] = $changedKey; + } + + if ($this->Attribute->save($attribute, true, $fieldsToSave)) { + $this->Attribute->Event->unpublishEvent($attribute['Attribute']['event_id'], false, $date->getTimestamp()); if ($attribute['Attribute']['object_id'] != 0) { $this->Attribute->Object->updateTimestamp($attribute['Attribute']['object_id'], $date->getTimestamp()); } - $this->autoRender = false; return new CakeResponse(array('body'=> json_encode(array('saved' => true, 'success' => 'Field updated.', 'check_publish' => true)), 'status'=>200, 'type' => 'json')); } else { - $this->autoRender = false; return new CakeResponse(array('body'=> json_encode(array('saved' => false, 'errors' => $this->Attribute->validationErrors)), 'status'=>200, 'type' => 'json')); } } @@ -1577,25 +1576,9 @@ class AttributesController extends AppController if (!isset($params['conditions']['Attribute.deleted'])) { $params['conditions']['Attribute.deleted'] = 0; } - $this->paginate = $params; - if (empty($this->paginate['limit'])) { - $this->paginate['limit'] = 60; - } - if (empty($this->paginate['page'])) { - $this->paginate['page'] = 1; - } - $this->paginate['recursive'] = -1; - $this->paginate['contain'] = array( - 'Event' => array( - 'fields' => array('Event.id', 'Event.orgc_id', 'Event.org_id', 'Event.info', 'Event.user_id', 'Event.date'), - ), - 'AttributeTag' => array('Tag'), - 'Object' => array( - 'fields' => array('Object.id', 'Object.distribution', 'Object.sharing_group_id') - ), - 'SharingGroup' => ['fields' => ['SharingGroup.name']], - ); + $this->paginate['conditions'] = $params['conditions']; $attributes = $this->paginate(); + $this->Attribute->attachTagsToAttributes($attributes, ['includeAllTags' => true]); $orgTable = $this->Attribute->Event->Orgc->find('all', [ 'fields' => ['Orgc.id', 'Orgc.name', 'Orgc.uuid'], @@ -1649,7 +1632,7 @@ class AttributesController extends AppController } } - private function __searchUI($attributes) + private function __searchUI(array $attributes) { if (empty($attributes)) { return [[], []]; @@ -1661,9 +1644,9 @@ class AttributesController extends AppController $this->loadModel('AttachmentScan'); $user = $this->Auth->user(); $attributeIds = []; - foreach ($attributes as $k => $attribute) { - $attributeId = $attribute['Attribute']['id']; - $attributeIds[] = $attributeId; + $galaxyTags = []; + foreach ($attributes as &$attribute) { + $attributeIds[] = $attribute['Attribute']['id']; if ($this->Attribute->isImage($attribute['Attribute'])) { if (extension_loaded('gd')) { // if extension is loaded, the data is not passed to the view because it is asynchronously fetched @@ -1671,20 +1654,33 @@ class AttributesController extends AppController } else { $attribute['Attribute']['image'] = $this->Attribute->base64EncodeAttachment($attribute['Attribute']); } - $attributes[$k] = $attribute; } if ($attribute['Attribute']['type'] === 'attachment' && $this->AttachmentScan->isEnabled()) { $infected = $this->AttachmentScan->isInfected(AttachmentScan::TYPE_ATTRIBUTE, $attribute['Attribute']['id']); - $attributes[$k]['Attribute']['infected'] = $infected; + $attribute['Attribute']['infected'] = $infected; } if ($attribute['Attribute']['distribution'] == 4) { - $attributes[$k]['Attribute']['SharingGroup'] = $attribute['SharingGroup']; + $attribute['Attribute']['SharingGroup'] = $attribute['SharingGroup']; } - $attributes[$k]['Attribute']['AttributeTag'] = $attributes[$k]['AttributeTag']; - $attributes[$k]['Attribute'] = $this->Attribute->Event->massageTags($this->Auth->user(), $attributes[$k]['Attribute'], 'Attribute', $excludeGalaxy = false, $cullGalaxyTags = true); - unset($attributes[$k]['AttributeTag']); + $attribute['Attribute']['AttributeTag'] = $attribute['AttributeTag']; + foreach ($attribute['Attribute']['AttributeTag'] as $at) { + if (substr($at['Tag']['name'], 0, 12) === 'misp-galaxy:') { + $galaxyTags[] = $at['Tag']['name']; + } + } + unset($attribute['AttributeTag']); + } + unset($attribute); + + // Fetch galaxy clusters in one query + if (!empty($galaxyTags)) { + $this->loadModel('GalaxyCluster'); + $clusters = $this->GalaxyCluster->getClusters($galaxyTags, $user, true, false); + $clusters = array_column(array_column($clusters, 'GalaxyCluster'), null, 'tag_id'); + } else { + $clusters = []; } // Fetch correlations in one query @@ -1696,6 +1692,27 @@ class AttributesController extends AppController $attributesWithFeedCorrelations = $this->Feed->attachFeedCorrelations(array_column($attributes, 'Attribute'), $user, $fakeEventArray); foreach ($attributes as $k => $attribute) { + // Assign galaxies + $galaxies = []; + foreach ($attribute['Attribute']['AttributeTag'] as $k2 => $attributeTag) { + if (!isset($clusters[$attributeTag['Tag']['id']])) { + continue; + } + $cluster = $clusters[$attributeTag['Tag']['id']]; + $galaxyId = $cluster['Galaxy']['id']; + $cluster['local'] = isset($attributeTag['local']) ? $attributeTag['local'] : false; + if (isset($attribute['Attribute']['Galaxy'][$galaxyId])) { + unset($cluster['Galaxy']); + $galaxies[$galaxyId]['GalaxyCluster'][] = $cluster; + } else { + $galaxies[$galaxyId] = $cluster['Galaxy']; + unset($cluster['Galaxy']); + $galaxies[$galaxyId]['GalaxyCluster'] = [$cluster]; + } + unset($attributes[$k]['Attribute']['AttributeTag'][$k2]); // remove galaxy tag + } + $attributes[$k]['Attribute']['Galaxy'] = array_values($galaxies); + if (isset($attributesWithFeedCorrelations[$k]['Feed'])) { $attributes[$k]['Attribute']['Feed'] = $attributesWithFeedCorrelations[$k]['Feed']; } @@ -1707,65 +1724,6 @@ class AttributesController extends AppController return array($attributes, $sightingsData); } - // If the checkbox for the alternate search is ticked, then this method is called to return the data to be represented - // This alternate view will show a list of events with matching search results and the percentage of those matched attributes being marked as to_ids - // events are sorted based on relevance (as in the percentage of matches being flagged as indicators for IDS) - public function searchAlternate($data) - { - $attributes = $this->Attribute->fetchAttributes( - $this->Auth->user(), - array( - 'conditions' => array( - 'AND' => $data - ), - 'contain' => array('Event' => array('Orgc' => array('fields' => array('Orgc.name')))), - 'fields' => array( - 'Attribute.id', 'Attribute.event_id', 'Attribute.type', 'Attribute.category', 'Attribute.to_ids', 'Attribute.value', 'Attribute.distribution', - 'Event.id', 'Event.org_id', 'Event.orgc_id', 'Event.info', 'Event.distribution', 'Event.attribute_count', 'Event.date', - ) - ) - ); - $events = array(); - foreach ($attributes as $attribute) { - if (isset($events[$attribute['Event']['id']])) { - if ($attribute['Attribute']['to_ids']) { - $events[$attribute['Event']['id']]['to_ids']++; - } else { - $events[$attribute['Event']['id']]['no_ids']++; - } - } else { - $events[$attribute['Event']['id']]['Event'] = $attribute['Event']; - $events[$attribute['Event']['id']]['to_ids'] = 0; - $events[$attribute['Event']['id']]['no_ids'] = 0; - if ($attribute['Attribute']['to_ids']) { - $events[$attribute['Event']['id']]['to_ids']++; - } else { - $events[$attribute['Event']['id']]['no_ids']++; - } - } - } - foreach ($events as $key => $event) { - $events[$key]['relevance'] = 100 * $event['to_ids'] / ($event['no_ids'] + $event['to_ids']); - } - if (!empty($events)) { - $events = $this->__subval_sort($events, 'relevance'); - } - return $events; - } - - // Sort the array of arrays based on a value of a sub-array - private function __subval_sort($a, $subkey) - { - foreach ($a as $k=>$v) { - $b[$k] = strtolower($v[$subkey]); - } - arsort($b); - foreach ($b as $key=>$val) { - $c[] = $a[$key]; - } - return $c; - } - public function checkComposites() { if (!self::_isAdmin()) { diff --git a/app/Controller/AuditLogsController.php b/app/Controller/AuditLogsController.php index 3b2fcf354..74d1e93e8 100644 --- a/app/Controller/AuditLogsController.php +++ b/app/Controller/AuditLogsController.php @@ -37,6 +37,7 @@ class AuditLogsController extends AppController 'Server', 'ShadowAttribute', 'SharingGroup', + 'SystemSetting', 'Tag', 'TagCollection', 'TagCollectionTag', @@ -484,10 +485,12 @@ class AuditLogsController extends AppController if (!empty($eventIds)) { $this->loadModel('Event'); - $events = $this->Event->fetchSimpleEvents($this->Auth->user(), [ - 'conditions' => ['Event.id' => array_unique($eventIds)], + $conditions = $this->Event->createEventConditions($this->Auth->user()); + $conditions['Event.id'] = array_unique($eventIds); + $events = $this->Event->find('list', [ + 'conditions' => $conditions, + 'fields' => ['Event.id', 'Event.info'], ]); - $events = array_column(array_column($events, 'Event'), null, 'id'); } $links = [ @@ -525,7 +528,7 @@ class AuditLogsController extends AppController case 'Event': if (isset($events[$modelId])) { $url = '/events/view/' . $modelId; - $eventInfo = $events[$modelId]['info']; + $eventInfo = $events[$modelId]; } break; case 'ObjectReference': @@ -535,7 +538,7 @@ class AuditLogsController extends AppController $url .= '/deleted:2'; } if (isset($events[$objects[$objectReferences[$modelId]]['event_id']])) { - $eventInfo = $events[$objects[$objectReferences[$modelId]]['event_id']]['info']; + $eventInfo = $events[$objects[$objectReferences[$modelId]]['event_id']]; } } break; @@ -546,7 +549,7 @@ class AuditLogsController extends AppController $url .= '/deleted:2'; } if (isset($events[$objects[$modelId]['event_id']])) { - $eventInfo = $events[$objects[$modelId]['event_id']]['info']; + $eventInfo = $events[$objects[$modelId]['event_id']]; } } break; @@ -557,7 +560,7 @@ class AuditLogsController extends AppController $url .= '/deleted:2'; } if (isset($events[$attributes[$modelId]['event_id']])) { - $eventInfo = $events[$attributes[$modelId]['event_id']]['info']; + $eventInfo = $events[$attributes[$modelId]['event_id']]; } } break; @@ -565,7 +568,7 @@ class AuditLogsController extends AppController if (isset($shadowAttributes[$modelId])) { $url = '/events/view/' . $shadowAttributes[$modelId]['event_id'] . '/focus:' . $shadowAttributes[$modelId]['uuid']; if (isset($events[$shadowAttributes[$modelId]['event_id']])) { - $eventInfo = $events[$shadowAttributes[$modelId]['event_id']]['info']; + $eventInfo = $events[$shadowAttributes[$modelId]['event_id']]; } } break; diff --git a/app/Controller/Component/ACLComponent.php b/app/Controller/Component/ACLComponent.php index 440408f79..778d60f4d 100644 --- a/app/Controller/Component/ACLComponent.php +++ b/app/Controller/Component/ACLComponent.php @@ -54,7 +54,6 @@ class ACLComponent extends Component 'returnAttributes' => array('*'), 'rpz' => array('*'), 'search' => array('*'), - 'searchAlternate' => array('*'), 'toggleCorrelation' => array('perm_add'), 'text' => array('*'), 'toggleToIDS' => array('perm_add'), diff --git a/app/Controller/OrganisationsController.php b/app/Controller/OrganisationsController.php index 705b2736f..78ca7dc79 100644 --- a/app/Controller/OrganisationsController.php +++ b/app/Controller/OrganisationsController.php @@ -372,19 +372,15 @@ class OrganisationsController extends AppController } $idList = json_decode($idList, true); $id_exclusion_list = array_merge($idList, array($this->Auth->user('Organisation')['id'])); - $temp = $this->Organisation->find('all', array( - 'conditions' => array( - 'local' => $local, - 'id !=' => $id_exclusion_list, - ), - 'recursive' => -1, - 'fields' => array('id', 'name'), - 'order' => array('lower(name) ASC') + $orgs = $this->Organisation->find('list', array( + 'conditions' => array( + 'local' => $local, + 'id !=' => $id_exclusion_list, + ), + 'recursive' => -1, + 'fields' => array('id', 'name'), + 'order' => array('lower(name) ASC') )); - $orgs = array(); - foreach ($temp as $org) { - $orgs[] = array('id' => $org['Organisation']['id'], 'name' => $org['Organisation']['name']); - } $this->set('local', $local); $this->layout = false; $this->autoRender = false; @@ -402,10 +398,13 @@ class OrganisationsController extends AppController $this->render('ajax/sg_org_row_empty'); } + /** + * @deprecated Probably not used anywhere. + */ public function getUUIDs() { - if (!$this->Auth->user('Role')['perm_sync']) { - throw new MethodNotAllowedException(__('This action is restricted to sync users')); + if (Configure::read('Security.hide_organisation_index_from_users')) { + throw new MethodNotAllowedException(__('This action is not enabled on this instance.')); } $temp = $this->Organisation->find('all', array( 'recursive' => -1, diff --git a/app/Controller/ServersController.php b/app/Controller/ServersController.php index 87ec25525..6a0a296f5 100644 --- a/app/Controller/ServersController.php +++ b/app/Controller/ServersController.php @@ -1485,18 +1485,18 @@ class ServersController extends AppController } } $this->autoRender = false; - $this->loadModel('Log'); - if (!is_writeable(APP . 'Config/config.php')) { + if (!Configure::read('MISP.system_setting_db') && !is_writeable(APP . 'Config/config.php')) { + $this->loadModel('Log'); $this->Log->create(); $this->Log->save(array( - 'org' => $this->Auth->user('Organisation')['name'], - 'model' => 'Server', - 'model_id' => 0, - 'email' => $this->Auth->user('email'), - 'action' => 'serverSettingsEdit', - 'user_id' => $this->Auth->user('id'), - 'title' => 'Server setting issue', - 'change' => 'There was an issue witch changing ' . $setting['name'] . ' to ' . $this->request->data['Server']['value'] . '. The error message returned is: app/Config.config.php is not writeable to the apache user. No changes were made.', + 'org' => $this->Auth->user('Organisation')['name'], + 'model' => 'Server', + 'model_id' => 0, + 'email' => $this->Auth->user('email'), + 'action' => 'serverSettingsEdit', + 'user_id' => $this->Auth->user('id'), + 'title' => 'Server setting issue', + 'change' => 'There was an issue witch changing ' . $setting['name'] . ' to ' . $this->request->data['Server']['value'] . '. The error message returned is: app/Config.config.php is not writeable to the apache user. No changes were made.', )); if ($this->_isRest()) { return $this->RestResponse->saveFailResponse('Servers', 'serverSettingsEdit', false, 'app/Config.config.php is not writeable to the apache user.', $this->response->type()); diff --git a/app/Controller/UsersController.php b/app/Controller/UsersController.php index 52b8c0539..30f1be3e3 100644 --- a/app/Controller/UsersController.php +++ b/app/Controller/UsersController.php @@ -1210,8 +1210,7 @@ class UsersController extends AppController } } // populate the DB with the first role (site admin) if it's empty - $this->loadModel('Role'); - if ($this->Role->find('count') == 0) { + if (!$this->User->Role->hasAny()) { $siteAdmin = array('Role' => array( 'id' => 1, 'name' => 'Site Admin', @@ -1230,14 +1229,14 @@ class UsersController extends AppController 'perm_template' => 1, 'perm_tagger' => 1, )); - $this->Role->save($siteAdmin); + $this->User->Role->save($siteAdmin); // PostgreSQL: update value of auto incremented serial primary key after setting the column by force - if ($dataSource == 'Database/Postgres') { + if ($dataSource === 'Database/Postgres') { $sql = "SELECT setval('roles_id_seq', (SELECT MAX(id) FROM roles));"; - $this->Role->query($sql); + $this->User->Role->query($sql); } } - if ($this->User->Organisation->find('count', array('conditions' => array('Organisation.local' => true))) == 0) { + if (!$this->User->Organisation->hasAny(array('Organisation.local' => true))) { $this->User->runUpdates(); $date = date('Y-m-d H:i:s'); $org = array('Organisation' => array( @@ -1253,23 +1252,25 @@ class UsersController extends AppController )); $this->User->Organisation->save($org); // PostgreSQL: update value of auto incremented serial primary key after setting the column by force - if ($dataSource == 'Database/Postgres') { + if ($dataSource === 'Database/Postgres') { $sql = "SELECT setval('organisations_id_seq', (SELECT MAX(id) FROM organisations));"; $this->User->Organisation->query($sql); } $org_id = $this->User->Organisation->id; - } else { - $hostOrg = $this->User->Organisation->find('first', array('conditions' => array('Organisation.name' => Configure::read('MISP.org'), 'Organisation.local' => true), 'recursive' => -1)); - if (!empty($hostOrg)) { - $org_id = $hostOrg['Organisation']['id']; - } else { - $firstOrg = $this->User->Organisation->find('first', array('conditions' => array('Organisation.local' => true), 'order' => 'Organisation.id ASC')); - $org_id = $firstOrg['Organisation']['id']; - } } // populate the DB with the first user if it's empty - if ($this->User->find('count') == 0) { + if (!$this->User->hasAny()) { + if (!isset($org_id)) { + $hostOrg = $this->User->Organisation->find('first', array('conditions' => array('Organisation.name' => Configure::read('MISP.org'), 'Organisation.local' => true), 'recursive' => -1)); + if (!empty($hostOrg)) { + $org_id = $hostOrg['Organisation']['id']; + } else { + $firstOrg = $this->User->Organisation->find('first', array('conditions' => array('Organisation.local' => true), 'order' => 'Organisation.id ASC')); + $org_id = $firstOrg['Organisation']['id']; + } + } + $this->User->runUpdates(); $this->User->createInitialUser($org_id); } diff --git a/app/Lib/Tools/BetterSecurity.php b/app/Lib/Tools/BetterSecurity.php new file mode 100644 index 000000000..0e98f108c --- /dev/null +++ b/app/Lib/Tools/BetterSecurity.php @@ -0,0 +1,66 @@ +name = get_class($this); $this->findMethods['column'] = true; } @@ -87,7 +85,7 @@ class AppModel extends Model 57 => false, 58 => false, 59 => false, 60 => false, 61 => false, 62 => false, 63 => true, 64 => false, 65 => false, 66 => false, 67 => false, 68 => false, 69 => false, 70 => false, 71 => true, 72 => true, 73 => false, 74 => false, - 75 => false, 76 => false + 75 => false, 76 => true, 77 => true ); public $advanced_updates_description = array( @@ -250,13 +248,13 @@ class AppModel extends Model $result = $this->Feed->addDefaultFeeds($feeds); $this->Log->create(); $entry = array( - 'org' => 'SYSTEM', - 'model' => 'Server', - 'model_id' => 0, - 'email' => 'SYSTEM', - 'action' => 'update_database', - 'user_id' => 0, - 'title' => 'Added new default feeds.' + 'org' => 'SYSTEM', + 'model' => 'Server', + 'model_id' => 0, + 'email' => 'SYSTEM', + 'action' => 'update_database', + 'user_id' => 0, + 'title' => 'Added new default feeds.' ); if ($result) { $entry['change'] = 'Feeds added: ' . $feedNames; @@ -1537,7 +1535,7 @@ class AppModel extends Model `value` text NOT NULL, `from_json` tinyint(1) default 0, PRIMARY KEY (`id`), - UNIQUE INDEX `value` (`value`(255)) + UNIQUE INDEX `value` (`value`(191)) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;"; break; case 66: @@ -1597,6 +1595,13 @@ class AppModel extends Model $this->__dropIndex('object_references', 'referenced_uuid'); break; case 76: + $sqlArray[] = "CREATE TABLE IF NOT EXISTS `system_settings` ( + `setting` varchar(255) NOT NULL, + `value` blob NOT NULL, + PRIMARY KEY (`setting`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;"; + break; + case 77: $sqlArray[] = "ALTER TABLE `jobs` MODIFY COLUMN `process_id` varchar(36) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL;"; case 'fixNonEmptySharingGroupID': $sqlArray[] = 'UPDATE `events` SET `sharing_group_id` = 0 WHERE `distribution` != 4;'; @@ -1787,14 +1792,14 @@ class AppModel extends Model if ($flagStop && $errorCount > 0) { $this->Log->create(); $this->Log->save(array( - 'org' => 'SYSTEM', - 'model' => 'Server', - 'model_id' => 0, - 'email' => 'SYSTEM', - 'action' => 'update_database', - 'user_id' => 0, - 'title' => __('Issues executing the SQL query for %s', $command), - 'change' => __('Database updates stopped as some errors occurred and the stop flag is enabled.') + 'org' => 'SYSTEM', + 'model' => 'Server', + 'model_id' => 0, + 'email' => 'SYSTEM', + 'action' => 'update_database', + 'user_id' => 0, + 'title' => __('Issues executing the SQL query for %s', $command), + 'change' => __('Database updates stopped as some errors occurred and the stop flag is enabled.') )); return false; } @@ -1860,14 +1865,14 @@ class AppModel extends Model } $this->Log->create(); $this->Log->save(array( - 'org' => 'SYSTEM', - 'model' => 'Server', - 'model_id' => 0, - 'email' => 'SYSTEM', - 'action' => 'update_database', - 'user_id' => 0, - 'title' => ($result ? 'Removed index ' : 'Failed to remove index ') . $icr['STATISTICS']['INDEX_NAME'] . ' from ' . $table, - 'change' => ($result ? 'Removed index ' : 'Failed to remove index ') . $icr['STATISTICS']['INDEX_NAME'] . ' from ' . $table, + 'org' => 'SYSTEM', + 'model' => 'Server', + 'model_id' => 0, + 'email' => 'SYSTEM', + 'action' => 'update_database', + 'user_id' => 0, + 'title' => ($result ? 'Removed index ' : 'Failed to remove index ') . $icr['STATISTICS']['INDEX_NAME'] . ' from ' . $table, + 'change' => ($result ? 'Removed index ' : 'Failed to remove index ') . $icr['STATISTICS']['INDEX_NAME'] . ' from ' . $table, )); } } @@ -1899,14 +1904,14 @@ class AppModel extends Model } $this->Log->create(); $this->Log->save(array( - 'org' => 'SYSTEM', - 'model' => 'Server', - 'model_id' => 0, - 'email' => 'SYSTEM', - 'action' => 'update_database', - 'user_id' => 0, - 'title' => ($result ? 'Added index ' : 'Failed to add index ') . $field . ' to ' . $table . ($duplicate ? ' (index already set)' : $errorMessage), - 'change' => ($result ? 'Added index ' : 'Failed to add index ') . $field . ' to ' . $table . ($duplicate ? ' (index already set)' : $errorMessage), + 'org' => 'SYSTEM', + 'model' => 'Server', + 'model_id' => 0, + 'email' => 'SYSTEM', + 'action' => 'update_database', + 'user_id' => 0, + 'title' => ($result ? 'Added index ' : 'Failed to add index ') . $field . ' to ' . $table . ($duplicate ? ' (index already set)' : $errorMessage), + 'change' => ($result ? 'Added index ' : 'Failed to add index ') . $field . ' to ' . $table . ($duplicate ? ' (index already set)' : $errorMessage), )); $additionResult = array('success' => $result || $duplicate); if (!$result) { @@ -1989,7 +1994,8 @@ class AppModel extends Model } // Try to create a table with a BIGINT(20) - public function seenOnAttributeAndObjectPreUpdate() { + public function seenOnAttributeAndObjectPreUpdate() + { $sqlArray[] = "CREATE TABLE IF NOT EXISTS testtable ( `testfield` BIGINT(6) NULL DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8;"; @@ -2007,10 +2013,6 @@ class AppModel extends Model } } - public function failingPreUpdate() { - throw new Exception('Yolo fail'); - } - public function runUpdates($verbose = false, $useWorker = true, $processId = false) { $this->AdminSetting = ClassRegistry::init('AdminSetting'); @@ -2057,14 +2059,14 @@ class AppModel extends Model if ($this->isUpdateLocked()) { // prevent creation of useless workers $this->Log->create(); $this->Log->save(array( - 'org' => 'SYSTEM', - 'model' => 'Server', - 'model_id' => 0, - 'email' => 'SYSTEM', - 'action' => 'update_db_worker', - 'user_id' => 0, - 'title' => __('Issues executing run_updates'), - 'change' => __('Database updates are locked. Worker not spawned') + 'org' => 'SYSTEM', + 'model' => 'Server', + 'model_id' => 0, + 'email' => 'SYSTEM', + 'action' => 'update_db_worker', + 'user_id' => 0, + 'title' => __('Issues executing run_updates'), + 'change' => __('Database updates are locked. Worker not spawned') )); if (!empty($job)) { // if multiple prio worker is enabled, want to mark them as done $job['Job']['progress'] = 100; @@ -2114,14 +2116,14 @@ class AppModel extends Model if ($this->isUpdateLocked()) { $this->Log->create(); $this->Log->save(array( - 'org' => 'SYSTEM', - 'model' => 'Server', - 'model_id' => 0, - 'email' => 'SYSTEM', - 'action' => 'update_db_worker', - 'user_id' => 0, - 'title' => __('Issues executing run_updates'), - 'change' => __('Updates are locked. Stopping worker gracefully') + 'org' => 'SYSTEM', + 'model' => 'Server', + 'model_id' => 0, + 'email' => 'SYSTEM', + 'action' => 'update_db_worker', + 'user_id' => 0, + 'title' => __('Issues executing run_updates'), + 'change' => __('Updates are locked. Stopping worker gracefully') )); if (!empty($job)) { $job['Job']['progress'] = 100; @@ -2379,6 +2381,10 @@ class AppModel extends Model } } + /** + * @param string $db_version + * @return array + */ protected function findUpgrades($db_version) { $updates = array(); @@ -2503,26 +2509,20 @@ class AppModel extends Model public function getKafkaPubTool() { if (!$this->loadedKafkaPubTool) { - $this->loadKafkaPubTool(); + App::uses('KafkaPubTool', 'Tools'); + $kafkaPubTool = new KafkaPubTool(); + $rdkafkaIni = Configure::read('Plugin.Kafka_rdkafka_config'); + $kafkaConf = array(); + if (!empty($rdkafkaIni)) { + $kafkaConf = parse_ini_file($rdkafkaIni); + } + $brokers = Configure::read('Plugin.Kafka_brokers'); + $kafkaPubTool->initTool($brokers, $kafkaConf); + $this->loadedKafkaPubTool = $kafkaPubTool; } return $this->loadedKafkaPubTool; } - public function loadKafkaPubTool() - { - App::uses('KafkaPubTool', 'Tools'); - $kafkaPubTool = new KafkaPubTool(); - $rdkafkaIni = Configure::read('Plugin.Kafka_rdkafka_config'); - $kafkaConf = array(); - if (!empty($rdkafkaIni)) { - $kafkaConf = parse_ini_file($rdkafkaIni); - } - $brokers = Configure::read('Plugin.Kafka_brokers'); - $kafkaPubTool->initTool($brokers, $kafkaConf); - $this->loadedKafkaPubTool = $kafkaPubTool; - return true; - } - public function publishKafkaNotification($topicName, $data, $action = false) { $kafkaTopic = $this->kafkaTopic($topicName); @@ -2671,15 +2671,6 @@ class AppModel extends Model } } - public function getRowCount($table = false) - { - if (empty($table)) { - $table = $this->table; - } - $table_data = $this->query("show table status like '" . $table . "'"); - return $table_data[0]['TABLES']['Rows']; - } - public function benchmarkCustomAdd($valueToAdd = 0, $name = 'default', $customName = 'custom') { if (empty($this->__profiler[$name]['custom'][$customName])) { @@ -2742,9 +2733,8 @@ class AppModel extends Model { static $versionArray; if ($versionArray === null) { - $file = new File(ROOT . DS . 'VERSION.json'); - $versionArray = $this->jsonDecode($file->read()); - $file->close(); + $content = FileAccessTool::readFromFile(ROOT . DS . 'VERSION.json'); + $versionArray = JsonTool::decode($content); } return $versionArray; } @@ -2945,14 +2935,14 @@ class AppModel extends Model $this->Log = ClassRegistry::init('Log'); $this->Log->create(); $entry = array( - 'org' => 'SYSTEM', - 'model' => 'Server', - 'model_id' => 0, - 'email' => 'SYSTEM', - 'action' => 'update_database', - 'user_id' => 0, - 'title' => 'Bumped the timestamps of locked events containing object references.', - 'change' => sprintf('Event timestamps updated: %s; Object timestamps updated: %s', count($event_ids), count($object_ids)) + 'org' => 'SYSTEM', + 'model' => 'Server', + 'model_id' => 0, + 'email' => 'SYSTEM', + 'action' => 'update_database', + 'user_id' => 0, + 'title' => 'Bumped the timestamps of locked events containing object references.', + 'change' => sprintf('Event timestamps updated: %s; Object timestamps updated: %s', count($event_ids), count($object_ids)) ); $this->Log->save($entry); } @@ -3110,10 +3100,7 @@ class AppModel extends Model $message .= "\n"; do { - $message .= sprintf("[%s] %s", - get_class($exception), - $exception->getMessage() - ); + $message .= sprintf("[%s] %s", get_class($exception), $exception->getMessage()); $message .= "\nStack Trace:\n" . $exception->getTraceAsString(); $exception = $exception->getPrevious(); } while ($exception !== null); diff --git a/app/Model/Attribute.php b/app/Model/Attribute.php index 77c9f3d61..1de64f4f7 100644 --- a/app/Model/Attribute.php +++ b/app/Model/Attribute.php @@ -2031,7 +2031,7 @@ class Attribute extends AppModel 'Event' => array( 'fields' => array('id', 'info', 'org_id', 'orgc_id', 'uuid'), ), - 'AttributeTag', // tags are fetched separately, @see Attribute::__attachTagsToAttributes + 'AttributeTag', // tags are fetched separately, @see Attribute::attachTagsToAttributes 'Object' => array( 'fields' => array('id', 'distribution', 'sharing_group_id') ) @@ -2232,7 +2232,7 @@ class Attribute extends AppModel unset($eventsById, $result); // unset result is important, because it is reference } - $this->__attachTagsToAttributes($results, $options); + $this->attachTagsToAttributes($results, $options); foreach ($results as $k => $result) { if (!empty($options['includeSightings'])) { @@ -2361,7 +2361,14 @@ class Attribute extends AppModel return $eventsById; } - private function __attachTagsToAttributes(array &$attributes, array $options) + /** + * Options: + * - includeAllTags - if true, include also exportable tags + * + * @param array $attributes + * @param array $options + */ + public function attachTagsToAttributes(array &$attributes, array $options) { $tagIdsToFetch = []; foreach ($attributes as $attribute) { @@ -2382,15 +2389,12 @@ class Attribute extends AppModel $conditions['Tag.exportable'] = 1; } - $tagsToModify = $this->AttributeTag->Tag->find('all', [ + $tags = $this->AttributeTag->Tag->find('all', [ 'conditions' => $conditions, 'fields' => ['id', 'name', 'colour', 'numerical_value'], 'recursive' => -1, ]); - $tags = []; - foreach ($tagsToModify as $tag) { - $tags[$tag['Tag']['id']] = $tag['Tag']; - } + $tags = array_column(array_column($tags, 'Tag'), null, 'id'); foreach ($attributes as $k => $attribute) { $tagCulled = false; diff --git a/app/Model/AuditLog.php b/app/Model/AuditLog.php index 99118107a..a1415c20f 100644 --- a/app/Model/AuditLog.php +++ b/app/Model/AuditLog.php @@ -131,13 +131,17 @@ class AuditLog extends AppModel if (in_array($auditLog['model'], ['Attribute', 'Object', 'ShadowAttribute'], true)) { $modelName = $auditLog['model'] === 'ShadowAttribute' ? 'Proposal' : $auditLog['model']; $title = __('%s from Event #%s', $modelName, $auditLog['event_id']); - } else { - $title = "{$auditLog['model']} #{$auditLog['model_id']}"; } + if (isset($auditLog['model_title']) && $auditLog['model_title']) { - $title .= ": {$auditLog['model_title']}"; + if (isset($title)) { + $title .= ": {$auditLog['model_title']}"; + return $title; + } else { + return $auditLog['model_title']; + } } - return $title; + return ''; } /** diff --git a/app/Model/Behavior/AuditLogBehavior.php b/app/Model/Behavior/AuditLogBehavior.php index d085d50d4..b05b6f2ee 100644 --- a/app/Model/Behavior/AuditLogBehavior.php +++ b/app/Model/Behavior/AuditLogBehavior.php @@ -39,6 +39,7 @@ class AuditLogBehavior extends ModelBehavior 'TagCollection' => 'name', 'Taxonomy' => 'namespace', 'Organisation' => 'name', + 'SystemSetting' => 'setting', 'AdminSetting' => 'setting', 'UserSetting' => 'setting', 'Galaxy' => 'name', @@ -117,7 +118,7 @@ class AuditLogBehavior extends ModelBehavior return; } - $id = $model->id ?: null; + $id = $model->id ?: 0; $data = $model->data[$model->alias]; if ($created) { @@ -127,7 +128,7 @@ class AuditLogBehavior extends ModelBehavior if (isset($data['deleted'])) { if ($data['deleted']) { $action = AuditLog::ACTION_SOFT_DELETE; - } else if ($this->old[$model->alias]['deleted']) { + } else if (isset($this->old[$model->alias]['deleted']) && $this->old[$model->alias]['deleted']) { $action = AuditLog::ACTION_UNDELETE; } } @@ -177,14 +178,14 @@ class AuditLogBehavior extends ModelBehavior } $id = $modelName === 'AttributeTag' ? $data['attribute_id'] : $data['event_id']; $modelName = $modelName === 'AttributeTag' ? 'Attribute' : 'Event'; - } - - if ($modelName === 'Event') { + } else if ($modelName === 'Event') { if (isset($changedFields['published'][1]) && $changedFields['published'][1]) { $action = AuditLog::ACTION_PUBLISH; } else if (isset($changedFields['sighting_timestamp'][1]) && $changedFields['sighting_timestamp'][1]) { $action = AuditLog::ACTION_PUBLISH_SIGHTINGS; } + } else if ($modelName === 'SystemSetting') { + $id = 0; } $this->auditLog()->insert(['AuditLog' => [ @@ -253,6 +254,8 @@ class AuditLogBehavior extends ModelBehavior } $id = $modelName === 'AttributeTag' ? $model->data[$model->alias]['attribute_id'] : $model->data[$model->alias]['event_id']; $modelName = $modelName === 'AttributeTag' ? 'Attribute' : 'Event'; + } else if ($modelName === 'SystemSetting') { + $id = 0; } $this->auditLog()->insert(['AuditLog' => [ @@ -350,7 +353,7 @@ class AuditLogBehavior extends ModelBehavior continue; } - if ($key === 'password' || $key === 'authkey') { + if ($key === 'password' || $key === 'authkey' || ($key === 'value' && $model->name === 'SystemSetting' && SystemSetting::isSensitive($model->data[$model->alias]['setting']))) { $value = '*****'; if ($old !== null) { $old = $value; diff --git a/app/Model/Event.php b/app/Model/Event.php index f0bd70dc7..4b8a38c72 100755 --- a/app/Model/Event.php +++ b/app/Model/Event.php @@ -1,9 +1,7 @@ find('first', array( 'recursive' => -1, @@ -5860,7 +5862,7 @@ class Event extends AppModel } $fields = ['published', 'timestamp']; $event['Event']['published'] = 0; - $event['Event']['timestamp'] = time(); + $event['Event']['timestamp'] = $timestamp ?: time(); if ($proposalLock) { $event['Event']['proposal_email_lock'] = 0; $fields[] = 'proposal_email_lock'; diff --git a/app/Model/Feed.php b/app/Model/Feed.php index c2e0a0555..19d959815 100644 --- a/app/Model/Feed.php +++ b/app/Model/Feed.php @@ -2,7 +2,6 @@ App::uses('AppModel', 'Model'); App::uses('RandomTool', 'Tools'); App::uses('TmpFileTool', 'Tools'); -App::uses('FileAccessTool', 'Tools'); App::uses('AttributeValidationTool', 'Tools'); class Feed extends AppModel diff --git a/app/Model/GalaxyCluster.php b/app/Model/GalaxyCluster.php index b25b9f4c6..ab074e107 100644 --- a/app/Model/GalaxyCluster.php +++ b/app/Model/GalaxyCluster.php @@ -1088,18 +1088,23 @@ class GalaxyCluster extends AppModel if (isset($options['list']) && $options['list']) { return $this->find('list', $params); } + if (isset($options['first']) && $options['first']) { $clusters = $this->find('first', $params); } else if (isset($options['count']) && $options['count']) { - $clusterCount = $this->find('count', $params); - return $clusterCount; + return $this->find('count', $params); } else { $clusters = $this->find('all', $params); } + if (empty($clusters)) { return $clusters; } + if (isset($options['first']) && $options['first']) { + $clusters = [$clusters]; + } + if ($full) { $clusterIds = array_column(array_column($clusters, 'GalaxyCluster'), 'id'); $targetingClusterRelations = $this->TargetingClusterRelation->fetchRelations($user, array( @@ -1115,11 +1120,15 @@ class GalaxyCluster extends AppModel $tagsToFetch = Hash::extract($clusters, "{n}.GalaxyClusterRelation.{n}.GalaxyClusterRelationTag.{n}.tag_id"); $tagsToFetch = array_merge($tagsToFetch, Hash::extract($targetingClusterRelations, "GalaxyClusterRelationTag.{n}.tag_id")); - $tags = $this->GalaxyClusterRelation->GalaxyClusterRelationTag->Tag->find('all', [ - 'conditions' => ['id' => array_unique($tagsToFetch)], - 'recursive' => -1, - ]); - $tags = array_column(array_column($tags, 'Tag'), null, 'id'); + if (!empty($tagsToFetch)) { + $tags = $this->GalaxyClusterRelation->GalaxyClusterRelationTag->Tag->find('all', [ + 'conditions' => ['id' => array_unique($tagsToFetch)], + 'recursive' => -1, + ]); + $tags = array_column(array_column($tags, 'Tag'), null, 'id'); + } else { + $tags = []; + } foreach ($targetingClusterRelations as $k => $targetingClusterRelation) { if (!empty($targetingClusterRelation['GalaxyClusterRelationTag'])) { @@ -1165,6 +1174,11 @@ class GalaxyCluster extends AppModel } $clusters[$i] = $this->arrangeData($clusters[$i]); } + + if (isset($options['first']) && $options['first']) { + return $clusters[0]; + } + return $clusters; } diff --git a/app/Model/GalaxyClusterRelation.php b/app/Model/GalaxyClusterRelation.php index bfb656f38..00b69153b 100644 --- a/app/Model/GalaxyClusterRelation.php +++ b/app/Model/GalaxyClusterRelation.php @@ -79,9 +79,9 @@ class GalaxyClusterRelation extends AppModel public function buildConditions($user, $clusterConditions = true) { - $this->Event = ClassRegistry::init('Event'); $conditions = []; if (!$user['Role']['perm_site_admin']) { + $this->Event = ClassRegistry::init('Event'); $alias = $this->alias; $sgids = $this->Event->cacheSgids($user, true); $gcOwnerIds = $this->SourceCluster->cacheGalaxyClusterOwnerIDs($user); diff --git a/app/Model/Server.php b/app/Model/Server.php index 546e51c8a..3926fe442 100644 --- a/app/Model/Server.php +++ b/app/Model/Server.php @@ -2,7 +2,7 @@ App::uses('AppModel', 'Model'); App::uses('GpgTool', 'Tools'); App::uses('ServerSyncTool', 'Tools'); -App::uses('FileAccessTool', 'Tools'); +App::uses('SystemSetting', 'Model'); /** * @property-read array $serverSettings @@ -161,14 +161,6 @@ class Server extends AppModel ) ); - private $__settingTabMergeRules = array( - 'GnuPG' => 'Encryption', - 'SMIME' => 'Encryption', - 'misc' => 'Security', - 'Security' => 'Security', - 'Session' => 'Security' - ); - public $validEventIndexFilters = array('searchall', 'searchpublished', 'searchorg', 'searchtag', 'searcheventid', 'searchdate', 'searcheventinfo', 'searchthreatlevel', 'searchdistribution', 'searchanalysis', 'searchattribute'); public function beforeSave($options = array()) @@ -233,7 +225,12 @@ class Server extends AppModel return false; } - private function __updatePulledEventBeforeInsert(&$event, $server, $user) + /** + * @param array $event + * @param array $server + * @param array $user + */ + private function __updatePulledEventBeforeInsert(array &$event, array $server, array $user) { // we have an Event array // The event came from a pull, so it should be locked. @@ -261,9 +258,9 @@ class Server extends AppModel } } } - if (isset($event['Event']['Attribute']) && !empty($event['Event']['Attribute'])) { - foreach ($event['Event']['Attribute'] as $key => $a) { - switch ($a['distribution']) { + if (isset($event['Event']['Attribute'])) { + foreach ($event['Event']['Attribute'] as $key => $attribute) { + switch ($attribute['distribution']) { case '1': $event['Event']['Attribute'][$key]['distribution'] = '0'; break; @@ -272,8 +269,8 @@ class Server extends AppModel break; } // We remove local tags obtained via pull - if (isset($a['Tag'])) { - foreach ($a['Tag'] as $k => $v) { + if (isset($attribute['Tag'])) { + foreach ($attribute['Tag'] as $k => $v) { if ($v['local']) { unset($event['Event']['Attribute'][$key]['Tag'][$k]); } @@ -281,9 +278,9 @@ class Server extends AppModel } } } - if (isset($event['Event']['Object']) && !empty($event['Event']['Object'])) { - foreach ($event['Event']['Object'] as $i => $o) { - switch ($o['distribution']) { + if (isset($event['Event']['Object'])) { + foreach ($event['Event']['Object'] as $i => $object) { + switch ($object['distribution']) { case '1': $event['Event']['Object'][$i]['distribution'] = '0'; break; @@ -291,8 +288,8 @@ class Server extends AppModel $event['Event']['Object'][$i]['distribution'] = '1'; break; } - if (isset($event['Event']['Object'][$i]['Attribute']) && !empty($event['Event']['Object'][$i]['Attribute'])) { - foreach ($event['Event']['Object'][$i]['Attribute'] as $j => $a) { + if (isset($object['Attribute'])) { + foreach ($object['Attribute'] as $j => $a) { switch ($a['distribution']) { case '1': $event['Event']['Object'][$i]['Attribute'][$j]['distribution'] = '0'; @@ -313,7 +310,7 @@ class Server extends AppModel } } } - if (isset($event['Event']['EventReport']) && !empty($event['Event']['EventReport'])) { + if (isset($event['Event']['EventReport'])) { foreach ($event['Event']['EventReport'] as $key => $r) { switch ($r['distribution']) { case '1': @@ -329,10 +326,13 @@ class Server extends AppModel // Distribution, set reporter of the event, being the admin that initiated the pull $event['Event']['user_id'] = $user['id']; - return $event; } - private function __checkIfEventSaveAble($event) + /** + * @param array $event + * @return bool True if event is not empty + */ + private function __checkIfEventSaveAble(array $event) { if (!empty($event['Event']['Attribute'])) { foreach ($event['Event']['Attribute'] as $attribute) { @@ -379,7 +379,7 @@ class Server extends AppModel $result = $eventModel->_add($event, true, $user, $server['Server']['org_id'], $passAlong, true, $jobId); if ($result) { $successes[] = $eventId; - if (Configure::read('Plugin.ZeroMQ_enable') && Configure::read('Plugin.ZeroMQ_event_notifications_enable')) { + if ($this->pubToZmq('event')) { $pubSubTool = $this->getPubSubTool(); $pubSubTool->event_save(array('Event' => $eventId, 'Server' => $server['Server']['id']), 'add_from_connected_server'); } @@ -393,7 +393,7 @@ class Server extends AppModel $result = $eventModel->_edit($event, $user, $existingEvent['Event']['id'], $jobId, $passAlong, $force); if ($result === true) { $successes[] = $eventId; - if (Configure::read('Plugin.ZeroMQ_enable') && Configure::read('Plugin.ZeroMQ_event_notifications_enable')) { + if ($this->pubToZmq('event')) { $pubSubTool = $this->getPubSubTool(); $pubSubTool->event_save(array('Event' => $eventId, 'Server' => $server['Server']['id']), 'edit_from_connected_server'); } @@ -431,7 +431,7 @@ class Server extends AppModel if ($this->__checkIfEventIsBlockedBeforePull($event)) { return false; } - $event = $this->__updatePulledEventBeforeInsert($event, $serverSync->server(), $user); + $this->__updatePulledEventBeforeInsert($event, $serverSync->server(), $user); if (!$this->__checkIfEventSaveAble($event)) { $fails[$eventId] = __('Empty event detected.'); } else { @@ -1342,12 +1342,20 @@ class Server extends AppModel public function serverSettingsRead($unsorted = false) { + $settingTabMergeRules = array( + 'GnuPG' => 'Encryption', + 'SMIME' => 'Encryption', + 'misc' => 'Security', + 'Security' => 'Security', + 'Session' => 'Security' + ); + $serverSettings = $this->getCurrentServerSettings(); $currentSettings = Configure::read(); $finalSettingsUnsorted = $this->__serverSettingsRead($serverSettings, $currentSettings); foreach ($finalSettingsUnsorted as $key => $temp) { - if (isset($this->__settingTabMergeRules[$temp['tab']])) { - $finalSettingsUnsorted[$key]['tab'] = $this->__settingTabMergeRules[$temp['tab']]; + if (isset($settingTabMergeRules[$temp['tab']])) { + $finalSettingsUnsorted[$key]['tab'] = $settingTabMergeRules[$temp['tab']]; } } if ($unsorted) { @@ -1372,8 +1380,20 @@ class Server extends AppModel private function __evaluateLeaf($leafValue, $leafKey, $setting) { if (isset($setting)) { + if ($setting instanceof EncryptedValue) { + try { + $setting = $setting->decrypt(); + } catch (Exception $e) { + $leafValue['errorMessage'] = 'Could not decrypt.'; + return $leafValue; + } + } if (!empty($leafValue['test'])) { - $result = $this->{$leafValue['test']}($setting, empty($leafValue['errorMessage']) ? false : $leafValue['errorMessage']); + if ($leafValue['test'] instanceof Closure) { + $result = $leafValue['test']($setting); + } else { + $result = $this->{$leafValue['test']}($setting, empty($leafValue['errorMessage']) ? false : $leafValue['errorMessage']); + } if ($result !== true) { $leafValue['error'] = 1; if ($result !== false) { @@ -1397,7 +1417,7 @@ class Server extends AppModel { $dirs = glob(APP . 'Locale/*', GLOB_ONLYDIR); $languages = array('eng' => 'eng'); - foreach ($dirs as $k => $dir) { + foreach ($dirs as $dir) { $dir = str_replace(APP . 'Locale' . DS, '', $dir); $languages[$dir] = $dir; } @@ -1427,13 +1447,17 @@ class Server extends AppModel private function loadLocalOrganisations($strict = false) { - $localOrgs = $this->Organisation->find('list', array( - 'conditions' => array('local' => 1), - 'recursive' => -1, - 'fields' => array('Organisation.id', 'Organisation.name') - )); + static $localOrgs; - if(!$strict){ + if ($localOrgs === null) { + $localOrgs = $this->Organisation->find('list', array( + 'conditions' => array('local' => 1), + 'recursive' => -1, + 'fields' => array('Organisation.id', 'Organisation.name') + )); + } + + if (!$strict) { return array_replace(array(0 => __('No organisation selected.')), $localOrgs); } @@ -1505,15 +1529,10 @@ class Server extends AppModel public function testLocalOrgStrict($value) { - $this->Organisation = ClassRegistry::init('Organisation'); if ($value == 0) { return 'No organisation selected'; } - $local_orgs = $this->Organisation->find('list', array( - 'conditions' => array('local' => 1), - 'recursive' => -1, - 'fields' => array('Organisation.id', 'Organisation.name') - )); + $local_orgs = $this->loadLocalOrganisations(true); if (in_array($value, array_keys($local_orgs))) { return true; } @@ -1619,7 +1638,6 @@ class Server extends AppModel return true; } - public function getHost() { if (function_exists('apache_request_headers')) { @@ -1668,11 +1686,14 @@ class Server extends AppModel } return true; } + if (empty($value)) { + return true; + } if ($this->testForEmpty($value) !== true) { return $this->testForEmpty($value); } $regex = "/^(?https?):\/\/(?([\w,\-,\.]+))(?::(?[0-9]+))?(?\/[a-z0-9_\-\.]+)?$/i"; - if ( + if ( !preg_match($regex, $value, $matches) || strtolower($matches['proto']) != strtolower($this->getProto()) || ( @@ -2071,10 +2092,10 @@ class Server extends AppModel private function __serverSettingNormaliseValue($data, $value, $setting) { if (!empty($data['type'])) { - if ($data['type'] == 'boolean') { - $value = $value ? true : false; - } elseif ($data['type'] == 'numeric') { - $value = intval($value); + if ($data['type'] === 'boolean') { + $value = (bool)$value; + } elseif ($data['type'] === 'numeric') { + $value = (int)$value; } } return $value; @@ -2125,30 +2146,41 @@ class Server extends AppModel if ($beforeResult !== true) { $this->Log = ClassRegistry::init('Log'); $this->Log->create(); - $result = $this->Log->save(array( - 'org' => $user['Organisation']['name'], - 'model' => 'Server', - 'model_id' => 0, - 'email' => $user['email'], - 'action' => 'serverSettingsEdit', - 'user_id' => $user['id'], - 'title' => 'Server setting issue', - 'change' => 'There was an issue witch changing ' . $setting['name'] . ' to ' . $value . '. The error message returned is: ' . $beforeResult . 'No changes were made.', + $this->Log->save(array( + 'org' => $user['Organisation']['name'], + 'model' => 'Server', + 'model_id' => 0, + 'email' => $user['email'], + 'action' => 'serverSettingsEdit', + 'user_id' => $user['id'], + 'title' => 'Server setting issue', + 'change' => 'There was an issue witch changing ' . $setting['name'] . ' to ' . $value . '. The error message returned is: ' . $beforeResult . 'No changes were made.', )); return $beforeResult; } } - $value = trim($value); - if ($setting['type'] === 'boolean') { - $value = (bool)$value; - } else if ($setting['type'] === 'numeric') { - $value = (int)($value); - } - if (!empty($setting['test'])) { - $testResult = $this->{$setting['test']}($value); + if ($value !== null) { + $value = trim($value); + if ($setting['type'] === 'boolean') { + $value = (bool)$value; + } else if ($setting['type'] === 'numeric') { + $value = (int)($value); + } + if (isset($setting['test'])) { + if ($setting['test'] instanceof Closure) { + $testResult = $setting['test']($value); + } else { + $testResult = $this->{$setting['test']}($value); + } + } else { + $testResult = true; # No test defined for this setting: cannot fail + } + } else if (isset($setting['null']) && $setting['null']) { + $testResult = true; } else { - $testResult = true; # No test defined for this setting: cannot fail + $testResult = __('Value could not be null.'); } + if (!$forceSave && $testResult !== true) { if ($testResult === false) { $errorMessage = $setting['errorMessage']; @@ -2158,14 +2190,23 @@ class Server extends AppModel return $errorMessage; } $oldValue = Configure::read($setting['name']); - $settingSaveResult = $this->serverSettingsSaveValue($setting['name'], $value); + $fileOnly = isset($setting['cli_only']) && $setting['cli_only']; + $settingSaveResult = $this->serverSettingsSaveValue($setting['name'], $value, $fileOnly); if ($settingSaveResult) { - $change = array($setting['name'] => array($oldValue, $value)); + if (SystemSetting::isSensitive($setting['name'])) { + $change = array($setting['name'] => array('*****', '*****')); + } else { + $change = array($setting['name'] => array($oldValue, $value)); + } $this->loadLog()->createLogEntry($user, 'serverSettingsEdit', 'Server', 0, 'Server setting changed', $change); // execute after hook if (isset($setting['afterHook'])) { - $afterResult = call_user_func_array(array($this, $setting['afterHook']), array($setting['name'], $value)); + if ($setting['afterHook'] instanceof Closure) { + $afterResult = $setting['afterHook']($setting['name'], $value, $oldValue); + } else { + $afterResult = call_user_func_array(array($this, $setting['afterHook']), array($setting['name'], $value, $oldValue)); + } if ($afterResult !== true) { $change = 'There was an issue after setting a new setting. The error message returned is: ' . $afterResult; $this->loadLog()->createLogEntry($user, 'serverSettingsEdit', 'Server', 0, 'Server setting issue', $change); @@ -2181,11 +2222,18 @@ class Server extends AppModel /** * @param string $setting * @param mixed $value + * @param bool $fileOnly If true, always store value in config file even when `MISP.system_setting_db` is enabled * @return bool * @throws Exception */ - public function serverSettingsSaveValue($setting, $value) + public function serverSettingsSaveValue($setting, $value, $fileOnly = false) { + if (!$fileOnly && Configure::read('MISP.system_setting_db')) { + /** @var SystemSetting $systemSetting */ + $systemSetting = ClassRegistry::init('SystemSetting'); + return $systemSetting->setSetting($setting, $value); + } + $configFilePath = APP . 'Config' . DS . 'config.php'; if (!is_writable($configFilePath)) { return false; // config file is not writeable @@ -2219,22 +2267,11 @@ class Server extends AppModel } } } - Configure::write($setting, $value); - $arrayFix = array( - 'Security.auth', - 'ApacheSecureAuth.ldapFilter' - ); - foreach ($arrayFix as $settingFix) { - if (Configure::read($settingFix) && is_array(Configure::read($settingFix)) && !empty(Configure::read($settingFix))) { - $arrayElements = array(); - foreach (Configure::read($settingFix) as $array) { - if (!in_array($array, $arrayElements)) { - $arrayElements[] = $array; - } - } - Configure::write($settingFix, $arrayElements); - } - } + + /** @var array $config */ + require $configFilePath; + $config = Hash::insert($config, $setting, $value); + $settingsToSave = array( 'debug', 'MISP', 'GnuPG', 'SMIME', 'Proxy', 'SecureAuth', 'Security', 'Session.defaults', 'Session.timeout', 'Session.cookieTimeout', @@ -2243,7 +2280,9 @@ class Server extends AppModel ); $settingsArray = array(); foreach ($settingsToSave as $setting) { - $settingsArray[$setting] = Configure::read($setting); + if (Hash::check($config, $setting)) { + $settingsArray[$setting] = Hash::get($config, $setting); + } } $settingsString = var_export($settingsArray, true); $settingsString = 'read(), true); - $file->close(); - if (!is_null($dbExpectedSchema)) { - return $dbExpectedSchema; - } else { + try { + $content = FileAccessTool::readFromFile(ROOT . DS . 'db_schema.json'); + return JsonTool::decode($content); + } catch (Exception $e) { return false; } } @@ -2862,8 +2898,7 @@ class Server extends AppModel } $dbActualIndexes[$table] = $this->getDatabaseIndexes($this->getDataSource()->config['database'], $table); } - } - else if ($dataSource == 'Database/Postgres') { + } else if ($dataSource == 'Database/Postgres') { return array('Database/Postgres' => array('description' => __('Can\'t check database schema for Postgres database type'))); } return ['schema' => $dbActualSchema, 'column' => $tableColumnNames, 'indexes' => $dbActualIndexes]; @@ -3258,7 +3293,7 @@ class Server extends AppModel try { $output['version'] = $gpg->getVersion(); } catch (Exception $e) { - // ingore + // ignore } try { @@ -4503,12 +4538,12 @@ class Server extends AppModel 'errorMessage' => __('The currently set baseurl does not match the URL through which you have accessed the page. Disregard this if you are accessing the page via an alternate URL (for example via IP address).'), 'test' => 'testBaseURL', 'type' => 'string', + 'null' => true ), 'external_baseurl' => array( 'level' => 0, 'description' => __('The base url of the application (in the format https://www.mymispinstance.com) as visible externally/by other MISPs. MISP will encode this URL in sharing groups when including itself. If this value is not set, the baseurl is used as a fallback.'), 'value' => '', - 'errorMessage' => '', 'test' => 'testURL', 'type' => 'string', ), @@ -4516,7 +4551,6 @@ class Server extends AppModel 'level' => 0, 'description' => __('Unless set to true, the instance will only be accessible by site admins.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testLive', 'type' => 'boolean', ), @@ -4524,7 +4558,6 @@ class Server extends AppModel 'level' => 0, 'description' => __('Select the language MISP should use. The default is english.'), 'value' => 'eng', - 'errorMessage' => '', 'test' => 'testLanguage', 'type' => 'string', 'optionsSource' => function () { @@ -4536,7 +4569,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('This values controls the internal fetcher\'s memory envelope when it comes to attributes. The number provided is the amount of attributes that can be loaded for each MB of PHP memory available in one shot. Consider lowering this number if your instance has a lot of attribute tags / attribute galaxies attached.'), 'value' => 80, - 'errorMessage' => '', 'test' => 'testForNumeric', 'type' => 'numeric', 'null' => true @@ -4545,7 +4577,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('This value controls the divisor for attribute weighting when it comes to loading full events. Meaning that it will load coefficient / divisor number of attributes per MB of memory available. Consider raising this number if you have a lot of correlations or highly contextualised events (large number of event level galaxies/tags).'), 'value' => 3, - 'errorMessage' => '', 'test' => 'testForNumeric', 'type' => 'numeric', 'null' => true @@ -4554,7 +4585,6 @@ class Server extends AppModel 'level' => 0, 'description' => __('Enable some performance heavy correlations (currently CIDR correlation)'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', 'null' => true @@ -4563,7 +4593,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('Enable this setting to directly save the config.php file without first creating a temporary file and moving it to avoid concurency issues. Generally not recommended, but useful when for example other tools modify/maintain the config.php file.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', 'null' => true @@ -4572,7 +4601,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('It is highly recommended to install all the python dependencies in a virtualenv. The recommended location is: %s/venv', ROOT), 'value' => false, - 'errorMessage' => '', 'null' => false, 'test' => 'testForBinExec', 'beforeHook' => 'beforeHookBinExec', @@ -4583,7 +4611,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('MISP will default to the bundled mozilla certificate bundle shipped with the framework, which is rather stale. If you wish to use an alternate bundle, just set this setting using the path to the bundle to use. This setting can only be modified via the CLI.'), 'value' => APP . 'Lib/cakephp/lib/Cake/Config/cacert.pem', - 'errorMessage' => '', 'null' => true, 'test' => 'testForCABundle', 'type' => 'string', @@ -4593,7 +4620,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('In some cases, a heavily used MISP instance can generate unwanted blackhole errors due to a high number of requests hitting the server. Disable the auto logout functionality to ease the burden on the system.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', 'null' => true @@ -4602,7 +4628,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('Set the ssdeep score at which to consider two ssdeep hashes as correlating [1-100]'), 'value' => 40, - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'numeric' ), @@ -4610,7 +4635,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('Sets the maximum number of correlations that can be fetched with a single event. For extreme edge cases this can prevent memory issues. The default value is 5k.'), 'value' => 5000, - 'errorMessage' => '', 'test' => 'testForNumeric', 'type' => 'numeric', 'null' => true @@ -4627,7 +4651,6 @@ class Server extends AppModel 'level' => 3, 'description' => __('This setting is deprecated and can be safely removed.'), 'value' => '', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', ), @@ -4635,7 +4658,6 @@ class Server extends AppModel 'level' => 3, 'description' => __('This setting is deprecated and can be safely removed.'), 'value' => '', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', ), @@ -4644,7 +4666,6 @@ class Server extends AppModel 'description' => __('Cached exports can take up a considerable amount of space and can be disabled instance wide using this setting. Disabling the cached exports is not recommended as it\'s a valuable feature, however, if your server is having free space issues it might make sense to take this step.'), 'value' => false, 'null' => true, - 'errorMessage' => '', 'test' => 'testDisableCache', 'type' => 'boolean', 'afterHook' => 'disableCacheAfterHook', @@ -4654,7 +4675,6 @@ class Server extends AppModel 'description' => __('Disable displaying / modifications to the threat level altogether on the instance (deprecated field).'), 'value' => false, 'null' => true, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean' ), @@ -4662,7 +4682,6 @@ class Server extends AppModel 'level' => 3, 'description' => __('This setting is deprecated and can be safely removed.'), 'value' => '', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', ), @@ -4670,7 +4689,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Footer text prepending the "Powered by MISP" text.'), 'value' => '', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', ), @@ -4678,7 +4696,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Footer text following the "Powered by MISP" text.'), 'value' => '', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', ), @@ -4686,7 +4703,6 @@ class Server extends AppModel 'level' => 3, 'description' => __('This setting is deprecated and can be safely removed.'), 'value' => '', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', ), @@ -4694,7 +4710,6 @@ class Server extends AppModel 'level' => 3, 'description' => __('This setting is deprecated and can be safely removed.'), 'value' => '', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', ), @@ -4702,7 +4717,6 @@ class Server extends AppModel 'level' => 3, 'description' => __('This setting is deprecated and can be safely removed.'), 'value' => '', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', ), @@ -4710,31 +4724,27 @@ class Server extends AppModel 'level' => 3, 'description' => __('This setting is deprecated and can be safely removed.'), 'value' => '', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', ), 'footer_logo' => array( - 'level' => 2 , + 'level' => 2, 'description' => __('If set, this setting allows you to display a logo on the right side of the footer. Upload it as a custom image in the file management tool.'), 'value' => '', - 'errorMessage' => '', 'test' => 'testForCustomImage', 'type' => 'string', ), 'home_logo' => array( - 'level' => 2 , + 'level' => 2, 'description' => __('If set, this setting allows you to display a logo as the home icon. Upload it as a custom image in the file management tool.'), 'value' => '', - 'errorMessage' => '', 'test' => 'testForCustomImage', 'type' => 'string', ), 'main_logo' => array( - 'level' => 2 , + 'level' => 2, 'description' => __('If set, the image specified here will replace the main MISP logo on the login screen. Upload it as a custom image in the file management tool.'), 'value' => '', - 'errorMessage' => '', 'test' => 'testForCustomImage', 'type' => 'string', ), @@ -4742,7 +4752,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('The organisation tag of the hosting organisation. This is used in the e-mail subjects.'), 'value' => '', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', ), @@ -4750,7 +4759,6 @@ class Server extends AppModel 'level' => 0, 'description' => __('The hosting organisation of this instance. If this is not selected then replication instances cannot be added.'), 'value' => '0', - 'errorMessage' => '', 'test' => 'testLocalOrgStrict', 'type' => 'numeric', 'optionsSource' => function () { @@ -4769,7 +4777,6 @@ class Server extends AppModel 'level' => 3, 'description' => __('This setting is deprecated and can be safely removed.'), 'value' => '', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', ), @@ -4777,7 +4784,6 @@ class Server extends AppModel 'level' => 0, 'description' => __('Setting this setting to \'false\' will hide all organisation names / logos.'), 'value' => '', - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', ), @@ -4785,7 +4791,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Put the event threat level in the notification E-mail subject.'), 'value' => true, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', ), @@ -4793,7 +4798,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('This is the TLP string for e-mails when email_subject_tag is not found.'), 'value' => 'tlp:amber', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', ), @@ -4801,7 +4805,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('If this tag is set on an event it\'s value will be sent in the E-mail subject. If the tag is not set the email_subject_TLP_string will be used.'), 'value' => 'tlp', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', ), @@ -4809,7 +4812,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Include in name of the email_subject_tag in the subject. When false only the tag value is used.'), 'value' => true, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', ), @@ -4817,7 +4819,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Notification e-mail sender name.'), 'value' => '', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', ], @@ -4825,7 +4826,6 @@ class Server extends AppModel 'level' => 3, 'description' => __('This setting is deprecated and can be safely removed.'), 'value' => '', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', ), @@ -4833,7 +4833,6 @@ class Server extends AppModel 'level' => 3, 'description' => __('This setting is deprecated and can be safely removed.'), 'value' => '', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', ), @@ -4841,15 +4840,13 @@ class Server extends AppModel 'level' => 1, 'description' => __('Enables the use of MISP\'s background processing.'), 'value' => '', - 'errorMessage' => '', 'test' => 'testBoolTrue', 'type' => 'boolean', ), 'attachments_dir' => array( 'level' => 2, 'description' => __('Directory where attachments are stored. MISP will NOT migrate the existing data if you change this setting. The only safe way to change this setting is in config.php, when MISP is not running, and after having moved/copied the existing data to the new location. This directory must already exist and be writable and readable by the MISP application.'), - 'value' => APP . '/files', # GUI display purpose only. - 'errorMessage' => '', + 'value' => APP . '/files', # GUI display purpose only. 'null' => false, 'test' => 'testForWritableDir', 'type' => 'string', @@ -4859,7 +4856,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('Allow the XML caches to include the encoded attachments.'), 'value' => '', - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', ), @@ -4867,7 +4863,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Always download attachments when loaded by a user in a browser'), 'value' => true, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', ), @@ -4875,7 +4870,6 @@ class Server extends AppModel 'level' => 0, 'description' => __('The Unix user MISP (php) is running as'), 'value' => 'www-data', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', ), @@ -4883,7 +4877,6 @@ class Server extends AppModel 'level' => 0, 'description' => __('The e-mail address that MISP should use for all notifications'), 'value' => '', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', ), @@ -4891,7 +4884,6 @@ class Server extends AppModel 'level' => 0, 'description' => __('You can disable all e-mailing using this setting. When enabled, no outgoing e-mails will be sent by MISP.'), 'value' => false, - 'errorMessage' => '', 'null' => true, 'test' => 'testDisableEmail', 'type' => 'boolean', @@ -4900,7 +4892,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('This setting is deprecated. Please use `MISP.event_alert_metadata_only` instead.'), 'value' => false, - 'errorMessage' => '', 'null' => true, 'test' => 'testBool', 'type' => 'boolean', @@ -4909,7 +4900,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('The e-mail address that MISP should include as a contact address for the instance\'s support team.'), 'value' => '', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', ), @@ -4917,7 +4907,6 @@ class Server extends AppModel 'level' => 3, 'description' => __('This setting is deprecated and can be safely removed.'), 'value' => '', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', ), @@ -4925,7 +4914,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('Turn Vulnerability type attributes into links linking to the provided CVE lookup'), 'value' => 'https://cve.circl.lu/cve/', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', ), @@ -4933,7 +4921,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('Turn Weakness type attributes into links linking to the provided CWE lookup'), 'value' => 'https://cve.circl.lu/cwe/', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', ), @@ -4941,7 +4928,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('This setting controls whether notification e-mails will be sent when an event is created via the REST interface. It might be a good idea to disable this setting when first setting up a link to another instance to avoid spamming your users during the initial pull. Quick recap: True = Emails are NOT sent, False = Emails are sent on events published via sync / REST.'), 'value' => true, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', ), @@ -4949,7 +4935,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('Enabling this flag will allow the event description to be transmitted in the alert e-mail\'s subject. Be aware that this is not encrypted by GnuPG, so only enable it if you accept that part of the event description will be sent out in clear-text.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean' ), @@ -4957,7 +4942,6 @@ class Server extends AppModel 'level' => self::SETTING_OPTIONAL, 'description' => __('If enabled, any requested URL before login will have their HTTP part replaced by HTTPS. This can be usefull if MISP is running behind a reverse proxy responsible for SSL and communicating unencrypted with MISP.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean' ), @@ -4965,7 +4949,6 @@ class Server extends AppModel 'level' => self::SETTING_OPTIONAL, 'description' => __('Send just event metadata (attributes and objects will be omitted) for event alert.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean' ], @@ -4973,7 +4956,6 @@ class Server extends AppModel 'level' => 0, 'description' => __('The default distribution setting for events (0-3).'), 'value' => '', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', 'options' => array('0' => __('Your organisation only'), '1' => __('This community only'), '2' => __('Connected communities'), '3' => __('All communities')), @@ -4982,16 +4964,20 @@ class Server extends AppModel 'level' => 0, 'description' => __('The default distribution setting for attributes, set it to \'event\' if you would like the attributes to default to the event distribution level. (0-3 or "event")'), 'value' => '', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', - 'options' => array('0' => __('Your organisation only'), '1' => __('This community only'), '2' => __('Connected communities'), '3' => __('All communities'), 'event' => __('Inherit from event')), + 'options' => array( + '0' => __('Your organisation only'), + '1' => __('This community only'), + '2' => __('Connected communities'), + '3' => __('All communities'), + 'event' => __('Inherit from event') + ), ), 'default_event_threat_level' => array( 'level' => 1, 'description' => __('The default threat level setting when creating events.'), 'value' => 4, - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', 'options' => array('1' => 'High', '2' => 'Medium', '3' => 'Low', '4' => 'undefined'), @@ -5000,7 +4986,6 @@ class Server extends AppModel 'level' => 0, 'description' => __('The tag collection to be applied to all events created manually.'), 'value' => 0, - 'errorMessage' => '', 'test' => 'testTagCollections', 'type' => 'numeric', 'optionsSource' => function () { @@ -5011,7 +4996,6 @@ class Server extends AppModel 'level' => 0, 'description' => __('The default setting for publish alerts when creating users.'), 'value' => true, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', 'null' => true @@ -5020,7 +5004,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('Enable the tagging feature of MISP. This is highly recommended.'), 'value' => '', - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', ), @@ -5028,7 +5011,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Show the full tag names on the event index.'), 'value' => '', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', 'options' => array(0 => 'Minimal tags', 1 => 'Full tags', 2 => 'Shortened tags'), @@ -5037,7 +5019,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Used on the login page, before the MISP logo'), 'value' => '', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', ), @@ -5045,7 +5026,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Used on the login page, after the MISP logo'), 'value' => '', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', ), @@ -5053,7 +5033,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Used on the login page, to the left of the MISP logo, upload it as a custom image in the file management tool.'), 'value' => '', - 'errorMessage' => '', 'test' => 'testForCustomImage', 'type' => 'string', ), @@ -5061,7 +5040,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Used on the login page, to the right of the MISP logo, upload it as a custom image in the file management tool.'), 'value' => '', - 'errorMessage' => '', 'test' => 'testForCustomImage', 'type' => 'string', ), @@ -5069,7 +5047,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Used in the page title, after the name of the page'), 'value' => 'MISP', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', ), @@ -5077,7 +5054,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Allows users to take ownership of an event uploaded via the "Add MISP XML" button. This allows spoofing the creator of a manually imported event, also breaking possibly breaking the original intended releasability. Synchronising with an instance that has a different creator for the same event can lead to unwanted consequences.'), 'value' => '', - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', ), @@ -5085,7 +5061,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Choose whether the terms and conditions should be displayed inline (false) or offered as a download (true)'), 'value' => '', - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean' ), @@ -5093,7 +5068,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('The filename of the terms and conditions file. Make sure that the file is located in your MISP/app/files/terms directory'), 'value' => '', - 'errorMessage' => '', 'test' => 'testForTermsFile', 'type' => 'string' ), @@ -5101,7 +5075,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('True enables the alternate org fields for the event index (source org and member org) instead of the traditional way of showing only an org field. This allows users to see if an event was uploaded by a member organisation on their MISP instance, or if it originated on an interconnected instance.'), 'value' => '', - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean' ), @@ -5109,7 +5082,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('True will deny access to unpublished events to users outside the organization of the submitter except site admins.'), 'value' => '', - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean' ), @@ -5118,7 +5090,6 @@ class Server extends AppModel 'bigField' => true, 'description' => __('The message sent to the user after account creation (has to be sent manually from the administration interface). Use \\n for line-breaks. The following variables will be automatically replaced in the text: $password = a new temporary password that MISP generates, $username = the user\'s e-mail address, $misp = the url of this instance, $org = the organisation that the instance belongs to, as set in MISP.org, $contact = the e-mail address used to contact the support team, as set in MISP.contact. For example, "the password for $username is $password" would appear to a user with the e-mail address user@misp.org as "the password for user@misp.org is hNamJae81".'), 'value' => 'Dear new MISP user,\n\nWe would hereby like to welcome you to the $org MISP community.\n\n Use the credentials below to log into MISP at $misp, where you will be prompted to manually change your password to something of your own choice.\n\nUsername: $username\nPassword: $password\n\nIf you have any questions, don\'t hesitate to contact us at: $contact.\n\nBest regards,\nYour $org MISP support team', - 'errorMessage' => '', 'test' => 'testPasswordResetText', 'type' => 'string' ), @@ -5127,7 +5098,6 @@ class Server extends AppModel 'bigField' => true, 'description' => __('The message sent to the users when a password reset is triggered. Use \\n for line-breaks. The following variables will be automatically replaced in the text: $password = a new temporary password that MISP generates, $username = the user\'s e-mail address, $misp = the url of this instance, $contact = the e-mail address used to contact the support team, as set in MISP.contact. For example, "the password for $username is $password" would appear to a user with the e-mail address user@misp.org as "the password for user@misp.org is hNamJae81".'), 'value' => 'Dear MISP user,\n\nA password reset has been triggered for your account. Use the below provided temporary password to log into MISP at $misp, where you will be prompted to manually change your password to something of your own choice.\n\nUsername: $username\nYour temporary password: $password\n\nIf you have any questions, don\'t hesitate to contact us at: $contact.\n\nBest regards,\nYour $org MISP support team', - 'errorMessage' => '', 'test' => 'testPasswordResetText', 'type' => 'string' ), @@ -5149,7 +5119,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('If enabled, all log entries will include the IP address of the user.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', 'beforeHook' => 'ipLogBeforeHook' @@ -5158,7 +5127,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('If log_client_ip is enabled, you can customize which header field contains the client\'s IP address. This is generally used when you have a reverse proxy infront of your MISP instance.'), 'value' => 'REMOTE_ADDR', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', 'null' => true, @@ -5167,7 +5135,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('If enabled, MISP will log all successful authentications using API keys. The requested URLs are also logged.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', ), @@ -5184,7 +5151,6 @@ class Server extends AppModel 'level' => 0, 'description' => __('If this functionality is enabled all page requests will be logged. Keep in mind this is extremely verbose and will become a burden to your database.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBoolFalse', 'type' => 'boolean', 'null' => true @@ -5193,7 +5159,6 @@ class Server extends AppModel 'level' => 0, 'description' => __('You can decide to skip the logging of the paranoid logs to the database.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testParanoidSkipDb', 'type' => 'boolean', 'null' => true @@ -5202,7 +5167,6 @@ class Server extends AppModel 'level' => 0, 'description' => __('If paranoid logging is enabled, include the POST body in the entries.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', 'null' => true @@ -5211,7 +5175,6 @@ class Server extends AppModel 'level' => 0, 'description' => __('Log user IPs on each request. 30 day retention for lookups by IP to get the last authenticated user ID for the given IP, whilst on the reverse, indefinitely stores all associated IPs for a user ID.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', 'null' => true @@ -5220,7 +5183,6 @@ class Server extends AppModel 'level' => self::SETTING_RECOMMENDED, 'description' => __('Log user IP and key usage on each API request. All logs for given keys are deleted after one year when this key is not used.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', 'null' => true @@ -5229,7 +5191,6 @@ class Server extends AppModel 'level' => self::SETTING_RECOMMENDED, 'description' => __('Enable new audit log system.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', 'null' => true @@ -5238,7 +5199,6 @@ class Server extends AppModel 'level' => self::SETTING_OPTIONAL, 'description' => __('Compress log changes by brotli algorithm. This will reduce log database size.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', 'null' => true @@ -5247,7 +5207,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('This feature allows users to create org only events and ask another organisation to take ownership of the event. This allows organisations to remain anonymous by asking a partner to publish an event for them.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', 'null' => true @@ -5256,7 +5215,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('When enabled, the number of correlations visible to the currently logged in user will be visible on the event index UI. This comes at a performance cost but can be very useful to see correlating events at a glance.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', 'null' => true @@ -5265,7 +5223,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('When enabled, the number of proposals for the events are shown on the index.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', 'null' => true @@ -5274,7 +5231,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('When enabled, the aggregate number of attribute sightings within the event becomes visible to the currently logged in user on the event index UI.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', 'null' => true @@ -5283,7 +5239,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('When enabled, the aggregate number of discussion posts for the event becomes visible to the currently logged in user on the event index UI.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', 'null' => true @@ -5292,7 +5247,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('When enabled, the aggregate number of event reports for the event becomes visible to the currently logged in user on the event index UI.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', 'null' => true @@ -5301,7 +5255,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('When enabled only Org and Site admins can edit a user\'s profile.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', 'null' => false, @@ -5310,7 +5263,6 @@ class Server extends AppModel 'level' => self::SETTING_RECOMMENDED, 'description' => __('When enabled only Site admins can change user email. This should be enabled if you manage user logins by external system.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', 'null' => false, @@ -5319,7 +5271,6 @@ class Server extends AppModel 'level' => self::SETTING_RECOMMENDED, 'description' => __('When enabled only Site admins can change user password. This should be enabled if you manage user passwords by external system.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', 'null' => false, @@ -5328,7 +5279,6 @@ class Server extends AppModel 'level' => self::SETTING_RECOMMENDED, 'description' => __('When enabled, Org Admins could not add new users. This should be enabled if you manage users by external system.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', 'null' => false, @@ -5337,7 +5287,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('Enable this setting to start blocking alert e-mails for events with a certain tag. Define the tag in MISP.block_event_alert_tag.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', 'null' => false, @@ -5346,7 +5295,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('If the MISP.block_event_alert setting is set, alert e-mails for events tagged with the tag defined by this setting will be blocked.'), 'value' => 'no-alerts="true"', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', 'null' => false, @@ -5355,7 +5303,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('Enable this setting to start blocking alert e-mails for events that have already been published since a specified amount of time. This threshold is defined by MISP.event_alert_republish_ban_threshold'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', 'null' => false, @@ -5364,7 +5311,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('If the MISP.event_alert_republish_ban setting is set, this setting will control how long no alerting by email will be done. Expected format: integer, in minutes'), 'value' => 5, - 'errorMessage' => '', 'test' => 'testForNumeric', 'type' => 'numeric', 'null' => false, @@ -5373,7 +5319,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('If the MISP.event_alert_republish_ban setting is set, this setting will control if a ban time should be reset if emails are tried to be sent during the ban.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', 'null' => false, @@ -5382,7 +5327,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('Enable this setting to start blocking users to send too many e-mails notification since a specified amount of time. This threshold is defined by MISP.user_email_notification_ban_threshold'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', 'null' => false, @@ -5391,7 +5335,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('If the MISP.user_email_notification_ban setting is set, this setting will control how long no notification by email will be done. Expected format: integer, in minutes'), 'value' => 120, - 'errorMessage' => '', 'test' => 'testForNumeric', 'type' => 'numeric', 'null' => false, @@ -5400,7 +5343,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('If the MISP.user_email_notification_ban setting is set, this setting will control how many notification by email can be send for the timeframe defined in MISP.user_email_notification_ban_time_threshold. Expected format: integer'), 'value' => 10, - 'errorMessage' => '', 'test' => 'testForNumeric', 'type' => 'numeric', 'null' => false, @@ -5409,7 +5351,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('Set a value to limit the number of email alerts that events can generate per creator organisation (for example, if an organisation pushes out 2000 events in one shot, only alert on the first 20).'), 'value' => 0, - 'errorMessage' => '', 'test' => 'testForNumeric', 'type' => 'numeric', 'null' => true, @@ -5418,7 +5359,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('Enable this setting to start blocking alert e-mails for old events. The exact timing of what constitutes an old event is defined by MISP.block_old_event_alert_age.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', 'null' => false, @@ -5427,7 +5367,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('If the MISP.block_old_event_alert setting is set, this setting will control how old an event can be for it to be alerted on. The "timestamp" field of the event is used. Expected format: integer, in days'), 'value' => false, - 'errorMessage' => '', 'test' => 'testForNumeric', 'type' => 'numeric', 'null' => false, @@ -5436,7 +5375,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('If the MISP.block_old_event_alert setting is set, this setting will control the threshold for the event.date field, indicating how old an event can be for it to be alerted on. The "date" field of the event is used. Expected format: integer, in days'), 'value' => false, - 'errorMessage' => '', 'test' => 'testForNumeric', 'type' => 'numeric', 'null' => false, @@ -5445,7 +5383,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('Please indicate the temp directory you wish to use for certain functionalities in MISP. By default this is set to /tmp and will be used among others to store certain temporary files extracted from imports during the import process.'), 'value' => '/tmp', - 'errorMessage' => '', 'test' => 'testForPath', 'type' => 'string', 'null' => true, @@ -5455,7 +5392,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('If you would like to customise the css, simply drop a css file in the /var/www/MISP/app/webroot/css directory and enter the name here.'), 'value' => '', - 'errorMessage' => '', 'test' => 'testForStyleFile', 'type' => 'string', 'null' => true, @@ -5464,7 +5400,6 @@ class Server extends AppModel 'level' => 0, 'description' => __('Enable this setting to allow blocking attributes from to_ids sensitive exports if a proposal has been made to it to remove the IDS flag or to remove the attribute altogether. This is a powerful tool to deal with false-positives efficiently.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', 'null' => false, @@ -5473,7 +5408,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('Enable this settings if new tags synced / added via incoming events from any source should not be selectable by users by default.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', 'null' => false @@ -5482,7 +5416,6 @@ class Server extends AppModel 'level' => 0, 'description' => __('*WARNING* This setting will completely disable the correlation on this instance and remove any existing saved correlations. Enabling this will trigger a full recorrelation of all data which is an extremely long and costly procedure. Only enable this if you know what you\'re doing.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBoolFalse', 'type' => 'boolean', 'null' => true, @@ -5492,7 +5425,6 @@ class Server extends AppModel 'level' => 0, 'description' => __('*WARNING* This setting will give event creators the possibility to disable the correlation of individual events / attributes that they have created.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBoolFalse', 'type' => 'boolean', 'null' => true @@ -5501,7 +5433,6 @@ class Server extends AppModel 'level' => 0, 'description' => __('The host running the redis server to be used for generic MISP tasks such as caching. This is not to be confused by the redis server used by the background processing.'), 'value' => '127.0.0.1', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string' ), @@ -5509,7 +5440,6 @@ class Server extends AppModel 'level' => 0, 'description' => __('The port used by the redis server to be used for generic MISP tasks such as caching. This is not to be confused by the redis server used by the background processing.'), 'value' => 6379, - 'errorMessage' => '', 'test' => 'testForNumeric', 'type' => 'numeric' ), @@ -5517,7 +5447,6 @@ class Server extends AppModel 'level' => 0, 'description' => __('The database on the redis server to be used for generic MISP tasks. If you run more than one MISP instance, please make sure to use a different database on each instance.'), 'value' => 13, - 'errorMessage' => '', 'test' => 'testForNumeric', 'type' => 'numeric' ), @@ -5525,7 +5454,6 @@ class Server extends AppModel 'level' => 0, 'description' => __('The password on the redis server (if any) to be used for generic MISP tasks.'), 'value' => '', - 'errorMessage' => '', 'test' => null, 'type' => 'string', 'redacted' => true @@ -5534,7 +5462,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Specify which fields to filter on when you search on the event view. Default values are : "id, uuid, value, comment, type, category, Tag.name"'), 'value' => 'id, uuid, value, comment, type, category, Tag.name', - 'errorMessage' => '', 'test' => null, 'type' => 'string', ), @@ -5542,7 +5469,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Set this to false if you would like to disable MISP managing its own worker processes (for example, if you are managing the workers with a systemd unit).'), 'value' => true, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean' ), @@ -5550,7 +5476,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('Only enable this if you have some tools using MISP with extreme high concurency. General performance will be lower as normal as certain transactional queries are avoided in favour of shorter table locks.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', 'null' => true @@ -5567,7 +5492,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('This is a performance tweak to change the behaviour of restSearch to use attribute filters solely for blocking. This means that a lookup on the event scope with for example the type field set will be ignored unless it\'s used to strip unwanted attributes from the results. If left disabled, passing [ip-src, ip-dst] for example will return any event with at least one ip-src or ip-dst attribute. This is generally not considered to be too useful and is a heavy burden on the database.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', 'null' => true @@ -5576,7 +5500,6 @@ class Server extends AppModel 'level' => self::SETTING_OPTIONAL, 'description' => __('Name of enrichment module that will be used for attachment malware scanning. This module must return av-signature or sb-signature object.'), 'value' => '', - 'errorMessage' => '', 'type' => 'string', 'null' => true, ], @@ -5584,7 +5507,6 @@ class Server extends AppModel 'level' => self::SETTING_OPTIONAL, 'description' => __('Send to attachment scan module just file hash. This can be useful if module sends attachment to remote service and you don\'t want to leak real data.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', 'null' => true, @@ -5593,20 +5515,41 @@ class Server extends AppModel 'level' => self::SETTING_OPTIONAL, 'description' => __('How long to wait for scan results in seconds.'), 'value' => 30, - 'errorMessage' => '', 'test' => 'testForPositiveInteger', 'type' => 'numeric', 'null' => true, ], 'warning_for_all' => [ - 'level' => 1, - 'description' => __('Enable warning list triggers regardless of the IDS flag value'), + 'level' => self::SETTING_RECOMMENDED, + 'description' => __('Enable warning list triggers regardless of the IDS flag value.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', 'null' => true ], + 'system_setting_db' => [ + 'level' => self::SETTING_RECOMMENDED, + 'description' => __('Enable storing setting in database.'), + 'value' => false, + 'test' => 'testBool', + 'type' => 'boolean', + 'null' => true, + 'cli_only' => true, + ], + 'menu_custom_right_link' => [ + 'level' => self::SETTING_OPTIONAL, + 'description' => __('Custom right menu URL.'), + 'value' => null, + 'type' => 'string', + 'null' => true, + ], + 'menu_custom_right_link_html' => [ + 'level' => self::SETTING_OPTIONAL, + 'description' => __('Custom right menu text (it is possible to use HTML).'), + 'value' => null, + 'type' => 'string', + 'null' => true, + ], ), 'GnuPG' => array( 'branch' => 1, @@ -5614,7 +5557,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('The location of the GnuPG executable. If you would like to use a different GnuPG executable than /usr/bin/gpg, you can set it here. If the default is fine, just keep the setting suggested by MISP.'), 'value' => '/usr/bin/gpg', - 'errorMessage' => '', 'test' => 'testForGPGBinary', 'type' => 'string', 'cli_only' => 1 @@ -5623,7 +5565,6 @@ class Server extends AppModel 'level' => 0, 'description' => __('Allow (false) unencrypted e-mails to be sent to users that don\'t have a GnuPG key.'), 'value' => '', - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', ), @@ -5631,7 +5572,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Allow (false) the body of unencrypted e-mails to contain details about the event.'), 'value' => '', - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', ), @@ -5639,7 +5579,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Enable the signing of GnuPG emails. By default, GnuPG emails are signed'), 'value' => true, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', ), @@ -5647,7 +5586,6 @@ class Server extends AppModel 'level' => 0, 'description' => __('The e-mail address that the instance\'s GnuPG key is tied to.'), 'value' => '', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', ), @@ -5655,7 +5593,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('The password (if it is set) of the GnuPG key of the instance.'), 'value' => '', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', 'redacted' => true @@ -5664,7 +5601,6 @@ class Server extends AppModel 'level' => 0, 'description' => __('The location of the GnuPG homedir.'), 'value' => '', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', ), @@ -5672,7 +5608,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('When enabled, the subject in signed and encrypted e-mails will not be sent in unencrypted form.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', ) @@ -5683,7 +5618,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Enable S/MIME encryption. The encryption posture of the GnuPG.onlyencrypted and GnuPG.bodyonlyencrypted settings are inherited if S/MIME is enabled.'), 'value' => '', - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', ), @@ -5691,7 +5625,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('The e-mail address that the instance\'s S/MIME key is tied to.'), 'value' => '', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', ), @@ -5699,7 +5632,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('The location of the public half of the signing certificate.'), 'value' => '/var/www/MISP/.smime/email@address.com.pem', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', ), @@ -5707,7 +5639,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('The location of the private half of the signing certificate.'), 'value' => '/var/www/MISP/.smime/email@address.com.key', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', ), @@ -5715,7 +5646,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('The password (if it is set) of the S/MIME key of the instance.'), 'value' => '', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', 'redacted' => true @@ -5727,7 +5657,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('The hostname of an HTTP proxy for outgoing sync requests. Leave empty to not use a proxy.'), 'value' => '', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', ), @@ -5735,7 +5664,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('The TCP port for the HTTP proxy.'), 'value' => '', - 'errorMessage' => '', 'test' => 'testForNumeric', 'type' => 'numeric', ), @@ -5743,7 +5671,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('The authentication method for the HTTP proxy. Currently supported are Basic or Digest. Leave empty for no proxy authentication.'), 'value' => '', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', ), @@ -5751,7 +5678,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('The authentication username for the HTTP proxy.'), 'value' => '', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', ), @@ -5759,7 +5685,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('The authentication password for the HTTP proxy.'), 'value' => '', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', ), @@ -5779,7 +5704,6 @@ class Server extends AppModel 'level' => self::SETTING_CRITICAL, 'description' => __('Enforce CSP. Content Security Policy (CSP) is an added layer of security that helps to detect and mitigate certain types of attacks, including Cross Site Scripting (XSS) and data injection attacks. When disabled, violations will be just logged.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', ], @@ -5787,17 +5711,15 @@ class Server extends AppModel 'level' => 0, 'description' => __('The salt used for the hashed passwords. You cannot reset this from the GUI, only manually from the settings.php file. Keep in mind, this will invalidate all passwords in the database.'), 'value' => '', - 'errorMessage' => '', 'test' => 'testSalt', 'type' => 'string', 'editable' => false, 'redacted' => true ), - 'log_each_individual_auth_fail' =>[ + 'log_each_individual_auth_fail' => [ 'level' => 1, 'description' => __('By default API authentication failures that happen within the same hour for the same key are omitted and a single log entry is generated. This allows administrators to more easily keep track of attackers that try to brute force API authentication, by reducing the noise generated by expired API keys. On the other hand, this makes little sense for internal MISP instances where detecting the misconfiguration of tools becomes more interesting, so if you fall into the latter category, enable this feature.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', ], @@ -5805,7 +5727,6 @@ class Server extends AppModel 'level' => 0, 'description' => __('Advanced authkeys will allow each user to create and manage a set of authkeys for themselves, each with individual expirations and comments. API keys are stored in a hashed state and can no longer be recovered from MISP. Users will be prompted to note down their key when creating a new authkey. You can generate a new set of API keys for all users on demand in the diagnostics page, or by triggering %s.', sprintf('%s', $this->baseurl, __('the advanced upgrade'))), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', ), @@ -5813,7 +5734,6 @@ class Server extends AppModel 'level' => self::SETTING_OPTIONAL, 'description' => __('Maximal key lifetime in days. Use can limit that validity even more. Just newly created keys will be affected. When not set, key validity is not limited.'), 'value' => '', - 'errorMessage' => '', 'type' => 'numeric', 'test' => 'testForNumeric', 'null' => true, @@ -5822,7 +5742,6 @@ class Server extends AppModel 'level' => self::SETTING_OPTIONAL, 'description' => __('When enabled, session is kept between API requests.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', 'null' => true, @@ -5831,7 +5750,6 @@ class Server extends AppModel 'level' => self::SETTING_OPTIONAL, 'description' => __('This optionally can be enabled if an external auth provider is used. When set to true, it will disable the default form authentication.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', ], @@ -5839,7 +5757,6 @@ class Server extends AppModel 'level' => 0, 'description' => __('Enable this setting if you wish for users to be able to query any arbitrary URL via the rest client. Keep in mind that queries are executed by the MISP server, so internal IPs in your MISP\'s network may be reachable.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', 'null' => true @@ -5848,7 +5765,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('If left empty, the baseurl of your MISP is used. However, in some instances (such as port-forwarded VM installations) this will not work. You can override the baseurl with a url through which your MISP can reach itself (typically https://127.0.0.1 would work).'), 'value' => false, - 'errorMessage' => '', 'test' => null, 'type' => 'string' ), @@ -5856,7 +5772,6 @@ class Server extends AppModel 'level' => 0, 'description' => __('Enable this setting to pass all audit log entries directly to syslog. Keep in mind, this is verbose and will include user, organisation, event data.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', 'null' => true @@ -5865,7 +5780,6 @@ class Server extends AppModel 'level' => self::SETTING_OPTIONAL, 'description' => __('Write syslog messages also to standard error output.'), 'value' => true, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', 'null' => true @@ -5874,7 +5788,6 @@ class Server extends AppModel 'level' => self::SETTING_OPTIONAL, 'description' => __('Syslog message identifier.'), 'value' => '', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', 'null' => true @@ -5883,7 +5796,6 @@ class Server extends AppModel 'level' => 0, 'description' => __('If enabled, any authkey will be replaced by asterisks in Audit log.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', 'null' => true @@ -5892,7 +5804,6 @@ class Server extends AppModel '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.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', 'null' => true, @@ -5901,7 +5812,6 @@ class Server extends AppModel 'level' => 0, 'description' => __('If enabled, any POST, PUT or AJAX request will be allow just when Sec-Fetch-Site header is not defined or contains "same-origin".'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', 'null' => true, @@ -5910,35 +5820,31 @@ class Server extends AppModel 'level' => self::SETTING_OPTIONAL, 'description' => __('If enabled, MISP server will consider all requests as secure. This is usually useful when you run MISP behind reverse proxy that terminates HTTPS.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', 'null' => true, ], 'email_otp_enabled' => array( - 'level'=> 2, + 'level' => 2, 'description' => __('Enable two step authentication with a OTP sent by email. Requires e-mailing to be enabled. Warning: You cannot use it in combination with external authentication plugins.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'beforeHook' => 'otpBeforeHook', 'type' => 'boolean', 'null' => true ), - 'email_otp_length' => array ( + 'email_otp_length' => array( 'level' => 2, 'description' => __('Define the length of the OTP code sent by email'), 'value' => '6', - 'errorMessage' => '', 'type' => 'numeric', 'test' => 'testForNumeric', 'null' => true, ), - 'email_otp_validity' => array ( + 'email_otp_validity' => array( 'level' => 2, 'description' => __('Define the validity (in minutes) of the OTP code sent by email'), 'value' => '5', - 'errorMessage' => '', 'type' => 'numeric', 'test' => 'testForNumeric', 'null' => true, @@ -5948,7 +5854,6 @@ class Server extends AppModel 'bigField' => true, 'description' => __('The message sent to the user when a new OTP is requested. Use \\n for line-breaks. The following variables will be automatically replaced in the text: $otp = the new OTP generated by MISP, $username = the user\'s e-mail address, $org the Organisation managing the instance, $misp = the url of this instance, $contact = the e-mail address used to contact the support team (as set in MISP.contact), $ip the IP used to complete the first step of the login and $validity the validity time in minutes.'), 'value' => 'Dear MISP user,\n\nYou have attempted to login to MISP ($misp) from $ip with username $username.\n\n Use the following OTP to log into MISP: $otp\n This code is valid for the next $validity minutes.\n\nIf you have any questions, don\'t hesitate to contact us at: $contact.\n\nBest regards,\nYour $org MISP support team', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', 'null' => true, @@ -5958,7 +5863,6 @@ class Server extends AppModel 'bigField' => true, 'description' => __('A comma separated list of emails for which the OTP is disabled. Note that if you remove someone from this list, the OTP will only be asked at next login.'), 'value' => '', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', 'null' => true, @@ -5967,7 +5871,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('Enabling this setting will allow users to have access to the pre-auth registration form. This will create an inbox entry for administrators to review.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', 'null' => true @@ -5977,7 +5880,6 @@ class Server extends AppModel 'bigField' => true, 'description' => __('The message sent shown to anyone trying to self-register.'), 'value' => 'If you would like to send us a registration request, please fill out the form below. Make sure you fill out as much information as possible in order to ease the task of the administrators.', - 'errorMessage' => '', 'test' => false, 'type' => 'string' ), @@ -5985,7 +5887,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Password length requirement. If it is not set or it is set to 0, then the default value is assumed (12).'), 'value' => '12', - 'errorMessage' => '', 'test' => 'testPasswordLength', 'type' => 'numeric', ), @@ -5993,7 +5894,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Password complexity requirement. Leave it empty for the default setting (3 out of 4, with either a digit or a special char) or enter your own regex. Keep in mind that the length is checked in another key. Default (simple 3 out of 4 or minimum 16 characters): /^((?=.*\d)|(?=.*\W+))(?![\n])(?=.*[A-Z])(?=.*[a-z]).*$|.{16,}/'), 'value' => '/^((?=.*\d)|(?=.*\W+))(?![\n])(?=.*[A-Z])(?=.*[a-z]).*$|.{16,}/', - 'errorMessage' => '', 'test' => 'testPasswordRegex', 'type' => 'string', ), @@ -6001,7 +5901,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('Enabling this setting will require users to submit their current password on any edits to their profile (including a triggered password change). For administrators, the confirmation will be required when changing the profile of any user. Could potentially mitigate an attacker trying to change a compromised user\'s password in order to establish persistance, however, enabling this feature will be highly annoying to users.'), 'value' => true, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', 'null' => true @@ -6010,7 +5909,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('Enabling this setting will sanitise the contents of an attribute on a soft delete'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', 'null' => true @@ -6019,7 +5917,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('Enabling this setting will block the organisation index from being visible to anyone besides site administrators on the current instance. Keep in mind that users can still see organisations that produce data via events, proposals, event history log entries, etc.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', 'null' => true @@ -6028,7 +5925,6 @@ class Server extends AppModel 'level' => self::SETTING_RECOMMENDED, 'description' => __('Enabling this setting will block the organisation list from being visible in sharing group besides user with sharing group permission.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', 'null' => true @@ -6037,7 +5933,6 @@ class Server extends AppModel 'level' => 0, 'description' => __('Disabling this setting will allow the creation/modification of local feeds (as opposed to network feeds). Enabling this setting will restrict feed sources to be network based only. When disabled, keep in mind that a malicious site administrator could get access to any arbitrary file on the system that the apache user has access to. Make sure that proper safe-guards are in place. This setting can only be modified via the CLI.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', 'null' => true, @@ -6056,7 +5951,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('Allow cross-origin requests to this instance, matching origins given in Security.cors_origins. Set to false to totally disable'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', 'null' => true @@ -6065,7 +5959,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('Set the origins from which MISP will allow cross-origin requests. Useful for external integration. Comma seperate if you need more than one.'), 'value' => '', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', 'null' => true @@ -6074,7 +5967,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('Enable this setting to create verbose logs of synced event data for debugging reasons. Logs are saved in your MISP directory\'s app/files/scripts/tmp/ directory.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBoolFalse', 'type' => 'boolean', 'null' => true @@ -6083,7 +5975,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('Enables the functionality to monitor users - thereby enabling all logging functionalities for a single user. This functionality is intrusive and potentially heavy on the system - use it with care.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', 'null' => true @@ -6092,11 +5983,31 @@ class Server extends AppModel 'level' => self::SETTING_OPTIONAL, 'description' => __('When enabled, logged in username will be included in X-Username HTTP response header. This is useful for request logging on webserver/proxy side.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', 'null' => true - ] + ], + 'encryption_key' => [ + 'level' => self::SETTING_OPTIONAL, + 'description' => __('Encryption key used to store sensitive data (like authkeys) in database encrypted. If empty, data are stored unecrypted. Required PHP 7.1 or newer.'), + 'value' => '', + 'test' => function ($value) { + if (strlen($value) < 32) { + return __('Encryption key must be at least 32 chars long.'); + } + return true; + }, + 'afterHook' => function ($setting, $new, $old) { + /** @var SystemSetting $systemSetting */ + $systemSetting = ClassRegistry::init('SystemSetting'); + $systemSetting->reencrypt($old, $new); + return true; + }, + 'type' => 'string', + 'null' => true, + 'cli_only' => true, + 'redacted' => true, + ], ), 'SecureAuth' => array( 'branch' => 1, @@ -6104,7 +6015,6 @@ class Server extends AppModel 'level' => 0, 'description' => __('The number of tries a user can try to login and fail before the bruteforce protection kicks in.'), 'value' => '', - 'errorMessage' => '', 'test' => 'testForNumeric', 'type' => 'string', ), @@ -6112,7 +6022,6 @@ class Server extends AppModel 'level' => 0, 'description' => __('The duration (in seconds) of how long the user will be locked out when the allowed number of login attempts are exhausted.'), 'value' => '', - 'errorMessage' => '', 'test' => 'testForNumeric', 'type' => 'string', ), @@ -6123,7 +6032,6 @@ class Server extends AppModel 'level' => 0, 'description' => __('Set to true to automatically regenerate sessions after x number of requests. This might lead to the user getting de-authenticated and is frustrating in general, so only enable it if you really need to regenerate sessions. (Not recommended)'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBoolFalse', 'type' => 'boolean', ), @@ -6131,7 +6039,6 @@ class Server extends AppModel 'level' => 0, 'description' => __('Set to true to check for the user agent string in each request. This can lead to occasional logouts (not recommended).'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBoolFalse', 'type' => 'boolean', ), @@ -6139,7 +6046,6 @@ class Server extends AppModel 'level' => 0, 'description' => __('The session type used by MISP. The default setting is php, which will use the session settings configured in php.ini for the session data (supported options: php, database). The recommended option is php and setting your PHP up to use redis sessions via your php.ini. Just add \'session.save_handler = redis\' and "session.save_path = \'tcp://localhost:6379\'" (replace the latter with your redis connection) to '), 'value' => '', - 'errorMessage' => '', 'test' => 'testForSessionDefaults', 'type' => 'string', 'options' => array('php' => 'php', 'database' => 'database', 'cake' => 'cake', 'cache' => 'cache'), @@ -6148,7 +6054,6 @@ class Server extends AppModel 'level' => 0, 'description' => __('The timeout duration of sessions (in MINUTES). 0 does not mean infinite for the PHP session handler, instead sessions will invalidate immediately.'), 'value' => '', - 'errorMessage' => '', 'test' => 'testForNumeric', 'type' => 'string' ), @@ -6156,7 +6061,6 @@ class Server extends AppModel 'level' => 0, 'description' => __('The expiration of the cookie (in MINUTES). The session timeout gets refreshed frequently, however the cookies do not. Generally it is recommended to have a much higher cookie_timeout than timeout.'), 'value' => '', - 'errorMessage' => '', 'test' => 'testForCookieTimeout', 'type' => 'numeric' ) @@ -6167,16 +6071,14 @@ class Server extends AppModel 'level' => 2, 'description' => __('The default policy action for the values added to the RPZ.'), 'value' => 1, - 'errorMessage' => '', 'test' => 'testForRPZBehaviour', 'type' => 'numeric', - 'options' => array(0 => 'DROP', 1 => 'NXDOMAIN', 2 => 'NODATA', 3 => 'Local-Data', 4 => 'PASSTHRU', 5 => 'TCP-only' ), + 'options' => array(0 => 'DROP', 1 => 'NXDOMAIN', 2 => 'NODATA', 3 => 'Local-Data', 4 => 'PASSTHRU', 5 => 'TCP-only'), ), 'RPZ_walled_garden' => array( 'level' => 2, 'description' => __('The default walled garden used by the RPZ export if the Local-Data policy setting is picked for the export.'), 'value' => '127.0.0.1', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', ), @@ -6184,7 +6086,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('The serial in the SOA portion of the zone file. (numeric, best practice is yyyymmddrr where rr is the two digit sub-revision of the file. $date will automatically get converted to the current yyyymmdd, so $date00 is a valid setting). Setting it to $time will give you an unixtime-based serial (good then you need more than 99 revisions per day).'), 'value' => '$date00', - 'errorMessage' => '', 'test' => 'testForRPZSerial', 'type' => 'string', ), @@ -6192,7 +6093,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('The refresh specified in the SOA portion of the zone file. (in seconds, or shorthand duration such as 15m)'), 'value' => '2h', - 'errorMessage' => '', 'test' => 'testForRPZDuration', 'type' => 'string', ), @@ -6200,7 +6100,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('The retry specified in the SOA portion of the zone file. (in seconds, or shorthand duration such as 15m)'), 'value' => '30m', - 'errorMessage' => '', 'test' => 'testForRPZDuration', 'type' => 'string', ), @@ -6208,7 +6107,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('The expiry specified in the SOA portion of the zone file. (in seconds, or shorthand duration such as 15m)'), 'value' => '30d', - 'errorMessage' => '', 'test' => 'testForRPZDuration', 'type' => 'string', ), @@ -6216,7 +6114,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('The minimum TTL specified in the SOA portion of the zone file. (in seconds, or shorthand duration such as 15m)'), 'value' => '1h', - 'errorMessage' => '', 'test' => 'testForRPZDuration', 'type' => 'string', ), @@ -6224,7 +6121,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('The TTL of the zone file. (in seconds, or shorthand duration such as 15m)'), 'value' => '1w', - 'errorMessage' => '', 'test' => 'testForRPZDuration', 'type' => 'string', ), @@ -6232,7 +6128,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Nameserver'), 'value' => 'localhost.', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', ), @@ -6240,7 +6135,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Alternate nameserver'), 'value' => '', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', ), @@ -6248,7 +6142,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('The e-mail address specified in the SOA portion of the zone file.'), 'value' => 'root.localhost', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', ), @@ -6256,7 +6149,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Enables or disables the Kafka pub feature of MISP. Make sure that you install the requirements for the plugin to work. Refer to the installation instructions for more information.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', ), @@ -6264,7 +6156,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('A comma separated list of Kafka bootstrap brokers'), 'value' => 'kafka:9092', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', ), @@ -6272,7 +6163,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('A path to an ini file with configuration options to be passed to rdkafka. Section headers in the ini file will be ignored.'), 'value' => '/etc/rdkafka.ini', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', ), @@ -6280,7 +6170,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Enable this setting to include the base64 encoded payloads of malware-samples/attachments in the output.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean' ), @@ -6288,7 +6177,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Enables or disables the publishing of any event creations/edits/deletions.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean' ), @@ -6296,7 +6184,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Topic for publishing event creations/edits/deletions.'), 'value' => 'misp_event', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string' ), @@ -6304,7 +6191,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('If enabled it will publish to Kafka the event at the time that the event gets published in MISP. Event actions (creation or edit) will not be published to Kafka.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean' ), @@ -6312,7 +6198,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Topic for publishing event information on publish.'), 'value' => 'misp_event_publish', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string' ), @@ -6320,7 +6205,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Enables or disables the publishing of any object creations/edits/deletions.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean' ), @@ -6328,7 +6212,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Topic for publishing object creations/edits/deletions.'), 'value' => 'misp_object', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string' ), @@ -6336,7 +6219,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Enables or disables the publishing of any object reference creations/deletions.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean' ), @@ -6344,7 +6226,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Topic for publishing object reference creations/deletions.'), 'value' => 'misp_object_reference', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string' ), @@ -6352,7 +6233,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Enables or disables the publishing of any attribute creations/edits/soft deletions.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean' ), @@ -6360,7 +6240,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Topic for publishing attribute creations/edits/soft deletions.'), 'value' => 'misp_attribute', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string' ), @@ -6368,7 +6247,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Enables or disables the publishing of any proposal creations/edits/deletions.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean' ), @@ -6376,7 +6254,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Topic for publishing proposal creations/edits/deletions.'), 'value' => 'misp_shadow_attribute', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string' ), @@ -6384,7 +6261,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Enables or disables the publishing of any tag creations/edits/deletions as well as tags being attached to / detached from various MISP elements.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean' ), @@ -6392,7 +6268,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Topic for publishing tag creations/edits/deletions as well as tags being attached to / detached from various MISP elements.'), 'value' => 'misp_tag', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string' ), @@ -6400,7 +6275,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Enables or disables the publishing of new sightings.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean' ), @@ -6408,7 +6282,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Topic for publishing sightings.'), 'value' => 'misp_sighting', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string' ), @@ -6416,7 +6289,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Enables or disables the publishing of new/modified users.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean' ), @@ -6424,7 +6296,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Topic for publishing new/modified users.'), 'value' => 'misp_user', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string' ), @@ -6432,7 +6303,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Enables or disables the publishing of new/modified organisations.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean' ), @@ -6440,7 +6310,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Topic for publishing new/modified organisations.'), 'value' => 'misp_organisation', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string' ), @@ -6448,7 +6317,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Enables or disables the publishing of log entries. Keep in mind, this can get pretty verbose depending on your logging settings.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean' ), @@ -6456,7 +6324,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Topic for publishing log entries.'), 'value' => 'misp_audit', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string' ), @@ -6464,7 +6331,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Enables or disables the pub/sub feature of MISP. Make sure that you install the requirements for the plugin to work. Refer to the installation instructions for more information.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', 'afterHook' => 'zmqAfterHook', @@ -6473,7 +6339,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('The host that the pub/sub feature will use.'), 'value' => '127.0.0.1', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', 'afterHook' => 'zmqAfterHook', @@ -6482,7 +6347,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('The port that the pub/sub feature will use.'), 'value' => 50000, - 'errorMessage' => '', 'test' => 'testForZMQPortNumber', 'type' => 'numeric', 'afterHook' => 'zmqAfterHook', @@ -6491,7 +6355,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('The username that client need to use to connect to ZeroMQ.'), 'value' => '', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', 'afterHook' => 'zmqAfterHook', @@ -6500,7 +6363,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('The password that client need to use to connect to ZeroMQ.'), 'value' => '', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', 'afterHook' => 'zmqAfterHook', @@ -6509,7 +6371,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Location of the Redis db used by MISP and the Python PUB script to queue data to be published.'), 'value' => 'localhost', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', 'afterHook' => 'zmqAfterHook', @@ -6518,7 +6379,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('The port that Redis is listening on.'), 'value' => 6379, - 'errorMessage' => '', 'test' => 'testForPortNumber', 'type' => 'numeric', 'afterHook' => 'zmqAfterHook', @@ -6527,7 +6387,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('The password, if set for Redis.'), 'value' => '', - 'errorMessage' => '', 'type' => 'string', 'afterHook' => 'zmqAfterHook', ), @@ -6535,7 +6394,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('The database to be used for queuing messages for the pub/sub functionality.'), 'value' => 1, - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', 'afterHook' => 'zmqAfterHook', @@ -6544,7 +6402,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('The namespace to be used for queuing messages for the pub/sub functionality.'), 'value' => 'mispq', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', 'afterHook' => 'zmqAfterHook', @@ -6553,7 +6410,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Enable this setting to include the base64 encoded payloads of malware-samples/attachments in the output.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean' ), @@ -6561,7 +6417,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Enables or disables the publishing of any event creations/edits/deletions.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean' ), @@ -6569,7 +6424,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Enables or disables the publishing of any object creations/edits/deletions.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean' ), @@ -6577,7 +6431,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Enables or disables the publishing of any object reference creations/deletions.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean' ), @@ -6585,7 +6438,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Enables or disables the publishing of any attribute creations/edits/soft deletions.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean' ), @@ -6593,7 +6445,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Enables or disables the publishing of any tag creations/edits/deletions as well as tags being attached to / detached from various MISP elements.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean' ), @@ -6601,7 +6452,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Enables or disables the publishing of new sightings to the ZMQ pubsub feed.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean' ), @@ -6609,7 +6459,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Enables or disables the publishing of new/modified users to the ZMQ pubsub feed.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean' ), @@ -6617,7 +6466,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Enables or disables the publishing of new/modified organisations to the ZMQ pubsub feed.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean' ), @@ -6625,15 +6473,13 @@ class Server extends AppModel 'level' => 2, 'description' => __('Enables or disables the publishing of log entries to the ZMQ pubsub feed. Keep in mind, this can get pretty verbose depending on your logging settings.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean' ), - 'ZeroMQ_warninglist_notifications_enable' => array( + 'ZeroMQ_warninglist_notifications_enable' => array( 'level' => 2, 'description' => __('Enables or disables the publishing of new/modified warninglist to the ZMQ pubsub feed.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean' ), @@ -6641,7 +6487,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Enabled logging to an ElasticSearch instance'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean' ), @@ -6649,7 +6494,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('The URL(s) at which to access ElasticSearch - comma separate if you want to have more than one.'), 'value' => '', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string' ), @@ -6657,7 +6501,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('The index in which to place logs'), 'value' => '', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string' ), @@ -6665,7 +6508,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Enables or disables uploading of malware samples to S3 rather than to disk (WARNING: Get permission from amazon first!)'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean' ), @@ -6673,7 +6515,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Bucket name to upload to'), 'value' => '', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string' ), @@ -6681,7 +6522,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Region in which your S3 bucket resides'), 'value' => '', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string' ), @@ -6689,7 +6529,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('AWS key to use when uploading samples (WARNING: It\' highly recommended that you use EC2 IAM roles if at all possible)'), 'value' => '', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string' ), @@ -6697,7 +6536,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('AWS secret key to use when uploading samples'), 'value' => '', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string' ), @@ -6705,7 +6543,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('This setting defines who will have access to seeing the reported sightings. The default setting is the event owner organisation alone (in addition to everyone seeing their own contribution) with the other options being Sighting reporters (meaning the event owner and any organisation that provided sighting data about the event) and Everyone (meaning anyone that has access to seeing the event / attribute).'), 'value' => 0, - 'errorMessage' => '', 'test' => 'testForSightingVisibility', 'type' => 'numeric', 'options' => array( @@ -6719,7 +6556,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('Enabling the anonymisation of sightings will simply aggregate all sightings instead of showing the organisations that have reported a sighting. Users will be able to tell the number of sightings their organisation has submitted and the number of sightings for other organisations'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', ), @@ -6727,7 +6563,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('When pushing sightings to another server, report all sightings from this instance as this organisation. This effectively hides all sightings from this instance behind a single organisation to the outside world. Sightings pulled from this instance follow the Sightings_policy above.'), 'value' => '0', - 'errorMessage' => '', 'test' => 'testLocalOrg', 'type' => 'numeric', 'optionsSource' => function () { @@ -6738,7 +6573,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('Set the range in which sightings will be taken into account when generating graphs. For example a sighting with a sighted_date of 7 years ago might not be relevant anymore. Setting given in number of days, default is 365 days'), 'value' => 365, - 'errorMessage' => '', 'test' => 'testForNumeric', 'type' => 'numeric' ), @@ -6746,7 +6580,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('Enable SightingDB integration.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean' ), @@ -6754,7 +6587,6 @@ class Server extends AppModel '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.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', 'null' => true, @@ -6764,7 +6596,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Set the header that MISP should look for here. If left empty it will default to the Authorization header.'), 'value' => 'Authorization', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', 'null' => true @@ -6773,7 +6604,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Use a header namespace for the auth header - default setting is enabled'), 'value' => true, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', 'null' => true @@ -6782,7 +6612,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('The default header namespace for the auth header - default setting is HTTP_'), 'value' => 'HTTP_', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', 'null' => true @@ -6791,7 +6620,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('If this setting is enabled then the only way to authenticate will be using the custom header. Alternatively, you can run in mixed mode that will log users in via the header if found, otherwise users will be redirected to the normal login page.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', 'null' => true @@ -6800,7 +6628,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('If you are using an external tool to authenticate with MISP and would like to only allow the tool\'s url as a valid point of entry then set this field. '), 'value' => '', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', 'null' => true @@ -6809,7 +6636,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('The name of the authentication method, this is cosmetic only and will be shown on the user creation page and logs.'), 'value' => 'External authentication', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', 'null' => true @@ -6818,7 +6644,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Disable the logout button for users authenticate with the external auth mechanism.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean' ), @@ -6826,7 +6651,6 @@ class Server extends AppModel 'level' => 0, 'description' => __('Enable/disable the enrichment services'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean' ), @@ -6834,7 +6658,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('Set a timeout for the enrichment services'), 'value' => 10, - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'numeric' ), @@ -6842,7 +6665,6 @@ class Server extends AppModel 'level' => 0, 'description' => __('Enable/disable the import services'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean' ), @@ -6850,7 +6672,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('Set a timeout for the import services'), 'value' => 10, - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'numeric' ), @@ -6858,7 +6679,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('The url used to access the import services. By default, it is accessible at http://127.0.0.1:6666'), 'value' => 'http://127.0.0.1', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string' ), @@ -6866,7 +6686,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('The port used to access the import services. By default, it is accessible at 127.0.0.1:6666'), 'value' => '6666', - 'errorMessage' => '', 'test' => 'testForPortNumber', 'type' => 'numeric' ), @@ -6874,7 +6693,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('The url used to access the export services. By default, it is accessible at http://127.0.0.1:6666'), 'value' => 'http://127.0.0.1', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string' ), @@ -6882,7 +6700,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('The port used to access the export services. By default, it is accessible at 127.0.0.1:6666'), 'value' => '6666', - 'errorMessage' => '', 'test' => 'testForPortNumber', 'type' => 'numeric' ), @@ -6890,7 +6707,6 @@ class Server extends AppModel 'level' => 0, 'description' => __('Enable/disable the export services'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean' ), @@ -6898,7 +6714,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('Set a timeout for the export services'), 'value' => 10, - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'numeric' ), @@ -6906,7 +6721,6 @@ class Server extends AppModel 'level' => 0, 'description' => __('Enable/disable the hover over information retrieved from the enrichment modules'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean' ), @@ -6914,7 +6728,6 @@ class Server extends AppModel 'level' => 0, 'description' => __('When enabled, users have to click on the magnifier icon to show the enrichment'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean' ), @@ -6922,7 +6735,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('Set a timeout for the hover services'), 'value' => 5, - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'numeric' ), @@ -6930,7 +6742,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('The url used to access the enrichment services. By default, it is accessible at http://127.0.0.1:6666'), 'value' => 'http://127.0.0.1', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string' ), @@ -6938,7 +6749,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('The port used to access the enrichment services. By default, it is accessible at 127.0.0.1:6666'), 'value' => 6666, - 'errorMessage' => '', 'test' => 'testForPortNumber', 'type' => 'numeric' ), @@ -6946,7 +6756,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('The url used to access Cortex. By default, it is accessible at http://cortex-url'), 'value' => 'http://127.0.0.1', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string' ), @@ -6954,7 +6763,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('The port used to access Cortex. By default, this is port 9000'), 'value' => 9000, - 'errorMessage' => '', 'test' => 'testForPortNumber', 'type' => 'numeric' ), @@ -6962,7 +6770,6 @@ class Server extends AppModel 'level' => 0, 'description' => __('Enable/disable the Cortex services'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean' ), @@ -6970,7 +6777,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('Set an authentication key to be passed to Cortex'), 'value' => '', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', 'null' => true @@ -6979,7 +6785,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('Set a timeout for the Cortex services'), 'value' => 120, - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'numeric' ), @@ -6987,7 +6792,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('Set to false to disable SSL verification. This is not recommended.'), 'value' => true, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', 'null' => true @@ -6996,7 +6800,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('Set to false if you wish to ignore hostname match errors when validating certificates.'), 'value' => true, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', 'null' => true @@ -7005,7 +6808,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('Set to true to enable self-signed certificates to be accepted. This requires Cortex_ssl_verify_peer to be enabled.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', 'null' => true @@ -7014,7 +6816,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('Set to the absolute path of the Certificate Authority file that you wish to use for verifying SSL certificates.'), 'value' => '', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', 'null' => true @@ -7023,7 +6824,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Provide your custom authentication users with an external URL to the authentication system to reset their passwords.'), 'value' => '', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', 'null' => true @@ -7032,7 +6832,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('Provide a custom logout URL for your users that will log them out using the authentication system you use.'), 'value' => '', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', 'null' => true @@ -7041,7 +6840,6 @@ class Server extends AppModel 'level' => 1, 'description' => __('Enable lookups for additional relations via CyCat.'), 'value' => false, - 'errorMessage' => '', 'test' => 'testBool', 'type' => 'boolean', 'null' => true @@ -7050,7 +6848,6 @@ class Server extends AppModel 'level' => 2, 'description' => __('URL to use for CyCat lookups, if enabled.'), 'value' => 'https://api.cycat.org', - 'errorMessage' => '', 'test' => 'testForEmpty', 'type' => 'string', 'null' => true @@ -7060,7 +6857,6 @@ class Server extends AppModel 'level' => 0, 'description' => __('The debug level of the instance, always use 0 for production instances.'), 'value' => '', - 'errorMessage' => '', 'test' => 'testDebug', 'type' => 'numeric', 'options' => array(0 => 'Debug off', 1 => 'Debug on', 2 => 'Debug + SQL dump'), @@ -7069,7 +6865,6 @@ class Server extends AppModel 'level' => 0, 'description' => __('The debug level of the instance for site admins. This feature allows site admins to run debug mode on a live instance without exposing it to other users. The most verbose option of debug and site_admin_debug is used for site admins.'), 'value' => '', - 'errorMessage' => '', 'test' => 'testDebugAdmin', 'type' => 'boolean', 'null' => true diff --git a/app/Model/SystemSetting.php b/app/Model/SystemSetting.php new file mode 100644 index 000000000..b789f1e59 --- /dev/null +++ b/app/Model/SystemSetting.php @@ -0,0 +1,198 @@ +value = $value; + } + + /** + * @return mixed + * @throws JsonException + * @throws Exception + */ + public function decrypt() + { + $decrypt = BetterSecurity::decrypt($this->value, Configure::read('Security.encryption_key')); + return JsonTool::decode($decrypt); + } + + public function __toString() + { + return $this->decrypt(); + } + + public function jsonSerialize() + { + return $this->decrypt(); + } +} + +class SystemSetting extends AppModel +{ + public $actsAs = [ + 'AuditLog' + ]; + + public $primaryKey = 'setting'; + + // Blocked setting that cannot be saved or fetched from DB. The same as cli_only settings. + const BLOCKED_SETTINGS = [ + 'Security.encryption_key', + 'Security.disable_local_feed_access', + 'GnuPG.binary', + 'MISP.python_bin', + 'MISP.ca_path', + 'MISP.tmpdir', + 'MISP.system_setting_db', + 'MISP.attachments_dir', + ]; + + // Allow to set config values just for these categories + const ALLOWED_CATEGORIES = [ + 'MISP', + 'Security', + 'GnuPG', + 'SMIME', + 'Proxy', + 'SecureAuth', + 'Session', + 'Plugin', + 'debug', + 'site_admin_debug', + ]; + + /** + * Set config values from database into global Configure class + */ + public static function setGlobalSetting() + { + $systemSetting = ClassRegistry::init('SystemSetting'); + $settings = $systemSetting->getSettings(); + foreach ($settings as $settingName => $settingValue) { + $firstPart = explode('.', $settingName)[0]; + if (in_array($firstPart, self::ALLOWED_CATEGORIES, true) && !in_array($settingName, self::BLOCKED_SETTINGS, true)) { + Configure::write($settingName, $settingValue); + } + } + } + + /** + * @return array + */ + public function getSettings() + { + $settings = $this->find('list', [ + 'fields' => ['SystemSetting.setting', 'SystemSetting.value'], + ]); + return array_map([$this, 'decode'], $settings); + } + + /** + * @param string $setting Setting name + * @param mixed $value + * @throws Exception + */ + public function setSetting($setting, $value) + { + $firstPart = explode('.', $setting)[0]; + if (!in_array($firstPart, self::ALLOWED_CATEGORIES, true) || in_array($setting, self::BLOCKED_SETTINGS, true)) { + return false; // blocked setting + } + + if ($value === '' || $value === null) { + if ($this->hasAny(['SystemSetting.setting' => $setting])) { + return $this->delete($setting); // delete the whole setting when value is empty + } + return true; + } + + $value = JsonTool::encode($value); + + // If encryption is enabled and setting name contains `password` or `apikey` string, encrypt value to protect it + $key = Configure::read('Security.encryption_key'); + if ($key && self::isSensitive($setting)) { + $value = EncryptedValue::ENCRYPTED_MAGIC . BetterSecurity::encrypt($value, $key); + } + + $valid = $this->save(['SystemSetting' => [ + 'setting' => $setting, + 'value' => $value, + ]]); + if (!$valid) { + throw new Exception("Could not save system setting `$setting` because of validation errors: " . JsonTool::encode($this->validationErrors)); + } + return true; + } + + /** + * @param string|null $old Old (or current) encryption key. + * @param string|null $new New encryption key. If empty, encrypted values will be decrypted. + * @throws JsonException + */ + public function reencrypt($old, $new) + { + $settings = $this->find('list', [ + 'fields' => ['SystemSetting.setting', 'SystemSetting.value'], + ]); + $toSave = []; + foreach ($settings as $setting => $value) { + if (!self::isSensitive($setting)) { + continue; + } + if (substr($value, 0, 2) === EncryptedValue::ENCRYPTED_MAGIC) { + $value = BetterSecurity::decrypt(substr($value, 2), $old); + } + if (!empty($new)) { + $value = EncryptedValue::ENCRYPTED_MAGIC . BetterSecurity::encrypt($value, $new); + } + $toSave[] = ['SystemSetting' => [ + 'setting' => $setting, + 'value' => $value, + ]]; + } + return $this->saveMany($toSave); + } + + /** + * @param string $value + * @return EncryptedValue|mixed + * @throws JsonException + */ + private function decode($value) + { + if (substr($value, 0, 2) === EncryptedValue::ENCRYPTED_MAGIC) { + return new EncryptedValue(substr($value, 2)); + } else { + return JsonTool::decode($value); + } + } + + /** + * Sensitive setting are passwords or api keys. + * @param string $setting Setting name + * @return bool + */ + public static function isSensitive($setting) + { + if ($setting === 'Security.encryption_key' || $setting === 'Security.salt') { + return true; + } + if (substr($setting, 0, 7) === 'Plugin.' && (strpos($setting, 'apikey') !== false || strpos($setting, 'secret') !== false)) { + return true; + } + return strpos($setting, 'password') !== false; + } +} diff --git a/app/Model/Template.php b/app/Model/Template.php index 4bc516960..44c92c70d 100644 --- a/app/Model/Template.php +++ b/app/Model/Template.php @@ -72,9 +72,4 @@ class Template extends AppModel return false; } } - - public function generateRandomFileName() - { - return (new RandomTool())->random_str(false, 12); - } } diff --git a/app/Plugin/ShibbAuth/Controller/Component/Auth/ApacheShibbAuthenticate.php b/app/Plugin/ShibbAuth/Controller/Component/Auth/ApacheShibbAuthenticate.php index 34ea870e2..a77fd389f 100644 --- a/app/Plugin/ShibbAuth/Controller/Component/Auth/ApacheShibbAuthenticate.php +++ b/app/Plugin/ShibbAuth/Controller/Component/Auth/ApacheShibbAuthenticate.php @@ -39,10 +39,11 @@ class ApacheShibbAuthenticate extends BaseAuthenticate * 'group_one' => 1, * ), * 'DefaultOrg' => 'MY_ORG', - * 'BlockOrgModifications' => false // set to true if you wish for the user's organisation never to be updated during login. Especially useful if you manually change organisations in MISP - * 'DefaultRole' => false // set to a specific value if you wish to hard-set users created via ApacheShibbAuth - * 'BlockRoleModifications' => false // set to true if you wish for the roles never to be updated during login. Especially * // useful if you manually change roles in MISP - * 'BlockOrgModifications' => false // set to true if you wish for the organizations never to be updated during login. Especially * // useful if you manually change orgs in MISP + * 'DefaultRole' => false, // set to a specific value if you wish to hard-set users created via ApacheShibbAuth + * 'BlockRoleModifications' => false, // set to true if you wish for the roles never to be updated during login. Especially + * // useful if you manually change roles in MISP + * 'BlockOrgModifications' => false, // set to true if you wish for the organizations never to be updated during login. Especially + * // useful if you manually change orgs in MISP * ), * @param CakeRequest $request The request that contains login information. * @param CakeResponse $response Unused response object. diff --git a/app/Plugin/ShibbAuth/README.md b/app/Plugin/ShibbAuth/README.md index 2ea9eb8b9..abfb80efe 100644 --- a/app/Plugin/ShibbAuth/README.md +++ b/app/Plugin/ShibbAuth/README.md @@ -145,6 +145,9 @@ in the list given by apache. By default, you can leave it at ';'. 'possible_group_attribute_value_1' => 1, ), 'DefaultOrg' => 'MISP_DEFAULT_ORG', + 'DefaultRole' => false, // set to a specific value if you wish to hard-set users created via ApacheShibbAuth + 'BlockRoleModifications' => false, // set to true if you wish for the roles never to be updated during login. Especially useful if you manually change roles in MISP + 'BlockOrgModifications' => false, // set to true if you wish for the organizations never to be updated during login. Especially useful if you manually change orgs in MISP ), ``` diff --git a/app/View/Attributes/search.ctp b/app/View/Attributes/search.ctp index f5c9a29e5..e94b014b0 100755 --- a/app/View/Attributes/search.ctp +++ b/app/View/Attributes/search.ctp @@ -28,10 +28,6 @@ 'type' => 'checkbox', 'label' => __('Only find IOCs flagged as to IDS') )); - echo $this->Form->input('alternate', array( - 'type' => 'checkbox', - 'label' => __('Alternate Search Result (Events)') - )); echo $this->Form->input('first_seen', array( 'type' => 'text', 'div' => 'input hidden', diff --git a/app/View/Elements/genericElements/IndexTable/Fields/target_event.ctp b/app/View/Elements/genericElements/IndexTable/Fields/target_event.ctp index 0422c8813..fe69b77a4 100644 --- a/app/View/Elements/genericElements/IndexTable/Fields/target_event.ctp +++ b/app/View/Elements/genericElements/IndexTable/Fields/target_event.ctp @@ -1,33 +1,33 @@ DataPathCollector->extract($row, $field['data_path']); - if ($data['Feed.enabled']) { - if (in_array($data['Feed.source_format'], array('freetext', 'csv'))) { - if ($data['Feed.fixed_event']) { - if (!empty($data['Feed.event_error'])) { +$feed = $row['Feed']; +if ($feed['enabled']) { + if (in_array($feed['source_format'], array('freetext', 'csv'))) { + if ($feed['fixed_event']) { + if (!empty($feed['event_error'])) { + echo sprintf( + '%s', + __('Error: Invalid event!') + ); + } else { + if ($feed['event_id']) { echo sprintf( - '%s', - __('Error: Invalid event!') + '%s', + $baseurl, + h($feed['event_id']), + __('Fixed event %s', h($feed['event_id'])) ); } else { - if ($data['Feed.event_id']) { - echo sprintf( - '%s', - $baseurl, - h($data['Feed.event_id']), - __('Fixed event %s', h($data['Feed.event_id'])) - ); - } else { - echo __('New fixed event'); - } + echo __('New fixed event'); } - } else { - echo sprintf( - '%s', - __('New event each pull can lead to potentially endlessly growing correlation tables. Only use this setting if you are sure that the data in the feed will mostly be completely distinct between each individual pull, otherwise use fixed events. Generally this setting is NOT recommended.'), - __('New event each pull') - ); } + } else { + echo sprintf( + '%s', + __('New event each pull can lead to potentially endlessly growing correlation tables. Only use this setting if you are sure that the data in the feed will mostly be completely distinct between each individual pull, otherwise use fixed events. Generally this setting is NOT recommended.'), + __('New event each pull') + ); } - } else { - echo __('Feed not enabled'); } +} else { + echo __('Feed not enabled'); +} diff --git a/app/View/Elements/global_menu.ctp b/app/View/Elements/global_menu.ctp index 9303626ad..2793797a6 100755 --- a/app/View/Elements/global_menu.ctp +++ b/app/View/Elements/global_menu.ctp @@ -495,8 +495,8 @@ 'html' => sprintf( '', (!empty($homepage['path']) && $homepage['path'] === $this->here) ? 'orange' : '', - __('Set the current page as your home page in MISP'), - __('Set the current page as your home page in MISP'), + __('Set the current page as your home page in MISP'), + __('Set the current page as your home page in MISP'), h($this->here) ) ), @@ -505,6 +505,12 @@ 'url' => empty($homepage['path']) ? $baseurl : $baseurl . h($homepage['path']), 'html' => '' ), + [ + 'type' => 'root', + 'url' => Configure::read('MISP.menu_custom_right_link'), + 'html' => Configure::read('MISP.menu_custom_right_link_html'), + 'requirement' => !empty(Configure::read('MISP.menu_custom_right_link')), + ], array( 'type' => 'root', 'url' => $baseurl . '/dashboards', @@ -528,7 +534,7 @@ ); } ?> -