From aeaa833f64d65c1f66d1ec34f84c265635e50833 Mon Sep 17 00:00:00 2001 From: Sami Mokaddem Date: Mon, 17 Jan 2022 11:29:50 +0100 Subject: [PATCH 1/8] new: [CodeMirror] Shows a placeholder whenever the textarea is empty --- templates/layout/default.php | 1 + .../css/CodeMirror/codemirror-additional.css | 4 + .../CodeMirror/addon/display/placeholder.js | 78 +++++++++++++++++++ 3 files changed, 83 insertions(+) create mode 100644 webroot/js/CodeMirror/addon/display/placeholder.js diff --git a/templates/layout/default.php b/templates/layout/default.php index 849592e..59d2efe 100644 --- a/templates/layout/default.php +++ b/templates/layout/default.php @@ -52,6 +52,7 @@ $sidebarOpen = $loggedUser->user_settings_by_name_with_fallback['ui.sidebar.expa Html->script('CodeMirror/addon/lint/json-lint') ?> Html->script('CodeMirror/addon/edit/matchbrackets') ?> Html->script('CodeMirror/addon/edit/closebrackets') ?> + Html->script('CodeMirror/addon/display/placeholder') ?> Html->css('CodeMirror/codemirror') ?> Html->css('CodeMirror/codemirror-additional') ?> Html->css('CodeMirror/addon/hint/show-hint') ?> diff --git a/webroot/css/CodeMirror/codemirror-additional.css b/webroot/css/CodeMirror/codemirror-additional.css index 0c62e18..4399c1a 100644 --- a/webroot/css/CodeMirror/codemirror-additional.css +++ b/webroot/css/CodeMirror/codemirror-additional.css @@ -26,4 +26,8 @@ .CodeMirror-hints { z-index: 1060 !important; /* Make sure hint is above modal */ +} + +.CodeMirror pre.CodeMirror-placeholder { + color: #999; } \ No newline at end of file diff --git a/webroot/js/CodeMirror/addon/display/placeholder.js b/webroot/js/CodeMirror/addon/display/placeholder.js new file mode 100644 index 0000000..d8e2dbd --- /dev/null +++ b/webroot/js/CodeMirror/addon/display/placeholder.js @@ -0,0 +1,78 @@ +// CodeMirror, copyright (c) by Marijn Haverbeke and others +// Distributed under an MIT license: https://codemirror.net/LICENSE + +(function (mod) { + if (typeof exports == "object" && typeof module == "object") // CommonJS + mod(require("../../lib/codemirror")); + else if (typeof define == "function" && define.amd) // AMD + define(["../../lib/codemirror"], mod); + else // Plain browser env + mod(CodeMirror); +})(function (CodeMirror) { + CodeMirror.defineOption("placeholder", "", function (cm, val, old) { + var prev = old && old != CodeMirror.Init; + if (val && !prev) { + cm.on("blur", onBlur); + cm.on("change", onChange); + cm.on("swapDoc", onChange); + CodeMirror.on(cm.getInputField(), "compositionupdate", cm.state.placeholderCompose = function () { onComposition(cm) }) + onChange(cm); + } else if (!val && prev) { + cm.off("blur", onBlur); + cm.off("change", onChange); + cm.off("swapDoc", onChange); + CodeMirror.off(cm.getInputField(), "compositionupdate", cm.state.placeholderCompose) + clearPlaceholder(cm); + var wrapper = cm.getWrapperElement(); + wrapper.className = wrapper.className.replace(" CodeMirror-empty", ""); + } + + if (val && !cm.hasFocus()) onBlur(cm); + }); + + function clearPlaceholder(cm) { + if (cm.state.placeholder) { + cm.state.placeholder.parentNode.removeChild(cm.state.placeholder); + cm.state.placeholder = null; + } + } + function setPlaceholder(cm) { + clearPlaceholder(cm); + var elt = cm.state.placeholder = document.createElement("pre"); + elt.style.cssText = "height: 0; overflow: visible"; + elt.style.direction = cm.getOption("direction"); + elt.className = "CodeMirror-placeholder CodeMirror-line-like"; + var placeHolder = cm.getOption("placeholder") + if (typeof placeHolder == "string") placeHolder = document.createTextNode(placeHolder) + elt.appendChild(placeHolder) + cm.display.lineSpace.insertBefore(elt, cm.display.lineSpace.firstChild); + } + + function onComposition(cm) { + setTimeout(function () { + var empty = false + if (cm.lineCount() == 1) { + var input = cm.getInputField() + empty = input.nodeName == "TEXTAREA" ? !cm.getLine(0).length + : !/[^\u200b]/.test(input.querySelector(".CodeMirror-line").textContent) + } + if (empty) setPlaceholder(cm) + else clearPlaceholder(cm) + }, 20) + } + + function onBlur(cm) { + if (isEmpty(cm)) setPlaceholder(cm); + } + function onChange(cm) { + var wrapper = cm.getWrapperElement(), empty = isEmpty(cm); + wrapper.className = wrapper.className.replace(" CodeMirror-empty", "") + (empty ? " CodeMirror-empty" : ""); + + if (empty) setPlaceholder(cm); + else clearPlaceholder(cm); + } + + function isEmpty(cm) { + return (cm.lineCount() === 1) && (cm.getLine(0) === ""); + } +}); From f18307b3cb476e5c727d8f211a27b29e06be1f2d Mon Sep 17 00:00:00 2001 From: Sami Mokaddem Date: Mon, 17 Jan 2022 11:30:26 +0100 Subject: [PATCH 2/8] chg: [localTools:local_tool_connectors] Added support of CodeMirror placeholder --- .../local_tool_connectors/MispConnector.php | 5 +++++ .../SkeletonConnectorExample.php | 16 ++++++++++++++++ src/Model/Table/LocalToolsTable.php | 3 ++- templates/LocalTools/add.php | 3 ++- 4 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/Lib/default/local_tool_connectors/MispConnector.php b/src/Lib/default/local_tool_connectors/MispConnector.php index 473573a..a021839 100644 --- a/src/Lib/default/local_tool_connectors/MispConnector.php +++ b/src/Lib/default/local_tool_connectors/MispConnector.php @@ -122,6 +122,11 @@ class MispConnector extends CommonConnectorTools 'type' => 'boolean' ], ]; + public $settingsPlaceholder = [ + 'url' => 'https://your.misp.intance', + 'authkey' => '', + 'skip_ssl' => '0', + ]; public function addSettingValidatorRules($validator) { diff --git a/src/Lib/default/local_tool_connectors/SkeletonConnectorExample.php b/src/Lib/default/local_tool_connectors/SkeletonConnectorExample.php index ce9b62d..362c514 100644 --- a/src/Lib/default/local_tool_connectors/SkeletonConnectorExample.php +++ b/src/Lib/default/local_tool_connectors/SkeletonConnectorExample.php @@ -46,6 +46,22 @@ class SkeletonConnector extends CommonConnectorTools 'redirect' => 'serverSettingsAction' ] ]; + public $settings = [ + 'url' => [ + 'type' => 'text' + ], + 'authkey' => [ + 'type' => 'text' + ], + 'skip_ssl' => [ + 'type' => 'boolean' + ], + ]; + public $settingsPlaceholder = [ + 'url' => 'https://your.url', + 'authkey' => '', + 'skip_ssl' => '0', + ]; public function health(Object $connection): array { diff --git a/src/Model/Table/LocalToolsTable.php b/src/Model/Table/LocalToolsTable.php index ac29bf9..64d6168 100644 --- a/src/Model/Table/LocalToolsTable.php +++ b/src/Model/Table/LocalToolsTable.php @@ -143,7 +143,8 @@ class LocalToolsTable extends AppTable 'connector' => $connector_type, 'connector_version' => $connector_class->version, 'connector_description' => $connector_class->description, - 'connector_settings' => $connector_class->settings ?? [] + 'connector_settings' => $connector_class->settings ?? [], + 'connector_settings_placeholder' => $connector_class->settingsPlaceholder ?? [], ]; if ($includeConnections) { $connector['connections'] = $this->healthCheck($connector_type, $connector_class); diff --git a/templates/LocalTools/add.php b/templates/LocalTools/add.php index 184f0e6..5197f21 100644 --- a/templates/LocalTools/add.php +++ b/templates/LocalTools/add.php @@ -22,7 +22,8 @@ 'codemirror' => [ 'height' => '10rem', 'hints' => $connectors[0]['connector_settings'] - ] + ], + 'placeholder' => json_encode($connectors[0]['connector_settings_placeholder'], JSON_FORCE_OBJECT | JSON_PRETTY_PRINT) ], [ 'field' => 'description', From 0328bfed4686d47c8b356d4c3ea5dab31ce73887 Mon Sep 17 00:00:00 2001 From: iglocska Date: Mon, 17 Jan 2022 13:20:34 +0100 Subject: [PATCH 3/8] fix: [inividuals] add shouldn't have the tagging options - can't tag that which does not exist yet --- templates/Individuals/add.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/templates/Individuals/add.php b/templates/Individuals/add.php index 935c2de..19bcf36 100644 --- a/templates/Individuals/add.php +++ b/templates/Individuals/add.php @@ -23,7 +23,8 @@ ), array( 'field' => 'tag_list', - 'type' => 'tags' + 'type' => 'tags', + 'requirements' => $this->request->getParam('action') === 'edit' ), ), 'metaTemplates' => empty($metaTemplates) ? [] : $metaTemplates, From 1c81257b7596f55d91839f5b485dc8076538d143 Mon Sep 17 00:00:00 2001 From: Sami Mokaddem Date: Mon, 17 Jan 2022 15:22:52 +0100 Subject: [PATCH 4/8] fix: [helpers:bootstrap] Table's cell generator gets the correct row index --- src/View/Helper/BootstrapHelper.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/View/Helper/BootstrapHelper.php b/src/View/Helper/BootstrapHelper.php index 6d9275f..ee24495 100644 --- a/src/View/Helper/BootstrapHelper.php +++ b/src/View/Helper/BootstrapHelper.php @@ -637,13 +637,13 @@ class BoostrapTable extends BootstrapGeneric { ], ]); foreach ($this->items as $i => $row) { - $body .= $this->genRow($row); + $body .= $this->genRow($row, $i); } $body .= $this->closeNode('tbody'); return $body; } - private function genRow($row) + private function genRow($row, $rowIndex) { $html = $this->openNode('tr',[ 'class' => [ @@ -658,21 +658,21 @@ class BoostrapTable extends BootstrapGeneric { $key = $field; } $cellValue = Hash::get($row, $key); - $html .= $this->genCell($cellValue, $field, $row, $i); + $html .= $this->genCell($cellValue, $field, $row, $rowIndex); } } else { // indexed array - foreach ($row as $cellValue) { - $html .= $this->genCell($cellValue, $field, $row, $i); + foreach ($row as $i => $cellValue) { + $html .= $this->genCell($cellValue, 'index', $row, $rowIndex); } } $html .= $this->closeNode('tr'); return $html; } - private function genCell($value, $field=[], $row=[], $i=0) + private function genCell($value, $field=[], $row=[], $rowIndex=0) { if (isset($field['formatter'])) { - $cellContent = $field['formatter']($value, $row, $i); + $cellContent = $field['formatter']($value, $row, $rowIndex); } else if (isset($field['element'])) { $cellContent = $this->btHelper->getView()->element($field['element'], [ 'data' => [$value], From ef2827e87a43cf7360abd161633d54710fc73dec Mon Sep 17 00:00:00 2001 From: Sami Mokaddem Date: Mon, 17 Jan 2022 15:24:30 +0100 Subject: [PATCH 5/8] fix: [userSettings] Various permissions issues --- src/Controller/UserSettingsController.php | 64 ++++++++++++++++++++--- src/Model/Table/UserSettingsTable.php | 2 +- 2 files changed, 57 insertions(+), 9 deletions(-) diff --git a/src/Controller/UserSettingsController.php b/src/Controller/UserSettingsController.php index 3d73707..d28f6ca 100644 --- a/src/Controller/UserSettingsController.php +++ b/src/Controller/UserSettingsController.php @@ -9,6 +9,8 @@ use \Cake\Database\Expression\QueryExpression; use Cake\Http\Exception\NotFoundException; use Cake\Http\Exception\MethodNotAllowedException; use Cake\Http\Exception\ForbiddenException; +use Cake\Http\Exception\UnauthorizedException; + class UserSettingsController extends AppController { @@ -19,8 +21,12 @@ class UserSettingsController extends AppController public function index() { $conditions = []; + $currentUser = $this->ACL->getUser(); + if (empty($currentUser['role']['perm_admin'])) { + $conditions['user_id'] = $currentUser->id; + } $this->CRUD->index([ - 'conditions' => [], + 'conditions' => $conditions, 'contain' => $this->containFields, 'filters' => $this->filterFields, 'quickFilters' => $this->quickFilterFields, @@ -39,6 +45,9 @@ class UserSettingsController extends AppController public function view($id) { + if (!$this->isLoggedUserAllowedToEdit($id)) { + throw new NotFoundException(__('Invalid {0}.', 'user setting')); + } $this->CRUD->view($id, [ 'contain' => ['Users'] ]); @@ -50,10 +59,13 @@ class UserSettingsController extends AppController public function add($user_id = false) { + $currentUser = $this->ACL->getUser(); $this->CRUD->add([ 'redirect' => ['action' => 'index', $user_id], - 'beforeSave' => function($data) use ($user_id) { - $data['user_id'] = $user_id; + 'beforeSave' => function ($data) use ($currentUser) { + if (empty($currentUser['role']['perm_admin'])) { + $data['user_id'] = $currentUser->id; + } return $data; } ]); @@ -61,10 +73,13 @@ class UserSettingsController extends AppController if (!empty($responsePayload)) { return $responsePayload; } + $allUsers = $this->UserSettings->Users->find('list', ['keyField' => 'id', 'valueField' => 'username'])->order(['username' => 'ASC']); + if (empty($currentUser['role']['perm_admin'])) { + $allUsers->where(['id' => $currentUser->id]); + $user_id = $currentUser->id; + } $dropdownData = [ - 'user' => $this->UserSettings->Users->find('list', [ - 'sort' => ['username' => 'asc'] - ]), + 'user' => $allUsers->all()->toArray(), ]; $this->set(compact('dropdownData')); $this->set('user_id', $user_id); @@ -75,6 +90,11 @@ class UserSettingsController extends AppController $entity = $this->UserSettings->find()->where([ 'id' => $id ])->first(); + + if (!$this->isLoggedUserAllowedToEdit($entity)) { + throw new NotFoundException(__('Invalid {0}.', 'user setting')); + } + $entity = $this->CRUD->edit($id, [ 'redirect' => ['action' => 'index', $entity->user_id] ]); @@ -94,6 +114,9 @@ class UserSettingsController extends AppController public function delete($id) { + if (!$this->isLoggedUserAllowedToEdit($id)) { + throw new NotFoundException(__('Invalid {0}.', 'user setting')); + } $this->CRUD->delete($id); $responsePayload = $this->CRUD->getResponsePayload(); if (!empty($responsePayload)) { @@ -160,7 +183,7 @@ class UserSettingsController extends AppController } } - public function getBookmarks($forSidebar=false) + public function getBookmarks($forSidebar = false) { $bookmarks = $this->UserSettings->getSettingByName($this->ACL->getUser(), $this->UserSettings->BOOKMARK_SETTING_NAME); $bookmarks = json_decode($bookmarks['value'], true); @@ -200,4 +223,29 @@ class UserSettingsController extends AppController $this->set('user_id', $this->ACL->getUser()->id); } -} \ No newline at end of file + /** + * isLoggedUserAllowedToEdit + * + * @param int|\App\Model\Entity\UserSetting $setting + * @return boolean + */ + private function isLoggedUserAllowedToEdit($setting): bool + { + $currentUser = $this->ACL->getUser(); + $isAllowed = false; + if (!empty($currentUser['role']['perm_admin'])) { + $isAllowed = true; + } else { + if (is_numeric($setting)) { + $setting = $this->UserSettings->find()->where([ + 'id' => $setting + ])->first(); + if (empty($setting)) { + return false; + } + } + $isAllowed = $setting->user_id == $currentUser->id; + } + return $isAllowed; + } +} diff --git a/src/Model/Table/UserSettingsTable.php b/src/Model/Table/UserSettingsTable.php index 4c7e708..bdfe535 100644 --- a/src/Model/Table/UserSettingsTable.php +++ b/src/Model/Table/UserSettingsTable.php @@ -11,7 +11,7 @@ use App\Settings\SettingsProvider\UserSettingsProvider; class UserSettingsTable extends AppTable { - protected $BOOKMARK_SETTING_NAME = 'ui.bookmarks'; + public $BOOKMARK_SETTING_NAME = 'ui.bookmarks'; public function initialize(array $config): void { From 98e8272810c91d0d81e59951df563e84a999f7ee Mon Sep 17 00:00:00 2001 From: Sami Mokaddem Date: Mon, 17 Jan 2022 15:29:58 +0100 Subject: [PATCH 6/8] fix: [ACL] Allow anyone to view encryption keys --- src/Controller/Component/ACLComponent.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Controller/Component/ACLComponent.php b/src/Controller/Component/ACLComponent.php index afa70ff..dbb618c 100644 --- a/src/Controller/Component/ACLComponent.php +++ b/src/Controller/Component/ACLComponent.php @@ -68,6 +68,7 @@ class ACLComponent extends Component 'view' => ['perm_admin'] ], 'EncryptionKeys' => [ + 'view' => ['*'], 'add' => ['*'], 'edit' => ['*'], 'delete' => ['*'], From 46870a4bcc53ed662ea669a2b364d051e03a20da Mon Sep 17 00:00:00 2001 From: Sami Mokaddem Date: Mon, 17 Jan 2022 15:45:51 +0100 Subject: [PATCH 7/8] fix: [organisation:add] Removed useless description field --- templates/Organisations/add.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/templates/Organisations/add.php b/templates/Organisations/add.php index ab3a17e..6278ff8 100644 --- a/templates/Organisations/add.php +++ b/templates/Organisations/add.php @@ -7,10 +7,6 @@ array( 'field' => 'name' ), - array( - 'field' => 'description', - 'type' => 'textarea' - ), array( 'field' => 'uuid', 'label' => 'UUID', From 49a3dd1623e41d802a661b49f30d87e1e5af91e7 Mon Sep 17 00:00:00 2001 From: Sami Mokaddem Date: Mon, 17 Jan 2022 15:55:55 +0100 Subject: [PATCH 8/8] chg: [instance] Added support of API response for 2 endpoints --- src/Controller/InstanceController.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/Controller/InstanceController.php b/src/Controller/InstanceController.php index 8479e5c..f136251 100644 --- a/src/Controller/InstanceController.php +++ b/src/Controller/InstanceController.php @@ -70,6 +70,12 @@ class InstanceController extends AppController usort($status, function($a, $b) { return strcmp($b['id'], $a['id']); }); + if ($this->ParamHandler->isRest()) { + return $this->RestResponse->viewData([ + 'status' => $status, + 'updateAvailables' => $migrationStatus['updateAvailables'], + ], 'json'); + } $this->set('status', $status); $this->set('updateAvailables', $migrationStatus['updateAvailables']); } @@ -140,6 +146,14 @@ class InstanceController extends AppController { $this->Settings = $this->getTableLocator()->get('Settings'); $all = $this->Settings->getSettings(true); + if ($this->ParamHandler->isRest()) { + return $this->RestResponse->viewData([ + 'settingsProvider' => $all['settingsProvider'], + 'settings' => $all['settings'], + 'settingsFlattened' => $all['settingsFlattened'], + 'notices' => $all['notices'], + ], 'json'); + } $this->set('settingsProvider', $all['settingsProvider']); $this->set('settings', $all['settings']); $this->set('settingsFlattened', $all['settingsFlattened']);