From 26cf3e84c32a0eccb6786e3e9cfa50ec3e75e441 Mon Sep 17 00:00:00 2001 From: Andras Iklody Date: Tue, 27 Apr 2021 23:54:27 +0200 Subject: [PATCH 01/14] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f08efb8..ea74248 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ The installation is documented at the following location [INSTALL/INSTALL.md](IN Hardware requirements: -A webserver with 4GB of memory and a single CPU core should be plenty for the current scope of Cerebrate. This might increase over the time with additional features being added, but the goal is to keep Cerebrate as lean as possible. +A webserver with 4GB of memory and a single CPU core should be plenty for the current scope of Cerebrate. This might increase over the time with additional features being added, but the goal is to keep Cerebrate as lean as possible. Expect to have at least 40GB of disk space, depending on your log rotation strategy you might want to go higher. # License From 5e2107cdb8cb13941514d7c2070477f6343d6701 Mon Sep 17 00:00:00 2001 From: Andras Iklody Date: Tue, 27 Apr 2021 23:55:32 +0200 Subject: [PATCH 02/14] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index ea74248..a00ccc6 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,8 @@ Hardware requirements: A webserver with 4GB of memory and a single CPU core should be plenty for the current scope of Cerebrate. This might increase over the time with additional features being added, but the goal is to keep Cerebrate as lean as possible. Expect to have at least 40GB of disk space, depending on your log rotation strategy you might want to go higher. +For installation via docker, refer to the [cerebrate-docker](https://github.com/cerebrate-project/cerebrate-docker) repo. + # License ~~~~ From 2d4727770c19bcf718e58e8d56855f1b45ad431f Mon Sep 17 00:00:00 2001 From: iglocska Date: Fri, 30 Apr 2021 23:59:53 +0200 Subject: [PATCH 03/14] wip: initial connectors --- INSTALL/mysql.sql | 15 ++ src/Application.php | 2 +- src/Controller/AppController.php | 7 +- src/Controller/Component/CRUDComponent.php | 16 +- src/Controller/LocalToolsController.php | 122 +++++++++++++ src/Controller/Open/IndividualsController.php | 35 ++++ src/Controller/UsersController.php | 2 +- .../CommonConnectorTools.php | 32 ++++ .../local_tool_connectors/MispConnector.php | 127 +++++++++++++ src/Model/Behavior/SyncTool.php | 172 ++++++++++++++++++ src/Model/Entity/LocalTool.php | 11 ++ src/Model/Table/LocalToolsTable.php | 103 +++++++++++ src/View/AppView.php | 1 + src/View/Helper/PrettyPrintHelper.php | 29 +++ templates/Common/index.php | 4 + templates/LocalTools/add.php | 26 +++ templates/LocalTools/connector_index.php | 68 +++++++ templates/LocalTools/index.php | 54 ++++++ templates/LocalTools/view.php | 0 templates/LocalTools/view_connector.php | 33 ++++ .../Configuration/Fields.php/scaffold.php | 4 + .../Configuration/scaffold.php | 33 ++++ .../IndexTable/Fields/array.php | 7 + .../IndexTable/Fields/health.php | 28 +++ .../genericElements/header_scaffold.php | 2 +- 25 files changed, 923 insertions(+), 10 deletions(-) create mode 100644 src/Controller/LocalToolsController.php create mode 100644 src/Controller/Open/IndividualsController.php create mode 100644 src/Lib/default/local_tool_connectors/CommonConnectorTools.php create mode 100644 src/Lib/default/local_tool_connectors/MispConnector.php create mode 100644 src/Model/Behavior/SyncTool.php create mode 100644 src/Model/Entity/LocalTool.php create mode 100644 src/Model/Table/LocalToolsTable.php create mode 100644 src/View/Helper/PrettyPrintHelper.php create mode 100644 templates/Common/index.php create mode 100644 templates/LocalTools/add.php create mode 100644 templates/LocalTools/connector_index.php create mode 100644 templates/LocalTools/index.php create mode 100644 templates/LocalTools/view.php create mode 100644 templates/LocalTools/view_connector.php create mode 100644 templates/element/genericElements/Configuration/Fields.php/scaffold.php create mode 100644 templates/element/genericElements/Configuration/scaffold.php create mode 100644 templates/element/genericElements/IndexTable/Fields/array.php create mode 100644 templates/element/genericElements/IndexTable/Fields/health.php diff --git a/INSTALL/mysql.sql b/INSTALL/mysql.sql index 9f888dc..c2f8f64 100644 --- a/INSTALL/mysql.sql +++ b/INSTALL/mysql.sql @@ -186,6 +186,21 @@ CREATE TABLE `individuals` ( ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; /*!40101 SET character_set_client = @saved_cs_client */; +-- +-- Table structure for table `local_tools` +-- + +DROP TABLE IF EXISTS `local_tools`; +CREATE TABLE `local_tools` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, + `connector` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, + `settings` text COLLATE utf8mb4_unicode_ci, + PRIMARY KEY (`id`), + KEY `name` (`name`), + KEY `connector` (`connector`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + -- -- Table structure for table `organisation_encryption_keys` -- diff --git a/src/Application.php b/src/Application.php index 90e3865..8eb1704 100644 --- a/src/Application.php +++ b/src/Application.php @@ -135,7 +135,7 @@ class Application extends BaseApplication implements AuthenticationServiceProvid $service->loadAuthenticator('Authentication.Session'); $service->loadAuthenticator('Authentication.Form', [ 'fields' => $fields, - 'loginUrl' => '/users/login' + 'loginUrl' => \Cake\Routing\Router::url('/users/login') ]); // Load identifiers diff --git a/src/Controller/AppController.php b/src/Controller/AppController.php index e713f50..87f968d 100644 --- a/src/Controller/AppController.php +++ b/src/Controller/AppController.php @@ -99,7 +99,7 @@ class AppController extends Controller if (!empty($user['disabled'])) { $this->Authentication->logout(); $this->Flash->error(__('The user account is disabled.')); - return $this->redirect(['controller' => 'Users', 'action' => 'login']); + return $this->redirect(\Cake\Routing\Router::url('/users/login')); } unset($user['password']); $this->ACL->setUser($user); @@ -112,11 +112,6 @@ class AppController extends Controller $this->set('ajax', $this->request->is('ajax')); $this->request->getParam('prefix'); $this->set('darkMode', !empty(Configure::read('Cerebrate.dark'))); - if (!empty(Configure::read('baseurl'))) { - Configure::write('App.fullBaseUrl', Configure::read('baseurl')); - } else if (!empty(env('CEREBRATE_BASEURL'))) { - Configure::write('App.fullBaseUrl', env('CEREBRATE_BASEURL')); - } $this->set('baseurl', Configure::read('App.fullBaseUrl')); } diff --git a/src/Controller/Component/CRUDComponent.php b/src/Controller/Component/CRUDComponent.php index e64470d..ee373a1 100644 --- a/src/Controller/Component/CRUDComponent.php +++ b/src/Controller/Component/CRUDComponent.php @@ -45,10 +45,24 @@ class CRUDComponent extends Component } if ($this->Controller->ParamHandler->isRest()) { $data = $query->all(); + if (isset($options['afterFind'])) { + if (is_callable($options['afterFind'])) { + $data = $options['afterFind']($data); + } else { + $data = $this->Table->{$options['afterFind']}($data); + } + } $this->Controller->restResponsePayload = $this->Controller->RestResponse->viewData($data, 'json'); } else { $this->Controller->loadComponent('Paginator'); $data = $this->Controller->Paginator->paginate($query); + if (isset($options['afterFind'])) { + if (is_callable($options['afterFind'])) { + $data = $options['afterFind']($data); + } else { + $data = $this->Table->{$options['afterFind']}($data); + } + } if (!empty($options['contextFilters'])) { $this->setFilteringContext($options['contextFilters'], $params); } @@ -63,7 +77,7 @@ class CRUDComponent extends Component $this->Controller->viewBuilder()->setLayout('ajax'); $this->Controller->render('/genericTemplates/filters'); } - + /** * getResponsePayload Returns the adaquate response payload based on the request context * diff --git a/src/Controller/LocalToolsController.php b/src/Controller/LocalToolsController.php new file mode 100644 index 0000000..5546051 --- /dev/null +++ b/src/Controller/LocalToolsController.php @@ -0,0 +1,122 @@ +LocalTools->extractMeta($this->LocalTools->getConnectors(), true); + if ($this->ParamHandler->isRest()) { + return $this->RestResponse->viewData($data, 'json'); + } + $data = $this->CustomPagination->paginate($data); + $this->set('data', $data); + if ($this->request->is('ajax')) { + $this->viewBuilder()->disableAutoLayout(); + } + $this->set('metaGroup', 'LocalTools'); + } + + public function connectorIndex() + { + $this->CRUD->index([ + 'filters' => ['name', 'connector'], + 'quickFilters' => ['name', 'connector'], + 'afterFind' => function($data) { + foreach ($data as $connector) { + $connector['health'] = [$this->LocalTools->healthCheckIndividual($connector)]; + } + return $data; + } + ]); + if ($this->ParamHandler->isRest()) { + return $this->restResponsePayload; + } + $this->set('metaGroup', 'LocalTools'); + } + + public function action() + { + $params = []; + $results = $this->LocalTools->runAction(); + $this->render('add'); + } + + public function add() + { + $this->CRUD->add(); + if ($this->ParamHandler->isRest()) { + return $this->restResponsePayload; + } + $connectors = $this->LocalTools->extractMeta($this->LocalTools->getConnectors()); + $dropdownData = ['connectors' => []]; + foreach ($connectors as $connector) { + $dropdownData['connectors'][$connector['connector']] = $connector['name']; + } + $this->set(compact('dropdownData')); + $this->set('metaGroup', 'LocalTools'); + } + + public function viewConnector($connector_name) + { + $connectors = $this->LocalTools->extractMeta($this->LocalTools->getConnectors()); + $connector = false; + foreach ($connectors as $c) { + if ($connector === false || version_compare($c['version'], $connectors['version']) > 0) { + $connector = $c; + } + } + if ($this->ParamHandler->isRest()) { + $this->restResponsePayload = $this->Controller->RestResponse->viewData($connector, 'json'); + } + $this->set('entity', $connector); + $this->set('metaGroup', 'LocalTools'); + } + + public function edit($id) + { + $this->CRUD->edit($id); + if ($this->ParamHandler->isRest()) { + return $this->restResponsePayload; + } + $connectors = $this->LocalTools->extractMeta($this->LocalTools->getConnectors()); + $dropdownData = ['connectors' => []]; + foreach ($connectors as $connector) { + $dropdownData['connectors'][$connector['connector']] = $connector['name']; + } + $this->set(compact('dropdownData')); + $this->set('metaGroup', 'LocalTools'); + $this->render('add'); + } + + public function delete($id) + { + $this->CRUD->delete($id); + if ($this->ParamHandler->isRest()) { + return $this->restResponsePayload; + } + $this->set('metaGroup', 'LocalTools'); + } + + public function view($id) + { + $this->CRUD->view($id); + $responsePayload = $this->CRUD->getResponsePayload(); + if (!empty($responsePayload)) { + return $responsePayload; + } + $this->set('metaGroup', 'LocalTools'); + } + + public function test() + { + $connectors = $this->LocalTools->getConnectors(); + $connectors['MispConnector']->test(); + } +} diff --git a/src/Controller/Open/IndividualsController.php b/src/Controller/Open/IndividualsController.php new file mode 100644 index 0000000..097489b --- /dev/null +++ b/src/Controller/Open/IndividualsController.php @@ -0,0 +1,35 @@ +Authentication->allowUnauthenticated(['index']); + } + + public function index() + { + $this->CRUD->index([ + 'filters' => ['uuid', 'email', 'first_name', 'last_name', 'position', 'Organisations.id'], + 'quickFilters' => ['uuid', 'email', 'first_name', 'last_name', 'position'], + 'contain' => ['Alignments' => 'Organisations'] + ]); + if ($this->ParamHandler->isRest()) { + return $this->restResponsePayload; + } + $this->set('alignmentScope', 'organisations'); + $this->set('metaGroup', 'Public'); + } +} diff --git a/src/Controller/UsersController.php b/src/Controller/UsersController.php index db11453..dadca75 100644 --- a/src/Controller/UsersController.php +++ b/src/Controller/UsersController.php @@ -131,7 +131,7 @@ class UsersController extends AppController if ($result->isValid()) { $this->Authentication->logout(); $this->Flash->success(__('Goodbye.')); - return $this->redirect(['controller' => 'Users', 'action' => 'login']); + return $this->redirect(\Cake\Routing\Router::url('/users/login')); } } } diff --git a/src/Lib/default/local_tool_connectors/CommonConnectorTools.php b/src/Lib/default/local_tool_connectors/CommonConnectorTools.php new file mode 100644 index 0000000..1a04acb --- /dev/null +++ b/src/Lib/default/local_tool_connectors/CommonConnectorTools.php @@ -0,0 +1,32 @@ +exposedFunctions[] = $functionName; + } + + public function runAction($action, $params) { + if (!in_array($action, $exposedFunctions)) { + throw new MethodNotAllowedException(__('Invalid connector function called.')); + } + return $this->{$action}($params); + } + + public function health(Object $connection): array + { + return 0; + } +} + +?> diff --git a/src/Lib/default/local_tool_connectors/MispConnector.php b/src/Lib/default/local_tool_connectors/MispConnector.php new file mode 100644 index 0000000..838d620 --- /dev/null +++ b/src/Lib/default/local_tool_connectors/MispConnector.php @@ -0,0 +1,127 @@ +exposedFunctions[] = $functionName; + } + + public function health(Object $connection): array + { + $settings = json_decode($connection->settings, true); + $http = new Client(); + $response = $http->post($settings['url'] . '/users/view/me.json', '{}', [ + 'headers' => [ + 'AUTHORIZATION' => $settings['authkey'], + 'Accept' => 'Application/json', + 'Content-type' => 'Application/json' + ] + ]); + $responseCode = $response->getStatusCode(); + if ($response->isOk()) { + $status = 1; + $message = __('OK'); + } else if ($responseCode == 403){ + $status = 3; + $message = __('Unauthorized'); + } else { + $status = 0; + $message = __('Something went wrong.'); + } + + return [ + 'status' => $status, + 'message' => $message + ]; + } + + private function viewOrgAction(array $params): array + { + if (empty($params['connection'])) { + throw new InvalidArgumentException(__('No connection object received.')); + } + $settings = json_decode($params['connection']->settings, true); + $http = new Client(); + $url = '/users/organisations/index/scope:all'; + if (!empty($params['page'])) { + $url .= '/page:' . $params['page']; + } + if (!empty($params['limit'])) { + $url .= '/limit:' . $params['limit']; + } + if (!empty($params['quickFilter'])) { + $url .= '/searchall:' . $params['quickFilter']; + } + $response = $http->post($settings['url'] . '/users/organisations/index/scope:all', '{}', [ + 'headers' => [ + 'AUTHORIZATION' => $settings['authkey'], + 'Accept' => 'Application/json', + 'Content-type' => 'Application/json' + ] + ]); + $responseCode = $response->getStatusCode(); + if ($response->isOk()) { + return [ + 'type' => 'index', + 'data' => [ + 'data' => json_decode($response->getBody(), true), + 'top_bar' => [ + 'children' => [ + [ + 'type' => 'search', + 'button' => __('Filter'), + 'placeholder' => __('Enter value to search'), + 'data' => '', + 'searchKey' => 'value' + ] + ] + ], + 'fields' => [ + [ + 'name' => '#', + 'sort' => 'id', + 'data_path' => 'Organisation.id', + ], + [ + 'name' => __('Name'), + 'sort' => 'name', + 'data_path' => 'Organisation.name', + ], + [ + 'name' => __('UUID'), + 'sort' => 'uuid', + 'data_path' => 'Organisation.uuid', + ] + ], + 'title' => false, + 'description' => false, + 'pull' => 'right', + 'actions' => [ + [ + 'url' => '/localTools/action/fetchOrg', + 'url_params_data_paths' => ['id'], + 'icon' => 'download' + ] + ] + ] + ]; + } else { + return __('Could not fetch the organisations, error code: {0}', $response->getStatusCode()); + } + } +} + + ?> diff --git a/src/Model/Behavior/SyncTool.php b/src/Model/Behavior/SyncTool.php new file mode 100644 index 0000000..ad3f756 --- /dev/null +++ b/src/Model/Behavior/SyncTool.php @@ -0,0 +1,172 @@ +createHttpSocket($params); + } + + public function setupHttpSocketFeed($feed = null) + { + return $this->setupHttpSocket(); + } + + /** + * @param array $params + * @return HttpSocket + * @throws Exception + */ + public function createHttpSocket($params = array()) + { + App::uses('HttpSocket', 'Network/Http'); + $HttpSocket = new HttpSocket($params); + $proxy = Configure::read('Proxy'); + if (empty($params['skip_proxy']) && isset($proxy['host']) && !empty($proxy['host'])) { + $HttpSocket->configProxy($proxy['host'], $proxy['port'], $proxy['method'], $proxy['user'], $proxy['password']); + } + return $HttpSocket; + } + + /** + * @param array $server + * @return array|void + * @throws Exception + */ + public static function getServerClientCertificateInfo(array $server) + { + if (!$server['client_cert_file']) { + return; + } + + $clientCertificate = new File(APP . "files" . DS . "certs" . DS . $server['id'] . '_client.pem'); + if (!$clientCertificate->exists()) { + throw new Exception("Certificate file '{$clientCertificate->pwd()}' doesn't exists."); + } + + $certificateContent = $clientCertificate->read(); + if ($certificateContent === false) { + throw new Exception("Could not read '{$clientCertificate->pwd()}' file with client certificate."); + } + + return self::getClientCertificateInfo($certificateContent); + } + + /** + * @param string $certificateContent PEM encoded certificate and private key. + * @return array + * @throws Exception + */ + private static function getClientCertificateInfo($certificateContent) + { + $certificate = openssl_x509_read($certificateContent); + if (!$certificate) { + throw new Exception("Could't parse certificate: " . openssl_error_string()); + } + $privateKey = openssl_pkey_get_private($certificateContent); + if (!$privateKey) { + throw new Exception("Could't get private key from certificate: " . openssl_error_string()); + } + $verify = openssl_x509_check_private_key($certificate, $privateKey); + if (!$verify) { + throw new Exception('Public and private key do not match.'); + } + return self::parseCertificate($certificate); + } + + /** + * @param mixed $certificate + * @return array + * @throws Exception + */ + private static function parseCertificate($certificate) + { + $parsed = openssl_x509_parse($certificate); + if (!$parsed) { + throw new Exception("Could't get parse X.509 certificate: " . openssl_error_string()); + } + $currentTime = new DateTime(); + $output = [ + 'serial_number' => $parsed['serialNumberHex'], + 'signature_type' => $parsed['signatureTypeSN'], + 'valid_from' => isset($parsed['validFrom_time_t']) ? new DateTime("@{$parsed['validFrom_time_t']}") : null, + 'valid_to' => isset($parsed['validTo_time_t']) ? new DateTime("@{$parsed['validTo_time_t']}") : null, + 'public_key_size' => null, + 'public_key_type' => null, + 'public_key_size_ok' => null, + ]; + + $output['valid_from_ok'] = $output['valid_from'] ? ($output['valid_from'] <= $currentTime) : null; + $output['valid_to_ok'] = $output['valid_to'] ? ($output['valid_to'] >= $currentTime) : null; + + $subject = []; + foreach ($parsed['subject'] as $type => $value) { + $subject[] = "$type=$value"; + } + $output['subject'] = implode(', ', $subject); + + $issuer = []; + foreach ($parsed['issuer'] as $type => $value) { + $issuer[] = "$type=$value"; + } + $output['issuer'] = implode(', ', $issuer); + + $publicKey = openssl_pkey_get_public($certificate); + if ($publicKey) { + $publicKeyDetails = openssl_pkey_get_details($publicKey); + if ($publicKeyDetails) { + $output['public_key_size'] = $publicKeyDetails['bits']; + switch ($publicKeyDetails['type']) { + case OPENSSL_KEYTYPE_RSA: + $output['public_key_type'] = 'RSA'; + $output['public_key_size_ok'] = $output['public_key_size'] >= 2048; + break; + case OPENSSL_KEYTYPE_DSA: + $output['public_key_type'] = 'DSA'; + $output['public_key_size_ok'] = $output['public_key_size'] >= 2048; + break; + case OPENSSL_KEYTYPE_DH: + $output['public_key_type'] = 'DH'; + break; + case OPENSSL_KEYTYPE_EC: + $output['public_key_type'] = "EC ({$publicKeyDetails['ec']['curve_name']})"; + $output['public_key_size_ok'] = $output['public_key_size'] >= 224; + break; + } + } + } + + return $output; + } +} diff --git a/src/Model/Entity/LocalTool.php b/src/Model/Entity/LocalTool.php new file mode 100644 index 0000000..f996d77 --- /dev/null +++ b/src/Model/Entity/LocalTool.php @@ -0,0 +1,11 @@ + 'UNKNOWN', + 1 => 'OK', + 2 => 'ISSUES', + 3 => 'ERROR', + ]; + + private $connectors = null; + + public function initialize(array $config): void + { + parent::initialize($config); + } + + public function validationDefault(Validator $validator): Validator + { + return $validator; + } + + public function getConnectors(string $name = null): array + { + $connectors = []; + $dirs = [ + ROOT . '/src/Lib/default/local_tool_connectors', + ROOT . '/src/Lib/custom/local_tool_connectors' + ]; + foreach ($dirs as $dir) { + $dir = new Folder($dir); + $files = $dir->find('.*Connector\.php'); + foreach ($files as $file) { + require_once($dir->pwd() . '/'. $file); + $className = substr($file, 0, -4); + $classNamespace = '\\' . $className . '\\' . $className; + if (empty($name) || $name === $className) { + $connectors[$className] = new $classNamespace; + } + } + } + return $connectors; + + } + + public function extractMeta(array $connector_classes, bool $includeConnections = false): array + { + $connectors = []; + foreach ($connector_classes as $connector_type => $connector_class) { + $connector = [ + 'name' => $connector_class->name, + 'connector' => $connector_type, + 'connector_version' => $connector_class->version, + 'connector_description' => $connector_class->description + ]; + if ($includeConnections) { + $connector['connections'] = $this->healthCheck($connector_type, $connector_class); + } + $connectors[] = $connector; + } + return $connectors; + } + + public function healthCheck(string $connector_type, Object $connector_class): array + { + $query = $this->find(); + $query->where([ + 'connector' => $connector_type + ]); + $connections = $query->all()->toList(); + foreach ($connections as &$connection) { + $connection = $this->healthCheckIndividual($connector_class, $connection); + } + return $connections; + } + + public function healthCheckIndividual(Object $connector): array + { + $connector_class = $this->getConnectors($connector['connector']); + if (empty($connector_class[$connector['connector']])) { + return []; + } + $connector_class = $connector_class[$connector['connector']]; + $health = $connector_class->health($connector); + return $connection = [ + 'name' => $connector->name, + 'health' => $health['status'], + 'message' => $health['message'], + 'url' => '/localTools/viewConnection/' . $connector['id'] + ]; + } +} diff --git a/src/View/AppView.php b/src/View/AppView.php index e4b82b6..87b775b 100644 --- a/src/View/AppView.php +++ b/src/View/AppView.php @@ -40,6 +40,7 @@ class AppView extends View { parent::initialize(); $this->loadHelper('Hash'); + $this->loadHelper('PrettyPrint'); $this->loadHelper('FormFieldMassage'); $this->loadHelper('Paginator', ['templates' => 'cerebrate-pagination-templates']); } diff --git a/src/View/Helper/PrettyPrintHelper.php b/src/View/Helper/PrettyPrintHelper.php new file mode 100644 index 0000000..c288cb3 --- /dev/null +++ b/src/View/Helper/PrettyPrintHelper.php @@ -0,0 +1,29 @@ + $value) { + if (is_array($value)) { + $value = $this->ppArray($value, $depth+1); + } else { + $value = h($value); + } + $text .= sprintf( + '
%s: %s
', + h($key), + $value + ); + } + return $text; + } +} + + +?> diff --git a/templates/Common/index.php b/templates/Common/index.php new file mode 100644 index 0000000..969eb89 --- /dev/null +++ b/templates/Common/index.php @@ -0,0 +1,4 @@ +element('genericElements/IndexTable/index_table', $index_table); + echo ''; +?> diff --git a/templates/LocalTools/add.php b/templates/LocalTools/add.php new file mode 100644 index 0000000..10cefb6 --- /dev/null +++ b/templates/LocalTools/add.php @@ -0,0 +1,26 @@ +element('genericElements/Form/genericForm', [ + 'data' => [ + 'description' => __('Add connections to local tools via any of the available connectors below.'), + 'model' => 'LocalTools', + 'fields' => [ + [ + 'field' => 'name' + ], + [ + 'field' => 'connector', + 'options' => $dropdownData['connectors'], + 'type' => 'dropdown' + ], + [ + 'field' => 'settings', + 'type' => 'textarea' + ] + ], + 'submit' => [ + 'action' => $this->request->getParam('action') + ] + ] + ]); +?> + diff --git a/templates/LocalTools/connector_index.php b/templates/LocalTools/connector_index.php new file mode 100644 index 0000000..06c8560 --- /dev/null +++ b/templates/LocalTools/connector_index.php @@ -0,0 +1,68 @@ +element('genericElements/IndexTable/index_table', [ + 'data' => [ + 'data' => $data, + 'top_bar' => [ + 'children' => [ + [ + 'type' => 'search', + 'button' => __('Filter'), + 'placeholder' => __('Enter value to search'), + 'data' => '', + 'searchKey' => 'value' + ] + ] + ], + 'fields' => [ + [ + 'name' => '#', + 'sort' => 'id', + 'data_path' => 'id', + ], + [ + 'name' => __('Name'), + 'sort' => 'name', + 'data_path' => 'name', + ], + [ + 'name' => __('Connector'), + 'sort' => 'connector', + 'data_path' => 'connector', + ], + [ + 'name' => 'settings', + 'data_path' => 'settings', + 'isJson' => 1, + 'element' => 'array' + ], + [ + 'name' => 'health', + 'data_path' => 'health', + 'element' => 'health', + 'class' => 'text-nowrap' + ] + ], + 'title' => false, + 'description' => false, + 'pull' => 'right', + 'actions' => [ + [ + 'url' => '/localTools/view', + 'url_params_data_paths' => ['id'], + 'icon' => 'eye' + ], + [ + 'open_modal' => '/localTools/edit/[onclick_params_data_path]', + 'modal_params_data_path' => 'id', + 'icon' => 'edit' + ], + [ + 'open_modal' => '/localTools/delete/[onclick_params_data_path]', + 'modal_params_data_path' => 'id', + 'icon' => 'trash' + ], + ] + ] +]); +echo ''; +?> diff --git a/templates/LocalTools/index.php b/templates/LocalTools/index.php new file mode 100644 index 0000000..91903a2 --- /dev/null +++ b/templates/LocalTools/index.php @@ -0,0 +1,54 @@ +element('genericElements/IndexTable/index_table', [ + 'data' => [ + 'data' => $data, + 'top_bar' => [ + 'children' => [ + [ + 'type' => 'search', + 'button' => __('Filter'), + 'placeholder' => __('Enter value to search'), + 'data' => '', + 'searchKey' => 'value' + ] + ] + ], + 'fields' => [ + [ + 'name' => __('Name'), + 'sort' => 'name', + 'data_path' => 'name', + ], + [ + 'name' => 'Connector', + 'data_path' => 'connector' + ], + [ + 'name' => 'Version', + 'data_path' => 'connector_version' + ], + [ + 'name' => 'Description', + 'data_path' => 'connector_description' + ], + [ + 'name' => 'Connections', + 'data_path' => 'connections', + 'element' => 'health', + 'class' => 'text-nowrap' + ] + ], + 'title' => __('Local tool connector index'), + 'description' => __('Cerebrate can connect to local tools via individual connectors, built to expose the various functionalities of the given tool via Cerebrate. Simply view the connectors\' details and the accompanying instance list to manage the connections using the given connector. '), + 'pull' => 'right', + 'actions' => [ + [ + 'url' => '/localTools/viewConnector', + 'url_params_data_paths' => ['connector'], + 'icon' => 'eye' + ] + ] + ] +]); +echo ''; +?> diff --git a/templates/LocalTools/view.php b/templates/LocalTools/view.php new file mode 100644 index 0000000..e69de29 diff --git a/templates/LocalTools/view_connector.php b/templates/LocalTools/view_connector.php new file mode 100644 index 0000000..d6c4665 --- /dev/null +++ b/templates/LocalTools/view_connector.php @@ -0,0 +1,33 @@ +element( + '/genericElements/SingleViews/single_view', + [ + 'title' => __('{0} connector view', $entity['name']), + 'data' => $entity, + 'fields' => [ + [ + 'key' => __('Name'), + 'path' => 'name' + ], + [ + 'key' => __('Connector name'), + 'path' => 'connector' + ], + [ + 'key' => __('version'), + 'path' => 'connector_version' + ], + [ + 'key' => __('Description'), + 'path' => 'connector_description' + ] + ], + 'children' => [ + [ + 'url' => '/localTools/connectorIndex/', + 'url_params' => ['connector'], + 'title' => __('Connections') + ] + ] + ] +); diff --git a/templates/element/genericElements/Configuration/Fields.php/scaffold.php b/templates/element/genericElements/Configuration/Fields.php/scaffold.php new file mode 100644 index 0000000..6a9187d --- /dev/null +++ b/templates/element/genericElements/Configuration/Fields.php/scaffold.php @@ -0,0 +1,4 @@ +element('genericElements/Configuration/Fields/' . $type . 'Field.php', ['data' => $field]); diff --git a/templates/element/genericElements/Configuration/scaffold.php b/templates/element/genericElements/Configuration/scaffold.php new file mode 100644 index 0000000..ea424fc --- /dev/null +++ b/templates/element/genericElements/Configuration/scaffold.php @@ -0,0 +1,33 @@ +'; +} +$fields = ''; +if (!empty($data['fields'])) { + foreach ($data['fields'] as $field) { + $fields .= $this->element('genericElements/Configuration/Fields/scaffold.php', ['data' => $field]); + } +} +echo sprintf( + '

%s

%s
<%s/div>
', + empty($ajax) ? 'col-8' : '', + h($data['title']), + $diagnostics, + $fields +); +?> diff --git a/templates/element/genericElements/IndexTable/Fields/array.php b/templates/element/genericElements/IndexTable/Fields/array.php new file mode 100644 index 0000000..b1cc9bf --- /dev/null +++ b/templates/element/genericElements/IndexTable/Fields/array.php @@ -0,0 +1,7 @@ +Hash->extract($row, $field['data_path']);; + if (!empty($field['isJson'])) { + $data = json_decode($data[0], true); + } + echo $this->PrettyPrint->ppArray($data); +?> diff --git a/templates/element/genericElements/IndexTable/Fields/health.php b/templates/element/genericElements/IndexTable/Fields/health.php new file mode 100644 index 0000000..ad14ac4 --- /dev/null +++ b/templates/element/genericElements/IndexTable/Fields/health.php @@ -0,0 +1,28 @@ +Hash->extract($row, $field['data_path']); + $lines = ''; + $status_colours = [ + 0 => 'secondary', + 1 => 'success', + 2 => 'warning', + 3 => 'danger' + ]; + foreach ($data as $healthElement) { + $name = h($healthElement['name']); + if (!empty($healthElement['url'])) { + $name = sprintf( + '%s', + $baseurl, + $healthElement['url'], + $name + ); + } + $lines .= sprintf( + '

%s: %s

', + $status_colours[$healthElement['health']], + $name, + h($healthElement['message']) + ); + } + echo $lines; +?> diff --git a/templates/element/genericElements/header_scaffold.php b/templates/element/genericElements/header_scaffold.php index 1fe6a2f..ac999d8 100644 --- a/templates/element/genericElements/header_scaffold.php +++ b/templates/element/genericElements/header_scaffold.php @@ -53,7 +53,7 @@ foreach ($data['menu'] as $name => $menuElement) { } } $logoutButton = sprintf( - '%s', + '%s', $baseurl, __('Logout') ); From 00adbb0d7ea2e85185f58f4643321eb885c59ce3 Mon Sep 17 00:00:00 2001 From: Andras Iklody Date: Fri, 14 May 2021 13:21:23 +0200 Subject: [PATCH 04/14] fix: [metatemplates] updated MYSQL.sql --- INSTALL/mysql.sql | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/INSTALL/mysql.sql b/INSTALL/mysql.sql index 9f888dc..0846444 100644 --- a/INSTALL/mysql.sql +++ b/INSTALL/mysql.sql @@ -326,12 +326,17 @@ CREATE TABLE `meta_fields` ( `field` varchar(191) NOT NULL, `value` varchar(191) NOT NULL, `uuid` varchar(40) CHARACTER SET ascii DEFAULT NULL, + `meta_template_id` int(10) unsigned NOT NULL, + `meta_template_field_id` int(10) unsigned NOT NULL, + `is_default` tinyint(1) NOT NULL DEFAULT 0, PRIMARY KEY (`id`), KEY `scope` (`scope`), KEY `uuid` (`uuid`), KEY `parent_id` (`parent_id`), KEY `field` (`field`), - KEY `value` (`value`) + KEY `value` (`value`), + KEY `meta_template_id` (`meta_template_id`), + KEY `meta_template_field_id` (`meta_template_field_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; CREATE TABLE `meta_templates` ( From 32fc30a401e4861c3c4ebc85d2d8501c2c070e77 Mon Sep 17 00:00:00 2001 From: iglocska Date: Fri, 14 May 2021 13:31:19 +0200 Subject: [PATCH 05/14] fix: [baseurl] for openmodal actions in the index factory fixed, fixes #46 - invalid url by prepending any url (even # for modal tags) with the baseurl --- .../element/genericElements/IndexTable/Fields/actions.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/templates/element/genericElements/IndexTable/Fields/actions.php b/templates/element/genericElements/IndexTable/Fields/actions.php index c50f3fb..ed94a65 100644 --- a/templates/element/genericElements/IndexTable/Fields/actions.php +++ b/templates/element/genericElements/IndexTable/Fields/actions.php @@ -35,7 +35,7 @@ } } $url_param_data_paths = ''; - $url = empty($action['url']) ? '#' : h($action['url']); + $url = empty($action['url']) ? '#' : $baseurl . h($action['url']); if (!empty($action['url_params_data_paths'])) { if (is_array($action['url_params_data_paths'])) { $temp = array(); @@ -90,8 +90,7 @@ $action['onclick'] = sprintf('UI.openModalFromURL(\'%s\', \'%s\', \'%s\')', $modal_url, $reload_url, $tableRandomValue); } echo sprintf( - ' ', - $baseurl, + ' ', $url, empty($action['title']) ? '' : h($action['title']), empty($action['title']) ? '' : h($action['title']), From cb28278f3000f84dcfeeabbc9a2d275b0369b797 Mon Sep 17 00:00:00 2001 From: iglocska Date: Fri, 14 May 2021 13:43:28 +0200 Subject: [PATCH 06/14] fix: [logout] url fixed, fixes #45 --- templates/element/genericElements/header_scaffold.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/element/genericElements/header_scaffold.php b/templates/element/genericElements/header_scaffold.php index 1fe6a2f..ac999d8 100644 --- a/templates/element/genericElements/header_scaffold.php +++ b/templates/element/genericElements/header_scaffold.php @@ -53,7 +53,7 @@ foreach ($data['menu'] as $name => $menuElement) { } } $logoutButton = sprintf( - '%s', + '%s', $baseurl, __('Logout') ); From 380683163329d2a61ecd7e3119048fc80ecd3d2f Mon Sep 17 00:00:00 2001 From: iglocska Date: Tue, 1 Jun 2021 07:45:01 +0200 Subject: [PATCH 07/14] new: [localtools] functionality added --- src/Controller/LocalToolsController.php | 126 ++++++++++++++++++++---- src/Model/Table/LocalToolsTable.php | 84 ++++++++++++++-- 2 files changed, 184 insertions(+), 26 deletions(-) diff --git a/src/Controller/LocalToolsController.php b/src/Controller/LocalToolsController.php index 5546051..44d9f4e 100644 --- a/src/Controller/LocalToolsController.php +++ b/src/Controller/LocalToolsController.php @@ -20,11 +20,12 @@ class LocalToolsController extends AppController if ($this->request->is('ajax')) { $this->viewBuilder()->disableAutoLayout(); } - $this->set('metaGroup', 'LocalTools'); + $this->set('metaGroup', 'Administration'); } public function connectorIndex() { + $this->set('metaGroup', 'Admin'); $this->CRUD->index([ 'filters' => ['name', 'connector'], 'quickFilters' => ['name', 'connector'], @@ -38,17 +39,67 @@ class LocalToolsController extends AppController if ($this->ParamHandler->isRest()) { return $this->restResponsePayload; } - $this->set('metaGroup', 'LocalTools'); + $this->set('metaGroup', 'Administration'); } - public function action() + public function action($connectionId, $actionName) { - $params = []; - $results = $this->LocalTools->runAction(); - $this->render('add'); + $connection = $this->LocalTools->query()->where(['id' => $connectionId])->first(); + if (empty($connection)) { + throw new NotFoundException(__('Invalid connector.')); + } + $params = $this->ParamHandler->harvestParams($this->LocalTools->getActionFilterOptions($connection->connector, $actionName)); + $actionDetails = $this->LocalTools->getActionDetails($actionName); + $params['connection'] = $connection; + $results = $this->LocalTools->action($this->ACL->getUser()['id'], $connection->connector, $actionName, $params, $this->request); + if (!empty($results['redirect'])) { + $this->redirect($results['redirect']); + } + if (!empty($results['restResponse'])) { + return $results['restResponse']; + } + if ($this->ParamHandler->isRest()) { + return $results['data']['data']; + } + $this->set('data', $results); + $this->set('metaGroup', 'Administration'); + if ($actionDetails['type'] === 'formAction') { + if ($this->request->is(['post', 'put'])) { + if ($this->ParamHandler->isAjax()) { + if (!empty($results['success'])) { + return $this->RestResponse->ajaxSuccessResponse( + 'LocalTools', + 'action', + $connection, + empty($results['message']) ? __('Success.') : $results['message'] + ); + } else { + return $this->RestResponse->ajaxSuccessResponse( + 'LocalTools', + 'action', + false, + empty($results['message']) ? __('Success.') : $results['message'] + //['displayOnSuccess' => $displayOnSuccess] + ); + } + } else { + if (!empty($results['success'])) { + $this->Flash->success(empty($results['message']) ? __('Success.') : $results['message']); + $this->redirect(['controller' => 'localTools', 'action' => 'action', $connectionId, $actionDetails['redirect']]); + } else { + $this->Flash->error(empty($results['message']) ? __('Could not execute the requested action.') : $results['message']); + $this->redirect(['controller' => 'localTools', 'action' => 'action', $connectionId, $actionDetails['redirect']]); + } + } + } else { + $this->render('/Common/getForm'); + } + } else { + $this->render('/Common/' . $actionDetails['type']); + } } - public function add() + public function add($connector = false) { $this->CRUD->add(); if ($this->ParamHandler->isRest()) { @@ -60,7 +111,7 @@ class LocalToolsController extends AppController $dropdownData['connectors'][$connector['connector']] = $connector['name']; } $this->set(compact('dropdownData')); - $this->set('metaGroup', 'LocalTools'); + $this->set('metaGroup', 'Administration'); } public function viewConnector($connector_name) @@ -73,10 +124,10 @@ class LocalToolsController extends AppController } } if ($this->ParamHandler->isRest()) { - $this->restResponsePayload = $this->Controller->RestResponse->viewData($connector, 'json'); + return $this->RestResponse->viewData($connector, 'json'); } $this->set('entity', $connector); - $this->set('metaGroup', 'LocalTools'); + $this->set('metaGroup', 'Administration'); } public function edit($id) @@ -85,13 +136,16 @@ class LocalToolsController extends AppController if ($this->ParamHandler->isRest()) { return $this->restResponsePayload; } + if ($this->ParamHandler->isAjax() && !empty($this->ajaxResponsePayload)) { + return $this->ajaxResponsePayload; + } $connectors = $this->LocalTools->extractMeta($this->LocalTools->getConnectors()); $dropdownData = ['connectors' => []]; foreach ($connectors as $connector) { $dropdownData['connectors'][$connector['connector']] = $connector['name']; } $this->set(compact('dropdownData')); - $this->set('metaGroup', 'LocalTools'); + $this->set('metaGroup', 'Administration'); $this->render('add'); } @@ -101,22 +155,60 @@ class LocalToolsController extends AppController if ($this->ParamHandler->isRest()) { return $this->restResponsePayload; } - $this->set('metaGroup', 'LocalTools'); + $this->set('metaGroup', 'Administration'); } public function view($id) { - $this->CRUD->view($id); + $localTools = $this->LocalTools; + $this->CRUD->view($id, [ + 'afterFind' => function ($data) use($id, $localTools) { + $data['children'] = $localTools->getChildParameters($id); + return $data; + } + ]); $responsePayload = $this->CRUD->getResponsePayload(); if (!empty($responsePayload)) { return $responsePayload; } - $this->set('metaGroup', 'LocalTools'); + $this->set('metaGroup', 'Administration'); } - public function test() + public function exposedTools() { - $connectors = $this->LocalTools->getConnectors(); - $connectors['MispConnector']->test(); + $this->CRUD->index([ + 'filters' => ['name', 'connector'], + 'quickFilters' => ['name', 'connector'], + 'fields' => ['id', 'name', 'connector', 'description'], + 'filterFunction' => function($query) { + $query->where(['exposed' => 1]); + return $query; + }, + 'afterFind' => function($data) { + foreach ($data as $connector) { + $connector = [ + 'id' => $connector['id'], + 'name' => $connector['name'], + 'connector' => $connector['connector'] + ]; + } + return $data; + } + ]); + if ($this->ParamHandler->isRest()) { + return $this->restResponsePayload; + } + $this->set('metaGroup', 'Administration'); + } + + public function broodTools($id) + { + $this->loadModel('Broods'); + $tools = $this->Broods->queryLocalTools($id); + if ($this->ParamHandler->isRest()) { + return $this->RestResponse->viewData($tools, 'json'); + } + $this->set('data', $tools); + $this->set('metaGroup', 'Administration'); } } diff --git a/src/Model/Table/LocalToolsTable.php b/src/Model/Table/LocalToolsTable.php index 3c3389a..5deec9d 100644 --- a/src/Model/Table/LocalToolsTable.php +++ b/src/Model/Table/LocalToolsTable.php @@ -8,6 +8,7 @@ use Cake\Validation\Validator; use Migrations\Migrations; use Cake\Filesystem\Folder; use Cake\Filesystem\File; +use Cake\Http\Exception\NotFoundException; class LocalToolsTable extends AppTable { @@ -19,6 +20,10 @@ class LocalToolsTable extends AppTable 3 => 'ERROR', ]; + public $exposedFunctions = []; + + private $currentConnector = null; + private $connectors = null; public function initialize(array $config): void @@ -31,6 +36,43 @@ class LocalToolsTable extends AppTable return $validator; } + public function loadConnector(string $connectorName): void + { + if (empty($currentConnector) || get_class($currentConnector) !== $connectorName) { + $connectors = $this->getConnectors($connectorName); + if (empty($connectors[$connectorName])) { + throw new NotFoundException(__('Invalid connector module requested.')); + } else { + $this->currentConnector = $connectors[$connectorName]; + } + } + } + + public function action(int $user_id, string $connectorName, string $actionName, array $params, \Cake\Http\ServerRequest $request): array + { + $this->loadConnector($connectorName); + $params['request'] = $request; + $params['user_id'] = $user_id; + return $this->currentConnector->{$actionName}($params); + } + + public function getActionDetails(string $actionName): array + { + if (!empty($this->currentConnector->exposedFunctions[$actionName])) { + return $this->currentConnector->exposedFunctions[$actionName]; + } + throw new NotFoundException(__('Invalid connector module action requested.')); + } + + public function getActionFilterOptions(string $connectorName, string $actionName): array + { + $this->loadConnector($connectorName); + if (!empty($this->currentConnector->exposedFunctions[$actionName])) { + return $this->currentConnector->exposedFunctions[$actionName]['params'] ?? []; + } + throw new NotFoundException(__('Invalid connector module action requested.')); + } + public function getConnectors(string $name = null): array { $connectors = []; @@ -51,7 +93,6 @@ class LocalToolsTable extends AppTable } } return $connectors; - } public function extractMeta(array $connector_classes, bool $includeConnections = false): array @@ -80,24 +121,49 @@ class LocalToolsTable extends AppTable ]); $connections = $query->all()->toList(); foreach ($connections as &$connection) { - $connection = $this->healthCheckIndividual($connector_class, $connection); + $connection = $this->healthCheckIndividual($connection); } return $connections; } - public function healthCheckIndividual(Object $connector): array + public function healthCheckIndividual(Object $connection): array { - $connector_class = $this->getConnectors($connector['connector']); - if (empty($connector_class[$connector['connector']])) { + $connector_class = $this->getConnectors($connection->connector); + if (empty($connector_class[$connection->connector])) { return []; } - $connector_class = $connector_class[$connector['connector']]; - $health = $connector_class->health($connector); + $connector_class = $connector_class[$connection->connector]; + $health = $connector_class->health($connection); return $connection = [ - 'name' => $connector->name, + 'name' => $connection->name, 'health' => $health['status'], 'message' => $health['message'], - 'url' => '/localTools/viewConnection/' . $connector['id'] + 'url' => '/localTools/view/' . $connection['id'] ]; } + + public function getConnectorByConnectionId($id): array + { + $connection = $this->find()->where(['id' => $id])->first(); + if (empty($connection)) { + throw new NotFoundException(__('Invalid connection.')); + } + return $this->getConnectors($connection->connector); + } + + public function getChildParameters($id): array + { + $connectors = $this->getConnectorByConnectionId($id); + if (empty($connectors)) { + throw new NotFoundException(__('Invalid connector.')); + } + $connector = array_values($connectors)[0]; + $children = []; + foreach ($connector->exposedFunctions as $functionName => $function) { + if ($function['type'] === 'index') { + $children[] = $functionName; + } + } + return $children; + } } From cc31a225274bfef2004e04c4c042a08bc6615976 Mon Sep 17 00:00:00 2001 From: iglocska Date: Tue, 1 Jun 2021 07:45:58 +0200 Subject: [PATCH 08/14] new: [localtools] templates added --- templates/LocalTools/add.php | 13 +++++- templates/LocalTools/brood_tools.php | 49 +++++++++++++++++++++++ templates/LocalTools/connector_index.php | 9 +++++ templates/LocalTools/view.php | 51 ++++++++++++++++++++++++ 4 files changed, 120 insertions(+), 2 deletions(-) create mode 100644 templates/LocalTools/brood_tools.php diff --git a/templates/LocalTools/add.php b/templates/LocalTools/add.php index 10cefb6..702e659 100644 --- a/templates/LocalTools/add.php +++ b/templates/LocalTools/add.php @@ -12,14 +12,23 @@ 'options' => $dropdownData['connectors'], 'type' => 'dropdown' ], + [ + 'field' => 'exposed', + 'type' => 'checkbox' + ], [ 'field' => 'settings', 'type' => 'textarea' - ] + ], + [ + 'field' => 'description', + 'type' => 'textarea' + ], ], 'submit' => [ 'action' => $this->request->getParam('action') - ] + ], + 'url' => empty($redirect) ? null : $redirect ] ]); ?> diff --git a/templates/LocalTools/brood_tools.php b/templates/LocalTools/brood_tools.php new file mode 100644 index 0000000..ae41159 --- /dev/null +++ b/templates/LocalTools/brood_tools.php @@ -0,0 +1,49 @@ +element('genericElements/IndexTable/index_table', [ + 'data' => [ + 'data' => $data, + 'top_bar' => [ + 'children' => [ + [ + 'type' => 'search', + 'button' => __('Filter'), + 'placeholder' => __('Enter value to search'), + 'data' => '', + 'searchKey' => 'value' + ] + ] + ], + 'fields' => [ + [ + 'name' => __('Id'), + 'data_path' => 'id', + ], + [ + 'name' => __('Name'), + 'data_path' => 'name', + ], + [ + 'name' => 'Connector', + 'data_path' => 'connector' + ], + [ + 'name' => __('Description'), + 'data_path' => 'description', + ] + ], + 'title' => __('Local tools made available by the remote Cerebrate'), + 'description' => __('Cerebrate can connect to local tools via individual connectors and administrators can choose to expose a subset of their tools to other members of their Cerebrate in order for their peers to be able to issue interconnection requests. '), + 'pull' => 'right', + 'skip_pagination' => 1, + 'actions' => [ + [ + 'url' => '/localTools/connectionRequest', + 'url_params_data_paths' => ['id'], + 'title' => 'Issue a connection request', + 'icon' => 'plug' + ] + ] + ] +]); +echo '
'; +?> diff --git a/templates/LocalTools/connector_index.php b/templates/LocalTools/connector_index.php index 06c8560..8a2bf4e 100644 --- a/templates/LocalTools/connector_index.php +++ b/templates/LocalTools/connector_index.php @@ -29,12 +29,21 @@ echo $this->element('genericElements/IndexTable/index_table', [ 'sort' => 'connector', 'data_path' => 'connector', ], + [ + 'name' => 'Exposed', + 'data_path' => 'exposed', + 'element' => 'boolean' + ], [ 'name' => 'settings', 'data_path' => 'settings', 'isJson' => 1, 'element' => 'array' ], + [ + 'name' => 'description', + 'data_path' => 'description' + ], [ 'name' => 'health', 'data_path' => 'health', diff --git a/templates/LocalTools/view.php b/templates/LocalTools/view.php index e69de29..7a523f9 100644 --- a/templates/LocalTools/view.php +++ b/templates/LocalTools/view.php @@ -0,0 +1,51 @@ + '/LocalTools/action/{{0}}/' . $child, + 'url_params' => ['id'], + 'title' => \Cake\Utility\Inflector::humanize(substr($child, 0, -6)) + ]; + } + } + echo $this->element( + '/genericElements/SingleViews/single_view', + [ + 'data' => $entity, + 'title' => sprintf( + '%s control panel using %s', + h($entity->name), + h($entity->connector) + ), + 'fields' => [ + [ + 'key' => __('ID'), + 'path' => 'id' + ], + [ + 'key' => __('Name'), + 'path' => 'name' + ], + [ + 'key' => __('Connector'), + 'path' => 'connector' + ], + [ + 'key' => __('Exposed'), + 'path' => 'exposed', + 'type' => 'boolean' + ], + [ + 'key' => __('settings'), + 'path' => 'settings', + 'type' => 'json' + ], + [ + 'key' => __('Description'), + 'path' => 'description' + ] + ], + 'children' => $children + ] + ); From d84caa97c98fcb4589e04d42612ab4eb1e3fb744 Mon Sep 17 00:00:00 2001 From: iglocska Date: Tue, 1 Jun 2021 07:46:29 +0200 Subject: [PATCH 09/14] chg: [templates] updated to support the new functionalities of localtools --- templates/Broods/index.php | 7 +++++++ templates/Common/getForm.php | 2 ++ templates/Common/index.php | 2 +- .../IndexTable/Fields/actions.php | 20 ++++++++++++++----- .../IndexTable/Fields/array_lookup_field.php | 4 +++- .../IndexTable/Fields/generic_field.php | 1 - .../SingleViews/Fields/genericField.php | 1 + .../SingleViews/Fields/jsonField.php | 19 ++++++++++++++++++ 8 files changed, 48 insertions(+), 8 deletions(-) create mode 100644 templates/Common/getForm.php create mode 100644 templates/element/genericElements/SingleViews/Fields/jsonField.php diff --git a/templates/Broods/index.php b/templates/Broods/index.php index 8e873d4..43d454a 100644 --- a/templates/Broods/index.php +++ b/templates/Broods/index.php @@ -66,8 +66,15 @@ echo $this->element('genericElements/IndexTable/index_table', [ [ 'url' => '/broods/view', 'url_params_data_paths' => ['id'], + 'title' => __('View details'), 'icon' => 'eye' ], + [ + 'url' => '/localTools/broodTools', + 'url_params_data_paths' => ['id'], + 'title' => __('List available local tools'), + 'icon' => 'wrench' + ], [ 'open_modal' => '/broods/edit/[onclick_params_data_path]', 'modal_params_data_path' => 'id', diff --git a/templates/Common/getForm.php b/templates/Common/getForm.php new file mode 100644 index 0000000..e147f14 --- /dev/null +++ b/templates/Common/getForm.php @@ -0,0 +1,2 @@ +element('genericElements/Form/genericForm', $data); diff --git a/templates/Common/index.php b/templates/Common/index.php index 969eb89..1f0a5f6 100644 --- a/templates/Common/index.php +++ b/templates/Common/index.php @@ -1,4 +1,4 @@ element('genericElements/IndexTable/index_table', $index_table); + echo $this->element('genericElements/IndexTable/index_table', $data); echo ''; ?> diff --git a/templates/element/genericElements/IndexTable/Fields/actions.php b/templates/element/genericElements/IndexTable/Fields/actions.php index ed94a65..4e4bf61 100644 --- a/templates/element/genericElements/IndexTable/Fields/actions.php +++ b/templates/element/genericElements/IndexTable/Fields/actions.php @@ -81,11 +81,21 @@ ); } else if (!empty($action['open_modal']) && !empty($action['modal_params_data_path'])) { - $modal_url = str_replace( - '[onclick_params_data_path]', - h(Cake\Utility\Hash::extract($row, $action['modal_params_data_path'])[0]), - $action['open_modal'] - ); + if (is_array($action['modal_params_data_path'])) { + foreach ($action['modal_params_data_path'] as $k => $v) { + $modal_url = str_replace( + sprintf('{{%s}}', $k), + h(Cake\Utility\Hash::extract($row, $v)[0]), + $action['open_modal'] + ); + } + } else { + $modal_url = str_replace( + '[onclick_params_data_path]', + h(Cake\Utility\Hash::extract($row, $action['modal_params_data_path'])[0]), + $action['open_modal'] + ); + } $reload_url = !empty($action['reload_url']) ? $action['reload_url'] : $this->Url->build(['action' => 'index']); $action['onclick'] = sprintf('UI.openModalFromURL(\'%s\', \'%s\', \'%s\')', $modal_url, $reload_url, $tableRandomValue); } diff --git a/templates/element/genericElements/IndexTable/Fields/array_lookup_field.php b/templates/element/genericElements/IndexTable/Fields/array_lookup_field.php index 9f929f2..d361ef1 100644 --- a/templates/element/genericElements/IndexTable/Fields/array_lookup_field.php +++ b/templates/element/genericElements/IndexTable/Fields/array_lookup_field.php @@ -1,4 +1,6 @@ Hash->extract($row, $field['data_path'])[0]]); ?> diff --git a/templates/element/genericElements/IndexTable/Fields/generic_field.php b/templates/element/genericElements/IndexTable/Fields/generic_field.php index edff114..2ef0980 100644 --- a/templates/element/genericElements/IndexTable/Fields/generic_field.php +++ b/templates/element/genericElements/IndexTable/Fields/generic_field.php @@ -16,7 +16,6 @@ '', $data ? 'check' : 'times' ); - $data = ''; } else { $data = h($data); if (!empty($field['privacy'])) { diff --git a/templates/element/genericElements/SingleViews/Fields/genericField.php b/templates/element/genericElements/SingleViews/Fields/genericField.php index 3c54be3..cba9ff6 100644 --- a/templates/element/genericElements/SingleViews/Fields/genericField.php +++ b/templates/element/genericElements/SingleViews/Fields/genericField.php @@ -2,6 +2,7 @@ if (isset($field['raw'])) { $string = $field['raw']; } else { + $value = 1; $value = Cake\Utility\Hash::extract($data, $field['path']); $string = empty($value[0]) ? '' : $value[0]; } diff --git a/templates/element/genericElements/SingleViews/Fields/jsonField.php b/templates/element/genericElements/SingleViews/Fields/jsonField.php new file mode 100644 index 0000000..9c8b16b --- /dev/null +++ b/templates/element/genericElements/SingleViews/Fields/jsonField.php @@ -0,0 +1,19 @@ +', + h($randomId) + ); +?> + + From 7a1b54c7abd3986d43b2b446cecd9d584d7dcc68 Mon Sep 17 00:00:00 2001 From: iglocska Date: Tue, 1 Jun 2021 07:46:51 +0200 Subject: [PATCH 10/14] chg: [js] minor changes to support the localtools --- webroot/js/api-helper.js | 15 +++++++-------- webroot/js/main.js | 26 ++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/webroot/js/api-helper.js b/webroot/js/api-helper.js index 282b1b4..fa09422 100644 --- a/webroot/js/api-helper.js +++ b/webroot/js/api-helper.js @@ -77,7 +77,7 @@ class AJAXApi { } /** - * + * * @param {FormData} formData - The data of a form * @param {Object} dataToMerge - Data to be merge into formData * @return {FormData} The form data merged with the additional dataToMerge data @@ -91,7 +91,7 @@ class AJAXApi { /** * @param {string} url - The URL to fetch - * @param {Object} [options={}] - The options supported by AJAXApi#defaultOptions + * @param {Object} [options={}] - The options supported by AJAXApi#defaultOptions * @return {Promise} Promise object resolving to the fetched HTML */ static async quickFetchURL(url, options={}) { @@ -102,7 +102,7 @@ class AJAXApi { /** * @param {string} url - The URL to fetch - * @param {Object} [options={}] - The options supported by AJAXApi#defaultOptions + * @param {Object} [options={}] - The options supported by AJAXApi#defaultOptions * @return {Promise} Promise object resolving to the fetched HTML */ static async quickFetchJSON(url, options={}) { @@ -113,7 +113,7 @@ class AJAXApi { /** * @param {string} url - The URL to fetch - * @param {Object} [options={}] - The options supported by AJAXApi#defaultOptions + * @param {Object} [options={}] - The options supported by AJAXApi#defaultOptions * @return {Promise} Promise object resolving to the fetched form */ static async quickFetchForm(url, options={}) { @@ -125,7 +125,7 @@ class AJAXApi { /** * @param {HTMLFormElement} form - The form to be posted * @param {Object} [dataToMerge={}] - Additional data to be integrated or modified in the form - * @param {Object} [options={}] - The options supported by AJAXApi#defaultOptions + * @param {Object} [options={}] - The options supported by AJAXApi#defaultOptions * @return {Promise} Promise object resolving to the result of the POST operation */ static async quickPostForm(form, dataToMerge={}, options={}) { @@ -328,7 +328,7 @@ class AJAXApi { } return toReturn } - + /** * @param {string} url - The URL from which to fetch the form * @param {Object} [dataToMerge={}] - Additional data to be integrated or modified in the form @@ -385,8 +385,7 @@ class AJAXApi { this.loadingOverlay.show() } else { this.loadingOverlay.hide() - + } } } - diff --git a/webroot/js/main.js b/webroot/js/main.js index 3e05d3a..ea39c37 100644 --- a/webroot/js/main.js +++ b/webroot/js/main.js @@ -66,6 +66,32 @@ function attachTestConnectionResultHtml(result, $container) { return $testResultDiv } +function syntaxHighlightJson(json, indent) { + if (indent === undefined) { + indent = 2; + } + if (typeof json == 'string') { + json = JSON.parse(json); + } + json = JSON.stringify(json, undefined, indent); + json = json.replace(/&/g, '&').replace(//g, '>').replace(/(?:\r\n|\r|\n)/g, '
').replace(/ /g, ' '); + return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) { + var cls = 'text-info'; + if (/^"/.test(match)) { + if (/:$/.test(match)) { + cls = 'text-primary'; + } else { + cls = ''; + } + } else if (/true|false/.test(match)) { + cls = 'text-info'; + } else if (/null/.test(match)) { + cls = 'text-danger'; + } + return '' + match + ''; + }); +} + var UI $(document).ready(() => { if (typeof UIFactory !== "undefined") { From 51b4bc811f70a5d9aed3589a10f7ad33cb046404 Mon Sep 17 00:00:00 2001 From: iglocska Date: Tue, 1 Jun 2021 07:47:22 +0200 Subject: [PATCH 11/14] chg: [improvements] to a st of controllers and components to support localtools - still missing ACL entries! --- src/Controller/Component/ACLComponent.php | 28 +++++++++++++++++++ src/Controller/Component/CRUDComponent.php | 9 ++++++ .../Component/RestResponseComponent.php | 4 +-- 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/Controller/Component/ACLComponent.php b/src/Controller/Component/ACLComponent.php index e7ff59f..2a5a648 100644 --- a/src/Controller/Component/ACLComponent.php +++ b/src/Controller/Component/ACLComponent.php @@ -671,6 +671,34 @@ class ACLComponent extends Component 'popup' => 1 ] ] + ], + 'LocalTools' => [ + 'label' => __('Local Tools'), + 'url' => '/localTools/index', + 'children' => [ + 'index' => [ + 'url' => '/localTools/index', + 'label' => __('List Connectors') + ], + 'viewConnector' => [ + 'url' => '/localTools/viewConnector/{{connector}}', + 'label' => __('View Connector'), + 'actions' => ['view'], + 'skipTopMenu' => 1 + ], + 'add' => [ + 'url' => '/localTools/add/{{connector}}', + 'label' => __('Add connection'), + 'actions' => ['viewConnector'], + 'skipTopMenu' => 1 + ], + 'view' => [ + 'url' => '/localTools/view/{{id}}', + 'label' => __('View Connection'), + 'actions' => ['view'], + 'skipTopMenu' => 1 + ] + ] ] ], 'Cerebrate' => [ diff --git a/src/Controller/Component/CRUDComponent.php b/src/Controller/Component/CRUDComponent.php index ee373a1..32d3717 100644 --- a/src/Controller/Component/CRUDComponent.php +++ b/src/Controller/Component/CRUDComponent.php @@ -38,11 +38,17 @@ class CRUDComponent extends Component } $params = $this->Controller->ParamHandler->harvestParams($optionFilters); $query = $this->Table->find(); + if (!empty($options['filterFunction'])) { + $query = $options['filterFunction']($query); + } $query = $this->setFilters($params, $query, $options); $query = $this->setQuickFilters($params, $query, empty($options['quickFilters']) ? [] : $options['quickFilters']); if (!empty($options['contain'])) { $query->contain($options['contain']); } + if (!empty($options['fields'])) { + $query->select($options['fields']); + } if ($this->Controller->ParamHandler->isRest()) { $data = $query->all(); if (isset($options['afterFind'])) { @@ -323,6 +329,9 @@ class CRUDComponent extends Component $data = $this->Table->get($id, $params); $data = $this->attachMetaData($id, $data); + if (isset($params['afterFind'])) { + $data = $params['afterFind']($data); + } if ($this->Controller->ParamHandler->isRest()) { $this->Controller->restResponsePayload = $this->Controller->RestResponse->viewData($data, 'json'); } diff --git a/src/Controller/Component/RestResponseComponent.php b/src/Controller/Component/RestResponseComponent.php index ef9bf53..96b3725 100644 --- a/src/Controller/Component/RestResponseComponent.php +++ b/src/Controller/Component/RestResponseComponent.php @@ -426,8 +426,8 @@ class RestResponseComponent extends Component $response = [ 'success' => true, 'message' => $message, - 'data' => $entity->toArray(), - 'url' => $this->__generateURL($action, $ObjectAlias, $entity->id) + 'data' => $entity->toArray() + //'url' => $this->__generateURL($action, $ObjectAlias, $entity->id) ]; if (!empty($additionalData)) { $response['additionalData'] = $additionalData; From 73457ed562ab6be27148f7590efbc20745d57a4f Mon Sep 17 00:00:00 2001 From: iglocska Date: Tue, 1 Jun 2021 07:48:06 +0200 Subject: [PATCH 12/14] new: [misp] connector fleshed out with more functionalities - additionally, improvements to the common connector functionalities --- .../CommonConnectorTools.php | 21 + .../local_tool_connectors/MispConnector.php | 485 ++++++++++++++++-- 2 files changed, 474 insertions(+), 32 deletions(-) diff --git a/src/Lib/default/local_tool_connectors/CommonConnectorTools.php b/src/Lib/default/local_tool_connectors/CommonConnectorTools.php index 1a04acb..c4ab2ef 100644 --- a/src/Lib/default/local_tool_connectors/CommonConnectorTools.php +++ b/src/Lib/default/local_tool_connectors/CommonConnectorTools.php @@ -1,6 +1,7 @@ get('Organisations'); + $organisations->captureOrg($input); + return true; + } + + public function captureSharingGroup($input): bool + { + if (empty($input['uuid'])) { + return false; + } + $sharing_groups = \Cake\ORM\TableRegistry::getTableLocator()->get('SharingGroups'); + $sharing_groups->captureSharingGroup($input); + return true; + } } ?> diff --git a/src/Lib/default/local_tool_connectors/MispConnector.php b/src/Lib/default/local_tool_connectors/MispConnector.php index 838d620..1acd685 100644 --- a/src/Lib/default/local_tool_connectors/MispConnector.php +++ b/src/Lib/default/local_tool_connectors/MispConnector.php @@ -3,14 +3,75 @@ namespace MispConnector; require_once(ROOT . '/src/Lib/default/local_tool_connectors/CommonConnectorTools.php'); use CommonConnectorTools\CommonConnectorTools; use Cake\Http\Client; +use Cake\Http\Exception\NotFoundException; +use Cake\Http\Exception\MethodNotAllowedException; +use Cake\Http\Client\Response; class MispConnector extends CommonConnectorTools { public $description = 'MISP connector, handling diagnostics, organisation and sharing group management of your instance. Synchronisation requests can also be managed through the connector.'; + public $name = 'MISP'; + public $exposedFunctions = [ - 'diagnostics', - 'viewOrgAction' + 'serverSettingsAction' => [ + 'type' => 'index', + 'scope' => 'child', + 'params' => [ + 'quickFilter', + 'sort', + 'direction', + 'page', + 'limit' + ] + ], + 'organisationsAction' => [ + 'type' => 'index', + 'scope' => 'child', + 'params' => [ + 'quickFilter', + 'limit', + 'page', + 'sort', + 'direction' + ] + ], + 'sharingGroupsAction' => [ + 'type' => 'index', + 'scope' => 'child', + 'params' => [ + 'quickFilter', + 'limit', + 'page', + 'sort', + 'direction' + ] + ], + 'fetchOrganisationAction' => [ + 'type' => 'formAction', + 'scope' => 'childAction', + 'params' => [ + 'uuid' + ], + 'redirect' => 'organisationsAction' + ], + 'fetchSharingGroupAction' => [ + 'type' => 'formAction', + 'scope' => 'childAction', + 'params' => [ + 'uuid' + ], + 'redirect' => 'sharingGroupsAction' + ], + 'modifySettingAction' => [ + 'type' => 'formAction', + 'scope' => 'childAction', + 'params' => [ + 'setting', + 'value' + ], + 'redirect' => 'serverSettingsAction' + ] ]; public $version = '0.1'; @@ -19,6 +80,15 @@ class MispConnector extends CommonConnectorTools $this->exposedFunctions[] = $functionName; } + public function getExposedFunction(string $functionName): array + { + if (!empty($this->exposedFunctions[$functionName])) { + return $exposedFunctions[$functionName]; + } else { + throw new NotFoundException(__('Invalid action requested.')); + } + } + public function health(Object $connection): array { $settings = json_decode($connection->settings, true); @@ -48,36 +118,100 @@ class MispConnector extends CommonConnectorTools ]; } - private function viewOrgAction(array $params): array + private function getData(string $url, array $params): Response { if (empty($params['connection'])) { - throw new InvalidArgumentException(__('No connection object received.')); + throw new NotFoundException(__('No connection object received.')); } $settings = json_decode($params['connection']->settings, true); $http = new Client(); - $url = '/users/organisations/index/scope:all'; - if (!empty($params['page'])) { - $url .= '/page:' . $params['page']; + if (!empty($params['sort'])) { + $list = explode('.', $params['sort']); + $params['sort'] = end($list); } - if (!empty($params['limit'])) { - $url .= '/limit:' . $params['limit']; + if (!isset($params['limit'])) { + $params['limit'] = 50; } - if (!empty($params['quickFilter'])) { - $url .= '/searchall:' . $params['quickFilter']; - } - $response = $http->post($settings['url'] . '/users/organisations/index/scope:all', '{}', [ + $url = $this->urlAppendParams($url, $params); + $response = $http->get($settings['url'] . $url, false, [ 'headers' => [ 'AUTHORIZATION' => $settings['authkey'], - 'Accept' => 'Application/json', - 'Content-type' => 'Application/json' + 'Accept' => 'application/json', + 'Content-type' => 'application/json' ] ]); - $responseCode = $response->getStatusCode(); if ($response->isOk()) { - return [ + return $response; + } else { + throw new NotFoundException(__('Could not retrieve the requested resource.')); + } + } + + private function postData(string $url, array $params): Response + { + if (empty($params['connection'])) { + throw new NotFoundException(__('No connection object received.')); + } + $settings = json_decode($params['connection']->settings, true); + $http = new Client(); + $url = $this->urlAppendParams($url, $params); + $response = $http->post($settings['url'] . $url, json_encode($params['body']), [ + 'headers' => [ + 'AUTHORIZATION' => $settings['authkey'], + 'Accept' => 'application/json' + ], + 'type' => 'json' + ]); + if ($response->isOk()) { + return $response; + } else { + throw new NotFoundException(__('Could not post to the requested resource.')); + } + } + + public function urlAppendParams(string $url, array $params): string + { + if (!isset($params['validParams'])) { + $validParams = [ + 'quickFilter' => 'searchall', + 'sort' => 'sort', + 'page' => 'page', + 'direction' => 'direction', + 'limit' => 'limit' + ]; + } else { + $validParams = $params['validParams']; + } + foreach ($validParams as $param => $remoteParam) { + if (!empty($params[$param])) { + $url .= sprintf('/%s:%s', $remoteParam, $params[$param]); + } + } + return $url; + } + + + public function diagnosticsAction(array $params): array + { + + } + + public function serverSettingsAction(array $params): array + { + $params['validParams'] = [ + 'limit' => 'limit', + 'page' => 'page', + 'quickFilter' => 'searchall' + ]; + $urlParams = h($params['connection']['id']) . '/serverSettingsAction'; + $response = $this->getData('/servers/serverSettings', $params); + $data = $response->getJson(); + if (!empty($data['finalSettings'])) { + $finalSettings = [ 'type' => 'index', 'data' => [ - 'data' => json_decode($response->getBody(), true), + 'data' => $data['finalSettings'], + 'skip_pagination' => 1, 'top_bar' => [ 'children' => [ [ @@ -85,25 +219,42 @@ class MispConnector extends CommonConnectorTools 'button' => __('Filter'), 'placeholder' => __('Enter value to search'), 'data' => '', - 'searchKey' => 'value' + 'searchKey' => 'value', + 'additionalUrlParams' => $urlParams ] ] ], 'fields' => [ [ - 'name' => '#', - 'sort' => 'id', - 'data_path' => 'Organisation.id', + 'name' => 'Setting', + 'sort' => 'setting', + 'data_path' => 'setting', ], [ - 'name' => __('Name'), - 'sort' => 'name', - 'data_path' => 'Organisation.name', + 'name' => 'Criticality', + 'sort' => 'level', + 'data_path' => 'level', + 'arrayData' => [ + 0 => 'Critical', + 1 => 'Recommended', + 2 => 'Optional' + ], + 'element' => 'array_lookup_field' ], [ - 'name' => __('UUID'), - 'sort' => 'uuid', - 'data_path' => 'Organisation.uuid', + 'name' => __('Value'), + 'sort' => 'value', + 'data_path' => 'value', + ], + [ + 'name' => __('Type'), + 'sort' => 'type', + 'data_path' => 'type', + ], + [ + 'name' => __('Error message'), + 'sort' => 'errorMessage', + 'data_path' => 'errorMessage', ] ], 'title' => false, @@ -111,17 +262,287 @@ class MispConnector extends CommonConnectorTools 'pull' => 'right', 'actions' => [ [ - 'url' => '/localTools/action/fetchOrg', - 'url_params_data_paths' => ['id'], - 'icon' => 'download' + 'open_modal' => '/localTools/action/' . h($params['connection']['id']) . '/modifySettingAction?setting={{0}}', + 'modal_params_data_path' => ['setting'], + 'icon' => 'download', + 'reload_url' => '/localTools/action/' . h($params['connection']['id']) . '/ServerSettingsAction' + ] + ] + ] + ]; + if (!empty($params['quickFilter'])) { + $needle = strtolower($params['quickFilter']); + foreach ($finalSettings['data']['data'] as $k => $v) { + if (strpos(strtolower($v['setting']), $needle) === false) { + unset($finalSettings['data']['data'][$k]); + } + } + $finalSettings['data']['data'] = array_values($finalSettings['data']['data']); + } + return $finalSettings; + } else { + return []; + } + } + + public function organisationsAction(array $params): array + { + $params['validParams'] = [ + 'limit' => 'limit', + 'page' => 'page', + 'quickFilter' => 'searchall' + ]; + $urlParams = h($params['connection']['id']) . '/organisationsAction'; + $response = $this->getData('/organisations/index', $params); + $data = $response->getJson(); + if (!empty($data)) { + return [ + 'type' => 'index', + 'data' => [ + 'data' => $data, + 'skip_pagination' => 1, + 'top_bar' => [ + 'children' => [ + [ + 'type' => 'search', + 'button' => __('Filter'), + 'placeholder' => __('Enter value to search'), + 'data' => '', + 'searchKey' => 'value', + 'additionalUrlParams' => $urlParams + ] + ] + ], + 'fields' => [ + [ + 'name' => 'Name', + 'sort' => 'Organisation.name', + 'data_path' => 'Organisation.name', + ], + [ + 'name' => 'uuid', + 'sort' => 'Organisation.uuid', + 'data_path' => 'Organisation.uuid' + ], + [ + 'name' => 'nationality', + 'sort' => 'Organisation.nationality', + 'data_path' => 'Organisation.nationality' + ], + [ + 'name' => 'sector', + 'sort' => 'Organisation.sector', + 'data_path' => 'Organisation.sector' + ] + ], + 'title' => false, + 'description' => false, + 'pull' => 'right', + 'actions' => [ + [ + 'open_modal' => '/localTools/action/' . h($params['connection']['id']) . '/fetchOrganisationAction?uuid={{0}}', + 'modal_params_data_path' => ['Organisation.uuid'], + 'icon' => 'download', + 'reload_url' => '/localTools/action/' . h($params['connection']['id']) . '/organisationsAction' ] ] ] ]; } else { - return __('Could not fetch the organisations, error code: {0}', $response->getStatusCode()); + return []; } } + + public function sharingGroupsAction(array $params): array + { + $params['validParams'] = [ + 'limit' => 'limit', + 'page' => 'page', + 'quickFilter' => 'searchall' + ]; + $urlParams = h($params['connection']['id']) . '/sharingGroupsAction'; + $response = $this->getData('/sharing_groups/index', $params); + $data = $response->getJson(); + if (!empty($data)) { + return [ + 'type' => 'index', + 'data' => [ + 'data' => $data['response'], + 'skip_pagination' => 1, + 'top_bar' => [ + 'children' => [ + [ + 'type' => 'search', + 'button' => __('Filter'), + 'placeholder' => __('Enter value to search'), + 'data' => '', + 'searchKey' => 'value', + 'additionalUrlParams' => $urlParams + ] + ] + ], + 'fields' => [ + [ + 'name' => 'Name', + 'sort' => 'SharingGroup.name', + 'data_path' => 'SharingGroup.name', + ], + [ + 'name' => 'uuid', + 'sort' => 'SharingGroup.uuid', + 'data_path' => 'SharingGroup.uuid' + ], + [ + 'name' => 'Organisations', + 'sort' => 'Organisation', + 'data_path' => 'Organisation', + 'element' => 'count_summary' + ], + [ + 'name' => 'Roaming', + 'sort' => 'SharingGroup.roaming', + 'data_path' => 'SharingGroup.roaming', + 'element' => 'boolean' + ], + [ + 'name' => 'External servers', + 'sort' => 'Server', + 'data_path' => 'Server', + 'element' => 'count_summary' + ] + ], + 'title' => false, + 'description' => false, + 'pull' => 'right', + 'actions' => [ + [ + 'open_modal' => '/localTools/action/' . h($params['connection']['id']) . '/fetchSharingGroupAction?uuid={{0}}', + 'modal_params_data_path' => ['SharingGroup.uuid'], + 'icon' => 'download', + 'reload_url' => '/localTools/action/' . h($params['connection']['id']) . '/SharingGroupsAction' + ] + ] + ] + ]; + } else { + return []; + } + } + + public function fetchOrganisationAction(array $params): array + { + if ($params['request']->is(['get'])) { + return [ + 'data' => [ + 'title' => __('Fetch organisation'), + 'description' => __('Fetch and create/update organisation ({0}) from MISP.', $params['uuid']), + 'submit' => [ + 'action' => $params['request']->getParam('action') + ], + 'url' => ['controller' => 'localTools', 'action' => 'action', $params['connection']['id'], 'fetchOrganisationAction', $params['uuid']] + ] + ]; + } elseif ($params['request']->is(['post'])) { + $response = $this->getData('/organisations/view/' . $params['uuid'], $params); + if ($response->getStatusCode() == 200) { + $result = $this->captureOrganisation($response->getJson()['Organisation']); + if ($result) { + return ['success' => 1, 'message' => __('Organisation created/modified.')]; + } else { + return ['success' => 0, 'message' => __('Could not save the changes to the organisation.')]; + } + } else { + return ['success' => 0, 'message' => __('Could not fetch the remote organisation.')]; + } + } + throw new MethodNotAllowedException(__('Invalid http request type for the given action.')); + } + + public function fetchSharingGroupAction(array $params): array + { + if ($params['request']->is(['get'])) { + return [ + 'data' => [ + 'title' => __('Fetch sharing group'), + 'description' => __('Fetch and create/update sharing group ({0}) from MISP.', $params['uuid']), + 'submit' => [ + 'action' => $params['request']->getParam('action') + ], + 'url' => ['controller' => 'localTools', 'action' => 'action', $params['connection']['id'], 'fetchSharingGroupAction', $params['uuid']] + ] + ]; + } elseif ($params['request']->is(['post'])) { + $response = $this->getData('/sharing_groups/view/' . $params['uuid'], $params); + if ($response->getStatusCode() == 200) { + $mispSG = $response->getJson(); + $sg = [ + 'uuid' => $mispSG['SharingGroup']['uuid'], + 'name' => $mispSG['SharingGroup']['name'], + 'releasability' => $mispSG['SharingGroup']['releasability'], + 'description' => $mispSG['SharingGroup']['description'], + 'organisation' => $mispSG['Organisation'], + 'sharing_group_orgs' => [] + ]; + foreach ($mispSG['SharingGroupOrg'] as $sgo) { + $sg['sharing_group_orgs'][] = $sgo['Organisation']; + } + $result = $this->captureSharingGroup($sg, $params['user_id']); + if ($result) { + return ['success' => 1, 'message' => __('Sharing group created/modified.')]; + } else { + return ['success' => 0, 'message' => __('Could not save the changes to the sharing group.')]; + } + } else { + return ['success' => 0, 'message' => __('Could not fetch the remote sharing group.')]; + } + } + throw new MethodNotAllowedException(__('Invalid http request type for the given action.')); + } + + public function modifySettingAction(array $params): array + { + if ($params['request']->is(['get'])) { + $response = $this->getData('/servers/getSetting/' . $params['setting'], $params); + if ($response->getStatusCode() != 200) { + throw new NotFoundException(__('Setting could not be fetched from the remote.')); + } + $response = $response->getJson(); + $types = [ + 'string' => 'text', + 'boolean' => 'checkbox', + 'numeric' => 'number' + ]; + $fields = [ + [ + 'field' => 'value', + 'label' => __('Value'), + 'default' => h($response['value']), + 'type' => $types[$response['type']] + ], + ]; + return [ + 'data' => [ + 'title' => __('Modify server setting'), + 'description' => __('Modify setting ({0}) on connected MISP instance.', $params['setting']), + 'fields' => $fields, + 'submit' => [ + 'action' => $params['request']->getParam('action') + ], + 'url' => ['controller' => 'localTools', 'action' => 'action', $params['connection']['id'], 'modifySettingAction', $params['setting']] + ] + ]; + } elseif ($params['request']->is(['post'])) { + $params['body'] = ['value' => $params['value']]; + $response = $this->postData('/servers/serverSettingsEdit/' . $params['setting'], $params); + if ($response->getStatusCode() == 200) { + return ['success' => 1, 'message' => __('Setting saved.')]; + } else { + return ['success' => 0, 'message' => __('Could not fetch the remote sharing group.')]; + } + } + throw new MethodNotAllowedException(__('Invalid http request type for the given action.')); + + } } ?> From c6332dee16791cf4ed8cc1cb427b17f7f6dc2dd5 Mon Sep 17 00:00:00 2001 From: iglocska Date: Tue, 1 Jun 2021 07:49:22 +0200 Subject: [PATCH 13/14] new: [sharing group] capture functionality added - capture incoming sharing groups and add/update them when appropriate - also capture child organisations --- src/Model/Table/SharingGroupsTable.php | 51 ++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/src/Model/Table/SharingGroupsTable.php b/src/Model/Table/SharingGroupsTable.php index 5b6fd66..41cf20c 100644 --- a/src/Model/Table/SharingGroupsTable.php +++ b/src/Model/Table/SharingGroupsTable.php @@ -43,4 +43,55 @@ class SharingGroupsTable extends AppTable { return $rules; } + + public function captureSharingGroup($input, int $user_id = 0): ?int + { + if (!empty($input['id'])) { + unset($input['id']); + } + if (!empty($input['uuid'])) { + $existingSG = $this->find()->where([ + 'uuid' => $input['uuid'] + ])->first(); + } else { + return null; + } + if (empty($existingSG)) { + $data = $this->newEmptyEntity(); + $input['organisation_id'] = $this->Organisations->captureOrg($input['organisation']); + $input['user_id'] = $user_id; + $data = $this->patchEntity($data, $input, ['associated' => []]); + if (!$this->save($data)) { + return null; + } + $savedSG = $data; + } else { + $reserved = ['id', 'uuid', 'metaFields']; + foreach ($input as $field => $value) { + if (in_array($field, $reserved)) { + continue; + } + $existingSG->$field = $value; + } + if (!$this->save($existingSG)) { + return null; + } + $savedSG = $existingSG; + } + $this->postCaptureActions($savedSG->id, $input); + return $savedSG->id; + } + + public function postCaptureActions($id, $input): void + { + $sharingGroup = $this->find()->where([ + 'id' => $id + ])->first(); + $orgs = []; + foreach ($input['sharing_group_orgs'] as $sgo) { + $organisation_id = $this->Organisations->captureOrg($sgo); + $orgs[] = $this->SharingGroupOrgs->get($organisation_id); + } + $this->SharingGroupOrgs->link($sharingGroup, $orgs); + } } From 62c43e854f144b2ed018cb8fb4613bd1d1f5ac13 Mon Sep 17 00:00:00 2001 From: iglocska Date: Tue, 1 Jun 2021 07:49:59 +0200 Subject: [PATCH 14/14] new: [queryLocalTools] endpoint added to broods - show a list of local tools exposed by a remote cerebrate --- src/Model/Table/BroodsTable.php | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/Model/Table/BroodsTable.php b/src/Model/Table/BroodsTable.php index 0f43c43..98a62ae 100644 --- a/src/Model/Table/BroodsTable.php +++ b/src/Model/Table/BroodsTable.php @@ -171,4 +171,25 @@ class BroodsTable extends AppTable return false; } } + + public function queryLocalTools($brood_id) + { + $query = $this->find(); + $brood = $query->where(['id' => $brood_id])->first(); + if (empty($brood)) { + throw new NotFoundException(__('Brood not found')); + } + $http = new Client(); + $response = $http->get($brood['url'] . '/localTools/exposedTools' , [], [ + 'headers' => [ + 'Authorization' => $brood['authkey'] + ], + 'type' => 'json' + ]); + if ($response->isOk()) { + return $response->getJson(); + } else { + return false; + } + } }