diff --git a/documentation/cerebrate-synchornization.pdf b/documentation/cerebrate-synchornization.pdf new file mode 100644 index 0000000..946306d Binary files /dev/null and b/documentation/cerebrate-synchornization.pdf differ diff --git a/documentation/local-tools-inter-connection.pdf b/documentation/local-tools-inter-connection.pdf new file mode 100644 index 0000000..5014c98 Binary files /dev/null and b/documentation/local-tools-inter-connection.pdf differ diff --git a/plugins/Tags/config/Migrations/20210831121348_TagSystem.php b/plugins/Tags/config/Migrations/20210831121348_TagSystem.php index bb2e412..98492a3 100644 --- a/plugins/Tags/config/Migrations/20210831121348_TagSystem.php +++ b/plugins/Tags/config/Migrations/20210831121348_TagSystem.php @@ -10,22 +10,22 @@ class TagSystem extends AbstractMigration $tags = $this->table('tags_tags'); $tags->addColumn('namespace', 'string', [ 'default' => null, - 'limit' => 255, + 'limit' => 191, 'null' => true, ]) ->addColumn('predicate', 'string', [ 'default' => null, - 'limit' => 255, + 'limit' => 191, 'null' => true, ]) ->addColumn('value', 'string', [ 'default' => null, - 'limit' => 255, + 'limit' => 191, 'null' => true, ]) ->addColumn('name', 'string', [ 'default' => null, - 'limit' => 255, + 'limit' => 191, 'null' => false, ]) ->addColumn('colour', 'string', [ @@ -66,7 +66,7 @@ class TagSystem extends AbstractMigration ]) ->addColumn('fk_model', 'string', [ 'default' => null, - 'limit' => 255, + 'limit' => 191, 'null' => false, 'comment' => 'The model name of the entity being tagged' ]) @@ -86,4 +86,4 @@ class TagSystem extends AbstractMigration $tagged->addIndex(['tag_id', 'fk_id', 'fk_model'], ['unique' => true]) ->update(); } -} \ No newline at end of file +} diff --git a/plugins/Tags/src/Model/Behavior/TagBehavior.php b/plugins/Tags/src/Model/Behavior/TagBehavior.php index b27ded9..5772d82 100644 --- a/plugins/Tags/src/Model/Behavior/TagBehavior.php +++ b/plugins/Tags/src/Model/Behavior/TagBehavior.php @@ -46,10 +46,10 @@ class TagBehavior extends Behavior $config = $this->getConfig(); $tagsAssoc = $config['tagsAssoc']; $taggedAssoc = $config['taggedAssoc']; - + $table = $this->_table; $tableAlias = $this->_table->getAlias(); - + $assocConditions = ['Tagged.fk_model' => $tableAlias]; if (!$table->hasAssociation('Tagged')) { @@ -114,7 +114,6 @@ class TagBehavior extends Behavior $property = $this->getConfig('tagsAssoc.propertyName'); $options['accessibleFields'][$property] = true; $options['associated']['Tags']['accessibleFields']['id'] = true; - if (isset($data['tags'])) { if (!empty($data['tags'])) { $data[$property] = $this->normalizeTags($data['tags']); @@ -131,7 +130,6 @@ class TagBehavior extends Behavior if (!$tag->isNew()) { continue; } - $existingTag = $this->getExistingTag($tag->name); if (!$existingTag) { continue; @@ -176,15 +174,14 @@ class TagBehavior extends Behavior $result[] = array_merge($common, ['id' => $existingTag->id]); continue; } - $result[] = array_merge( $common, [ 'name' => $tagIdentifier, + 'colour' => '#924da6' ] ); } - return $result; } @@ -312,7 +309,7 @@ class TagBehavior extends Behavior $key = 'Tags.' . $finderField; $taggedAlias = 'Tagged'; $foreignKey = $this->getConfig('tagsAssoc.foreignKey'); - + if (!empty($filterValue['AND'])) { $subQuery = $this->buildQuerySnippet($filterValue['AND'], $finderField, $OperatorAND); $modelAlias = $this->_table->getAlias(); @@ -352,4 +349,4 @@ class TagBehavior extends Behavior return $query; } -} \ No newline at end of file +} diff --git a/src/Controller/AppController.php b/src/Controller/AppController.php index 4d41bac..0bb2373 100644 --- a/src/Controller/AppController.php +++ b/src/Controller/AppController.php @@ -54,7 +54,6 @@ class AppController extends Controller public function initialize(): void { parent::initialize(); - $this->loadComponent('RequestHandler'); $this->loadComponent('Flash'); $this->loadComponent('RestResponse'); diff --git a/src/Controller/AuthKeysController.php b/src/Controller/AuthKeysController.php index 9e8ac03..454b922 100644 --- a/src/Controller/AuthKeysController.php +++ b/src/Controller/AuthKeysController.php @@ -64,8 +64,30 @@ class AuthKeysController extends AppController public function add() { $this->set('metaGroup', $this->isAdmin ? 'Administration' : 'Cerebrate'); + $validUsers = []; + $userConditions = []; + $currentUser = $this->ACL->getUser(); + if (empty($currentUser['role']['perm_admin'])) { + if (empty($currentUser['role']['perm_org_admin'])) { + $userConditions['id'] = $currentUser['id']; + } else { + $role_ids = $this->Users->Roles->find()->where(['perm_admin' => 0])->all()->extract('id')->toList(); + $userConditions['role_id IN'] = $role_ids; + } + } + $users = $this->Users->find('list'); + if (!empty($userConditions)) { + $users->where($userConditions); + } + $users = $users->order(['username' => 'asc'])->all()->toList(); $this->CRUD->add([ - 'displayOnSuccess' => 'authkey_display' + 'displayOnSuccess' => 'authkey_display', + 'beforeSave' => function($data) use ($users) { + if (!in_array($data['user_id'], array_keys($users))) { + return false; + } + return $data; + } ]); $responsePayload = $this->CRUD->getResponsePayload([ 'displayOnSuccess' => 'authkey_display' @@ -75,9 +97,7 @@ class AuthKeysController extends AppController } $this->loadModel('Users'); $dropdownData = [ - 'user' => $this->Users->find('list', [ - 'sort' => ['username' => 'asc'] - ]) + 'user' => $users ]; $this->set(compact('dropdownData')); } diff --git a/src/Controller/Component/ACLComponent.php b/src/Controller/Component/ACLComponent.php index 0c6d897..fb51f49 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' => ['*'], @@ -109,7 +110,7 @@ class ACLComponent extends Component 'batchAction' => ['perm_admin'], 'broodTools' => ['perm_admin'], 'connectionRequest' => ['perm_admin'], - 'connectLocal' => ['perm_admin'], + // 'connectLocal' => ['perm_admin'], 'delete' => ['perm_admin'], 'edit' => ['perm_admin'], 'exposedTools' => ['OR' => ['perm_admin', 'perm_sync']], diff --git a/src/Controller/Component/CRUDComponent.php b/src/Controller/Component/CRUDComponent.php index 4ebabff..4ebc674 100644 --- a/src/Controller/Component/CRUDComponent.php +++ b/src/Controller/Component/CRUDComponent.php @@ -175,6 +175,9 @@ class CRUDComponent extends Component $data = $this->Table->patchEntity($data, $input, $patchEntityParams); if (isset($params['beforeSave'])) { $data = $params['beforeSave']($data); + if ($data === false) { + throw new NotFoundException(__('Could not save {0} due to the input failing to meet expectations. Your input is bad and you should feel bad.', $this->ObjectAlias)); + } } $savedData = $this->Table->save($data); if ($savedData !== false) { diff --git a/src/Controller/EncryptionKeysController.php b/src/Controller/EncryptionKeysController.php index bafe8ce..e04ebc0 100644 --- a/src/Controller/EncryptionKeysController.php +++ b/src/Controller/EncryptionKeysController.php @@ -39,7 +39,15 @@ class EncryptionKeysController extends AppController public function delete($id) { - $this->CRUD->delete($id); + $orgConditions = []; + $individualConditions = []; + $dropdownData = []; + $currentUser = $this->ACL->getUser(); + $params = []; + if (empty($currentUser['role']['perm_admin'])) { + $params = $this->buildBeforeSave($params, $currentUser, $orgConditions, $individualConditions, $dropdownData); + } + $this->CRUD->delete($id, $params); $responsePayload = $this->CRUD->getResponsePayload(); if (!empty($responsePayload)) { return $responsePayload; @@ -47,49 +55,38 @@ class EncryptionKeysController extends AppController $this->set('metaGroup', 'ContactDB'); } - public function add() + private function buildBeforeSave(array $params, $currentUser, array &$orgConditions, array &$individualConditions, array &$dropdownData): array { - $orgConditions = []; - $individualConditions = []; - $currentUser = $this->ACL->getUser(); - $params = ['redirect' => $this->referer()]; - if (empty($currentUser['role']['perm_admin'])) { - $orgConditions = [ - 'id' => $currentUser['organisation_id'] + $orgConditions = [ + 'id' => $currentUser['organisation_id'] + ]; + if (empty($currentUser['role']['perm_org_admin'])) { + $individualConditions = [ + 'id' => $currentUser['individual_id'] ]; - if (empty($currentUser['role']['perm_org_admin'])) { - $individualConditions = [ - 'id' => $currentUser['individual_id'] - ]; - } - $params['beforeSave'] = function($entity) use($currentUser) { - if ($entity['owner_model'] === 'organisation') { - $entity['owner_id'] = $currentUser['organisation_id']; + } + $params['beforeSave'] = function($entity) use($currentUser) { + if ($entity['owner_model'] === 'organisation') { + $entity['owner_id'] = $currentUser['organisation_id']; + } else { + if ($currentUser['role']['perm_org_admin']) { + $this->loadModel('Alignments'); + $validIndividuals = $this->Alignments->find('list', [ + 'keyField' => 'individual_id', + 'valueField' => 'id', + 'conditions' => ['organisation_id' => $currentUser['organisation_id']] + ])->toArray(); + if (!isset($validIndividuals[$entity['owner_id']])) { + throw new MethodNotAllowedException(__('Selected individual cannot be linked by the current user.')); + } } else { - if ($currentUser['role']['perm_org_admin']) { - $this->loadModel('Alignments'); - $validIndividuals = $this->Alignments->find('list', [ - 'keyField' => 'individual_id', - 'valueField' => 'id', - 'conditions' => ['organisation_id' => $currentUser['organisation_id']] - ])->toArray(); - if (!isset($validIndividuals[$entity['owner_id']])) { - throw new MethodNotAllowedException(__('Selected individual cannot be linked by the current user.')); - } - } else { - if ($entity['owner_id'] !== $currentUser['id']) { - throw new MethodNotAllowedException(__('Selected individual cannot be linked by the current user.')); - } + if ($entity['owner_id'] !== $currentUser['id']) { + throw new MethodNotAllowedException(__('Selected individual cannot be linked by the current user.')); } } - return $entity; - }; - } - $this->CRUD->add($params); - $responsePayload = $this->CRUD->getResponsePayload(); - if (!empty($responsePayload)) { - return $responsePayload; - } + } + return $entity; + }; $this->loadModel('Organisations'); $this->loadModel('Individuals'); $dropdownData = [ @@ -102,13 +99,35 @@ class EncryptionKeysController extends AppController 'conditions' => $individualConditions ]) ]; + return $params; + } + + public function add() + { + $orgConditions = []; + $individualConditions = []; + $dropdownData = []; + $currentUser = $this->ACL->getUser(); + $params = [ + 'redirect' => $this->referer() + ]; + if (empty($currentUser['role']['perm_admin'])) { + $params = $this->buildBeforeSave($params, $currentUser, $orgConditions, $individualConditions, $dropdownData); + } + $this->CRUD->add($params); + $responsePayload = $this->CRUD->getResponsePayload(); + if (!empty($responsePayload)) { + return $responsePayload; + } $this->set(compact('dropdownData')); $this->set('metaGroup', 'ContactDB'); } public function edit($id = false) { - $conditions = []; + $orgConditions = []; + $individualConditions = []; + $dropdownData = []; $currentUser = $this->ACL->getUser(); $params = [ 'fields' => [ @@ -117,9 +136,7 @@ class EncryptionKeysController extends AppController 'redirect' => $this->referer() ]; if (empty($currentUser['role']['perm_admin'])) { - if (empty($currentUser['role']['perm_org_admin'])) { - - } + $params = $this->buildBeforeSave($params, $currentUser, $orgConditions, $individualConditions, $dropdownData); } $this->CRUD->edit($id, $params); $responsePayload = $this->CRUD->getResponsePayload(); @@ -130,4 +147,16 @@ class EncryptionKeysController extends AppController $this->set('metaGroup', 'ContactDB'); $this->render('add'); } + + public function view($id = false) + { + $this->CRUD->view($id, [ + 'contain' => ['Individuals', 'Organisations'] + ]); + $responsePayload = $this->CRUD->getResponsePayload(); + if (!empty($responsePayload)) { + return $responsePayload; + } + $this->set('metaGroup', 'ContactDB'); + } } 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']); diff --git a/src/Controller/LocalToolsController.php b/src/Controller/LocalToolsController.php index a0d31b9..12d9d62 100644 --- a/src/Controller/LocalToolsController.php +++ b/src/Controller/LocalToolsController.php @@ -340,6 +340,7 @@ class LocalToolsController extends AppController } } +/* public function connectLocal($local_tool_id) { $params = [ @@ -355,10 +356,8 @@ class LocalToolsController extends AppController $params['target_tool_id'] = $postParams['target_tool_id']; $result = $this->LocalTools->encodeLocalConnection($params); // Send message to remote inbox - debug($result); } else { $target_tools = $this->LocalTools->findConnectable($local_tool); - debug($target_tools); if (empty($target_tools)) { throw new NotFoundException(__('No tools found to connect.')); } @@ -369,4 +368,5 @@ class LocalToolsController extends AppController ]); } } +*/ } 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/Controller/UsersController.php b/src/Controller/UsersController.php index 6331d7b..a5065db 100644 --- a/src/Controller/UsersController.php +++ b/src/Controller/UsersController.php @@ -97,8 +97,16 @@ class UsersController extends AppController public function edit($id = false) { $currentUser = $this->ACL->getUser(); - if (empty($id) || (empty($currentUser['role']['perm_org_admin']) && empty($currentUser['role']['perm_site_admin']))) { + if (empty($id)) { $id = $currentUser['id']; + } else { + if ((empty($currentUser['role']['perm_org_admin']) && empty($currentUser['role']['perm_admin']))) { + if ($id !== $currentUser['id']) { + throw new MethodNotAllowedException(__('You are not authorised to edit that user.')); + } else { + $id = $currentUser['id']; + } + } } $params = [ @@ -111,12 +119,15 @@ class UsersController extends AppController 'password' ], 'fields' => [ - 'id', 'individual_id', 'username', 'disabled', 'password', 'confirm_password' + 'password', 'confirm_password' ] ]; if (!empty($this->ACL->getUser()['role']['perm_admin'])) { + $params['fields'][] = 'individual_id'; + $params['fields'][] = 'username'; $params['fields'][] = 'role_id'; $params['fields'][] = 'organisation_id'; + $params['fields'][] = 'disabled'; } $this->CRUD->edit($id, $params); $responsePayload = $this->CRUD->getResponsePayload(); diff --git a/src/Lib/default/local_tool_connectors/CommonConnectorTools.php b/src/Lib/default/local_tool_connectors/CommonConnectorTools.php index 179a238..213db17 100644 --- a/src/Lib/default/local_tool_connectors/CommonConnectorTools.php +++ b/src/Lib/default/local_tool_connectors/CommonConnectorTools.php @@ -2,6 +2,8 @@ namespace CommonConnectorTools; use Cake\ORM\Locator\LocatorAwareTrait; +use Cake\Log\Log; +use Cake\Log\Engine\FileLog; class CommonConnectorTools { @@ -20,6 +22,35 @@ class CommonConnectorTools const STATE_CANCELLED = 'Request cancelled'; const STATE_DECLINED = 'Request declined by remote'; + public function __construct() + { + Log::setConfig("LocalToolDebug", [ + 'className' => FileLog::class, + 'path' => LOGS, + 'file' => "{$this->connectorName}-debug", + 'scopes' => [$this->connectorName], + 'levels' => ['notice', 'info', 'debug'], + ]); + Log::setConfig("LocalToolError", [ + 'className' => FileLog::class, + 'path' => LOGS, + 'file' => "{$this->connectorName}-error", + 'scopes' => [$this->connectorName], + 'levels' => ['warning', 'error', 'critical', 'alert', 'emergency'], + ]); + + } + + protected function logDebug($message) + { + Log::debug($message, [$this->connectorName]); + } + + protected function logError($message, $scope=[]) + { + Log::error($message, [$this->connectorName]); + } + public function addExposedFunction(string $functionName): void { $this->exposedFunctions[] = $functionName; diff --git a/src/Lib/default/local_tool_connectors/MispConnector.php b/src/Lib/default/local_tool_connectors/MispConnector.php index 473573a..4b6c653 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) { @@ -183,6 +188,7 @@ class MispConnector extends CommonConnectorTools $settings = json_decode($connection->settings, true); $http = $this->genHTTPClient($connection, $options); $url = sprintf('%s%s', $settings['url'], $relativeURL); + $this->logDebug(sprintf('%s %s %s', __('Posting data') . PHP_EOL, "POST {$url}" . PHP_EOL, json_encode($data))); return $http->post($url, $data, $options); } @@ -234,14 +240,18 @@ class MispConnector extends CommonConnectorTools if (!empty($params['softError'])) { return $response; } - throw new NotFoundException(__('Could not retrieve the requested resource.')); + $errorMsg = __('Could not post to the requested resource for `{0}`. Remote returned:', $url) . PHP_EOL . $response->getStringBody(); + $this->logError($errorMsg); + throw new NotFoundException($errorMsg); } } private function postData(string $url, array $params): Response { if (empty($params['connection'])) { - throw new NotFoundException(__('No connection object received.')); + $errorMsg = __('No connection object received.'); + $this->logError($errorMsg); + throw new NotFoundException($errorMsg); } $url = $this->urlAppendParams($url, $params); if (!is_string($params['body'])) { @@ -251,7 +261,9 @@ class MispConnector extends CommonConnectorTools if ($response->isOk()) { return $response; } else { - throw new NotFoundException(__('Could not post to the requested resource. Remote returned:') . PHP_EOL . $response->getStringBody()); + $errorMsg = __('Could not post to the requested resource for `{0}`. Remote returned:', $url) . PHP_EOL . $response->getStringBody(); + $this->logError($errorMsg); + throw new NotFoundException($errorMsg); } } 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/Entity/Outbox.php b/src/Model/Entity/Outbox.php new file mode 100644 index 0000000..51304e6 --- /dev/null +++ b/src/Model/Entity/Outbox.php @@ -0,0 +1,11 @@ + $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); @@ -288,6 +289,7 @@ class LocalToolsTable extends AppTable return $jsonReply; } +/* public function findConnectable($local_tool): array { $connectors = $this->getInterconnectors($local_tool['connector']); @@ -297,8 +299,8 @@ class LocalToolsTable extends AppTable $validTargets[$connector['connects'][1]] = 1; } } - } +*/ public function fetchConnection($id): object { 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 { 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], diff --git a/templates/EncryptionKeys/view.php b/templates/EncryptionKeys/view.php new file mode 100644 index 0000000..e92da92 --- /dev/null +++ b/templates/EncryptionKeys/view.php @@ -0,0 +1,32 @@ +element( + '/genericElements/SingleViews/single_view', + [ + 'data' => $entity, + 'fields' => [ + [ + 'key' => __('ID'), + 'path' => 'id' + ], + [ + 'key' => __('Type'), + 'path' => 'type' + ], + [ + 'key' => __('Owner'), + 'path' => 'owner_id', + 'owner_model_path' => 'owner_model', + 'type' => 'owner' + ], + [ + 'key' => __('Revoked'), + 'path' => 'revoked' + ], + + [ + 'key' => __('Key'), + 'path' => 'encryption_key' + ] + ] + ] +); 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, 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', diff --git a/templates/LocalTools/connector_index.php b/templates/LocalTools/connector_index.php index e6ef205..f199d30 100644 --- a/templates/LocalTools/connector_index.php +++ b/templates/LocalTools/connector_index.php @@ -89,12 +89,14 @@ echo $this->element('genericElements/IndexTable/index_table', [ 'url_params_data_paths' => ['id'], 'icon' => 'eye' ], + /* [ 'open_modal' => '/localTools/connectLocal/[onclick_params_data_path]', 'modal_params_data_path' => 'id', 'reload_url' => sprintf('/localTools/connectorIndex/%s', h($connectorName)), 'icon' => 'plug' ], + */ [ 'open_modal' => '/localTools/edit/[onclick_params_data_path]', 'modal_params_data_path' => 'id', diff --git a/templates/Organisations/add.php b/templates/Organisations/add.php index 987809e..6278ff8 100644 --- a/templates/Organisations/add.php +++ b/templates/Organisations/add.php @@ -7,17 +7,13 @@ array( 'field' => 'name' ), - array( - 'field' => 'description', - 'type' => 'textarea' - ), array( 'field' => 'uuid', 'label' => 'UUID', 'type' => 'uuid' ), array( - 'field' => 'URL' + 'field' => 'url' ), array( 'field' => 'nationality' diff --git a/templates/element/genericElements/SingleViews/Fields/ownerField.php b/templates/element/genericElements/SingleViews/Fields/ownerField.php new file mode 100644 index 0000000..4f213a7 --- /dev/null +++ b/templates/element/genericElements/SingleViews/Fields/ownerField.php @@ -0,0 +1,6 @@ +element('/genericElements/IndexTable/Fields/owner', [ + 'field' => $field, + 'row' => $data +]); +?> 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) === ""); + } +});