wip: initial connectors
parent
dd30519e73
commit
2d4727770c
|
@ -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`
|
||||
--
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'));
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -0,0 +1,122 @@
|
|||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Controller\AppController;
|
||||
use Cake\Utility\Hash;
|
||||
use Cake\Utility\Text;
|
||||
use \Cake\Database\Expression\QueryExpression;
|
||||
|
||||
class LocalToolsController extends AppController
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$data = $this->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();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
namespace App\Controller\Open;
|
||||
|
||||
use App\Controller\AppController;
|
||||
use Cake\Utility\Hash;
|
||||
use Cake\Utility\Text;
|
||||
use \Cake\Database\Expression\QueryExpression;
|
||||
use Cake\Http\Exception\NotFoundException;
|
||||
use Cake\Http\Exception\MethodNotAllowedException;
|
||||
use Cake\Http\Exception\ForbiddenException;
|
||||
use Cake\Event\EventInterface;
|
||||
|
||||
class IndividualsController extends AppController
|
||||
{
|
||||
public function beforeFilter(EventInterface $event)
|
||||
{
|
||||
parent::beforeFilter($event);
|
||||
$this->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');
|
||||
}
|
||||
}
|
|
@ -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'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
namespace CommonConnectorTools;
|
||||
|
||||
class CommonConnectorTools
|
||||
{
|
||||
public $description = '';
|
||||
public $name = '';
|
||||
public $exposedFunctions = [
|
||||
'diagnostics'
|
||||
];
|
||||
public $version = '???';
|
||||
|
||||
public function addExposedFunction(string $functionName): void
|
||||
{
|
||||
$this->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;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -0,0 +1,127 @@
|
|||
<?php
|
||||
namespace MispConnector;
|
||||
require_once(ROOT . '/src/Lib/default/local_tool_connectors/CommonConnectorTools.php');
|
||||
use CommonConnectorTools\CommonConnectorTools;
|
||||
use Cake\Http\Client;
|
||||
|
||||
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'
|
||||
];
|
||||
public $version = '0.1';
|
||||
|
||||
public function addExposedFunction(string $functionName): void
|
||||
{
|
||||
$this->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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -0,0 +1,172 @@
|
|||
<?php
|
||||
namespace App\Model\Behavior;
|
||||
|
||||
use Cake\ORM\Behavior;
|
||||
use Cake\Event\EventInterface;
|
||||
use Cake\Datasource\EntityInterface;
|
||||
use ArrayObject;
|
||||
use Cake\Utility\Text;
|
||||
use Cake\Http\Client;
|
||||
|
||||
class SyncTool extends Behavior
|
||||
{
|
||||
// take a server as parameter and return a HttpSocket object using the ssl options defined in the server settings
|
||||
public function setupHttpSocket($server = null, $timeout = false)
|
||||
{
|
||||
$params = array();
|
||||
if (!empty($server)) {
|
||||
if ($server['cert_file']) {
|
||||
$params['ssl_cafile'] = APP . "files" . DS . "certs" . DS . $server['id'] . '.pem';
|
||||
}
|
||||
if ($server['client_cert_file']) {
|
||||
$params['ssl_local_cert'] = APP . "files" . DS . "certs" . DS . $server['id'] . '_client.pem';
|
||||
}
|
||||
if ($server['self_signed']) {
|
||||
$params['ssl_allow_self_signed'] = true;
|
||||
$params['ssl_verify_peer_name'] = false;
|
||||
if (!isset($server['cert_file'])) {
|
||||
$params['ssl_verify_peer'] = false;
|
||||
}
|
||||
}
|
||||
if (!empty($server['skip_proxy'])) {
|
||||
$params['skip_proxy'] = 1;
|
||||
}
|
||||
if (!empty($timeout)) {
|
||||
$params['timeout'] = $timeout;
|
||||
}
|
||||
}
|
||||
return $this->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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
namespace App\Model\Entity;
|
||||
|
||||
use App\Model\Entity\AppModel;
|
||||
use Cake\ORM\Entity;
|
||||
|
||||
class LocalTool extends AppModel
|
||||
{
|
||||
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
<?php
|
||||
|
||||
namespace App\Model\Table;
|
||||
|
||||
use App\Model\Table\AppTable;
|
||||
use Cake\ORM\Table;
|
||||
use Cake\Validation\Validator;
|
||||
use Migrations\Migrations;
|
||||
use Cake\Filesystem\Folder;
|
||||
use Cake\Filesystem\File;
|
||||
|
||||
class LocalToolsTable extends AppTable
|
||||
{
|
||||
|
||||
const HEALTH_CODES = [
|
||||
0 => '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']
|
||||
];
|
||||
}
|
||||
}
|
|
@ -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']);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
namespace App\View\Helper;
|
||||
|
||||
use Cake\View\Helper;
|
||||
|
||||
class PrettyPrintHelper extends Helper
|
||||
{
|
||||
|
||||
public function ppArray($array, $depth = 0)
|
||||
{
|
||||
$text = '';
|
||||
foreach ($array as $key => $value) {
|
||||
if (is_array($value)) {
|
||||
$value = $this->ppArray($value, $depth+1);
|
||||
} else {
|
||||
$value = h($value);
|
||||
}
|
||||
$text .= sprintf(
|
||||
'<div><span class="text-primary">%s</span>: %s</div>',
|
||||
h($key),
|
||||
$value
|
||||
);
|
||||
}
|
||||
return $text;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
?>
|
|
@ -0,0 +1,4 @@
|
|||
<?php
|
||||
echo $this->element('genericElements/IndexTable/index_table', $index_table);
|
||||
echo '</div>';
|
||||
?>
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
echo $this->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')
|
||||
]
|
||||
]
|
||||
]);
|
||||
?>
|
||||
</div>
|
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
echo $this->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 '</div>';
|
||||
?>
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
echo $this->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 '</div>';
|
||||
?>
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
echo $this->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')
|
||||
]
|
||||
]
|
||||
]
|
||||
);
|
|
@ -0,0 +1,4 @@
|
|||
<?php
|
||||
$random = Cake\Utility\Security::randomString(8);
|
||||
$type = empty($data['type']) ? 'generic' : $data['type'];
|
||||
echo $this->element('genericElements/Configuration/Fields/' . $type . 'Field.php', ['data' => $field]);
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
/*
|
||||
* Generic configuration panel builder
|
||||
*
|
||||
* Simply pass a JSON with the following keys set:
|
||||
* - title: The title for the configuration interface
|
||||
* - diagnostic-view (optional): url of the diagnostic page to be shown and refreshed on any setting change
|
||||
* - fields: an array with each element generating an input field
|
||||
* - field: the unique field name for the context
|
||||
* - description: a brief description of the field
|
||||
* - type (optional): the type of form element to use
|
||||
* - options (optional): for select style elements
|
||||
* - validation (optional): regex to validate input
|
||||
* - requirements (optional): boolean, if false is passed the field is skipped
|
||||
*/
|
||||
$diagnostics = '';
|
||||
if (!empty($data['diagnostics'])) {
|
||||
$diagnostics = '<div data-url="' . h($data['diagnostics']) . '"></div>';
|
||||
}
|
||||
$fields = '';
|
||||
if (!empty($data['fields'])) {
|
||||
foreach ($data['fields'] as $field) {
|
||||
$fields .= $this->element('genericElements/Configuration/Fields/scaffold.php', ['data' => $field]);
|
||||
}
|
||||
}
|
||||
echo sprintf(
|
||||
'<div class="%s"><h2>%s</h2><div>%s</div><div><%s/div></div>',
|
||||
empty($ajax) ? 'col-8' : '',
|
||||
h($data['title']),
|
||||
$diagnostics,
|
||||
$fields
|
||||
);
|
||||
?>
|
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
$data = $this->Hash->extract($row, $field['data_path']);;
|
||||
if (!empty($field['isJson'])) {
|
||||
$data = json_decode($data[0], true);
|
||||
}
|
||||
echo $this->PrettyPrint->ppArray($data);
|
||||
?>
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
$data = $this->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(
|
||||
'<a href="%s/%s">%s</a>',
|
||||
$baseurl,
|
||||
$healthElement['url'],
|
||||
$name
|
||||
);
|
||||
}
|
||||
$lines .= sprintf(
|
||||
'<p><span class="text-%s"><i class="fas fa-circle" ></i></span> %s: %s</p>',
|
||||
$status_colours[$healthElement['health']],
|
||||
$name,
|
||||
h($healthElement['message'])
|
||||
);
|
||||
}
|
||||
echo $lines;
|
||||
?>
|
|
@ -53,7 +53,7 @@ foreach ($data['menu'] as $name => $menuElement) {
|
|||
}
|
||||
}
|
||||
$logoutButton = sprintf(
|
||||
'<span class="nav-item"><a href="%susers/logout" class="nav-link">%s</a></span>',
|
||||
'<span class="nav-item"><a href="%s/users/logout" class="nav-link">%s</a></span>',
|
||||
$baseurl,
|
||||
__('Logout')
|
||||
);
|
||||
|
|
Loading…
Reference in New Issue