diff --git a/config/Migrations/20210311110021_InboxSystem.php b/config/Migrations/20210311110021_InboxSystem.php
index 65f143a..8357ff7 100644
--- a/config/Migrations/20210311110021_InboxSystem.php
+++ b/config/Migrations/20210311110021_InboxSystem.php
@@ -56,11 +56,6 @@ class InboxSystem extends AbstractMigration
'null' => false,
'limit' => 191,
])
- // ->addColumn('ip', 'string', [
- // 'limit' => 191,
- // 'default' => null,
- // 'null' => true,
- // ])
->addColumn('user_id', 'integer', [
'default' => null,
'null' => true,
@@ -92,7 +87,6 @@ class InboxSystem extends AbstractMigration
->addIndex('action')
->addIndex('title')
->addIndex('origin')
- // ->addIndex('ip')
->addIndex('created')
->addIndex('user_id');
diff --git a/config/Migrations/20210612140828_RemoteToolConnections.php b/config/Migrations/20210612140828_RemoteToolConnections.php
new file mode 100644
index 0000000..b75e112
--- /dev/null
+++ b/config/Migrations/20210612140828_RemoteToolConnections.php
@@ -0,0 +1,84 @@
+table('remote_tool_connections', [
+ 'signed' => false,
+ 'collation' => 'utf8mb4_unicode_ci',
+ ]);
+ $table
+ ->addColumn('id', 'integer', [
+ 'autoIncrement' => true,
+ 'limit' => 10,
+ 'signed' => false,
+ ])
+ ->addPrimaryKey('id')
+ ->addColumn('local_tool_id', 'integer', [
+ 'null' => false,
+ 'signed' => false,
+ 'length' => 10,
+ ])
+ ->addColumn('remote_tool_id', 'integer', [
+ 'null' => false,
+ 'signed' => false,
+ 'length' => 10,
+ ])
+ ->addColumn('remote_tool_name', 'string', [
+ 'null' => false,
+ 'limit' => 191,
+ ])
+ ->addColumn('brood_id', 'integer', [
+ 'null' => false,
+ 'signed' => false,
+ 'length' => 10,
+ ])
+ ->addColumn('name', 'string', [
+ 'null' => true,
+ 'limit' => 191,
+ ])
+ ->addColumn('settings', 'text', [
+ 'null' => true,
+ 'limit' => MysqlAdapter::TEXT_LONG
+ ])
+ ->addColumn('status', 'string', [
+ 'null' => true,
+ 'limit' => 32,
+ 'encoding' => 'ascii',
+ ])
+ ->addColumn('created', 'datetime', [
+ 'default' => null,
+ 'null' => false,
+ ])
+ ->addColumn('modified', 'datetime', [
+ 'default' => null,
+ 'null' => false,
+ ]);
+
+ $table->addForeignKey('local_tool_id', 'local_tools', 'id', ['delete'=> 'CASCADE', 'update'=> 'CASCADE']);
+
+ $table->addIndex('remote_tool_id')
+ ->addIndex('remote_tool_name')
+ ->addIndex('status')
+ ->addIndex('name');
+
+ $table->create();
+ }
+}
+
diff --git a/config/Migrations/20210618102027_OutboxSystem.php b/config/Migrations/20210618102027_OutboxSystem.php
new file mode 100644
index 0000000..2b74b71
--- /dev/null
+++ b/config/Migrations/20210618102027_OutboxSystem.php
@@ -0,0 +1,90 @@
+table('outbox', [
+ 'signed' => false,
+ 'collation' => 'utf8mb4_unicode_ci',
+ ]);
+ $table
+ ->addColumn('id', 'integer', [
+ 'autoIncrement' => true,
+ 'limit' => 10,
+ 'signed' => false,
+ ])
+ ->addPrimaryKey('id')
+ ->addColumn('uuid', 'uuid', [
+ 'default' => null,
+ 'null' => false,
+ ])
+ ->addColumn('scope', 'string', [
+ 'default' => null,
+ 'null' => false,
+ 'limit' => 191,
+ 'comment' => 'The to model linked with the message',
+ ])
+ ->addColumn('action', 'string', [
+ 'default' => null,
+ 'null' => false,
+ 'limit' => 191,
+ 'comment' => 'The action linked with the message',
+ ])
+ ->addColumn('title', 'string', [
+ 'default' => null,
+ 'null' => false,
+ 'limit' => 191,
+ ])
+ ->addColumn('user_id', 'integer', [
+ 'default' => null,
+ 'null' => true,
+ 'signed' => false,
+ 'length' => 10,
+ ])
+ ->addColumn('comment', 'text', [
+ 'default' => null,
+ 'null' => true,
+ ])
+ ->addColumn('description', 'text', [
+ 'default' => null,
+ 'null' => true,
+ ])
+ ->addColumn('data', 'text', [
+ 'default' => null,
+ 'null' => true,
+ 'limit' => MysqlAdapter::TEXT_LONG
+ ])
+ ->addColumn('created', 'datetime', [
+ 'default' => null,
+ 'null' => false,
+ ]);
+
+ $table->addForeignKey('user_id', 'users', 'id', ['delete'=> 'CASCADE', 'update'=> 'CASCADE']);
+
+ $table->addIndex(['uuid'], ['unique' => true])
+ ->addIndex('scope')
+ ->addIndex('action')
+ ->addIndex('title')
+ ->addIndex('created')
+ ->addIndex('user_id');
+
+ $table->create();
+ }
+}
+
diff --git a/config/Migrations/20210628104235_RolesPermSync.php b/config/Migrations/20210628104235_RolesPermSync.php
new file mode 100644
index 0000000..04c36ae
--- /dev/null
+++ b/config/Migrations/20210628104235_RolesPermSync.php
@@ -0,0 +1,18 @@
+table('roles')
+ ->addColumn('perm_sync', 'boolean', [
+ 'default' => 0,
+ 'null' => false,
+ ])
+ ->update();
+ }
+}
\ No newline at end of file
diff --git a/libraries/default/InboxProcessors/BroodInboxProcessor.php b/libraries/default/InboxProcessors/BroodInboxProcessor.php
new file mode 100644
index 0000000..c7d655f
--- /dev/null
+++ b/libraries/default/InboxProcessors/BroodInboxProcessor.php
@@ -0,0 +1,65 @@
+description = __('Handle tool interconnection request from other cerebrate instance');
+ $this->Broods = TableRegistry::getTableLocator()->get('Broods');
+ }
+
+ protected function addValidatorRules($validator)
+ {
+ return $validator;
+ }
+
+ public function create($requestData) {
+ $this->validateRequestData($requestData);
+ $requestData['title'] = __('Cerebrate instance {0} requested interconnection for tool {1}', 'Insert brood name', 'Insert tool name');
+ return parent::create($requestData);
+ }
+
+ public function process($id, $requestData, $inboxRequest)
+ {
+ $connectionSuccessfull = false;
+ $interConnectionResult = [];
+ if ($connectionSuccessfull) {
+ $this->discard($id, $requestData);
+ }
+ return $this->genActionResult(
+ $interConnectionResult,
+ $connectionSuccessfull,
+ $connectionSuccessfull ? __('Interconnection for `{0}` created', 'Insert tool name') : __('Could interconnect tool `{0}`.', 'Insert tool name'),
+ []
+ );
+ }
+
+ public function discard($id, $requestData)
+ {
+ return parent::discard($id, $requestData);
+ }
+}
diff --git a/libraries/default/RequestProcessors/GenericRequestProcessor.php b/libraries/default/InboxProcessors/GenericInboxProcessor.php
similarity index 85%
rename from libraries/default/RequestProcessors/GenericRequestProcessor.php
rename to libraries/default/InboxProcessors/GenericInboxProcessor.php
index b5d2300..1ca84ea 100644
--- a/libraries/default/RequestProcessors/GenericRequestProcessor.php
+++ b/libraries/default/InboxProcessors/GenericInboxProcessor.php
@@ -5,26 +5,31 @@ use Cake\Utility\Inflector;
use Cake\Validation\Validator;
use Cake\View\ViewBuilder;
-interface GenericProcessorActionI
+interface GenericInboxProcessorActionI
{
public function create($requestData);
- public function process($requestID, $serverRequest);
+ public function process($requestID, $serverRequest, $inboxRequest);
public function discard($requestID ,$requestData);
}
-class GenericRequestProcessor
+class GenericInboxProcessor
{
protected $Inbox;
protected $registeredActions = [];
protected $validator;
- private $processingTemplate = '/genericTemplates/confirm';
- private $processingTemplatesDirectory = ROOT . '/libraries/default/RequestProcessors/templates';
+ protected $processingTemplate = '/genericTemplates/confirm';
+ protected $processingTemplatesDirectory = ROOT . '/libraries/default/InboxProcessors/templates';
public function __construct($registerActions=false) {
$this->Inbox = TableRegistry::getTableLocator()->get('Inbox');
if ($registerActions) {
$this->registerActionInProcessor();
}
+ $this->assignProcessingTemplate();
+ }
+
+ private function assignProcessingTemplate()
+ {
$processingTemplatePath = $this->getProcessingTemplatePath();
$file = new File($this->processingTemplatesDirectory . DS . $processingTemplatePath);
if ($file->exists()) {
@@ -33,6 +38,10 @@ class GenericRequestProcessor
$file->close();
}
+ protected function updateProcessingTemplate($request)
+ {
+ }
+
public function getRegisteredActions()
{
return $this->registeredActions;
@@ -41,14 +50,16 @@ class GenericRequestProcessor
{
return $this->scope;
}
-
- private function getProcessingTemplatePath()
+ public function getDescription()
+ {
+ return $this->description ?? '';
+ }
+
+ protected function getProcessingTemplatePath()
{
- $class = str_replace('RequestProcessor', '', get_parent_class($this));
- $action = strtolower(str_replace('Processor', '', get_class($this)));
return sprintf('%s/%s.php',
- $class,
- $action
+ $this->scope,
+ $this->action
);
}
@@ -57,15 +68,17 @@ class GenericRequestProcessor
return $this->processingTemplate;
}
- public function render($request=[])
+ public function render($request=[], Cake\Http\ServerRequest $serverRequest)
{
- $processingTemplate = $this->getProcessingTemplate();
$viewVariables = $this->getViewVariables($request);
+ $this->updateProcessingTemplate($request);
+ $processingTemplate = $this->getProcessingTemplate();
$builder = new ViewBuilder();
$builder->disableAutoLayout()
->setClassName('Monad')
->setTemplate($processingTemplate);
$view = $builder->build($viewVariables);
+ $view->setRequest($serverRequest);
return $view->render();
}
@@ -141,7 +154,7 @@ class GenericRequestProcessor
if ($controller->ParamHandler->isRest()) {
$response = $controller->RestResponse->viewData($processResult, 'json');
} else if ($controller->ParamHandler->isAjax()) {
- $response = $controller->RestResponse->ajaxSuccessResponse('RequestProcessor', "{$scope}.{$action}", $processResult['data'], $message);
+ $response = $controller->RestResponse->ajaxSuccessResponse('InboxProcessor', "{$scope}.{$action}", $processResult['data'], $message);
} else {
$controller->Flash->success($message);
if (!is_null($redirect)) {
@@ -155,7 +168,7 @@ class GenericRequestProcessor
if ($controller->ParamHandler->isRest()) {
$response = $controller->RestResponse->viewData($processResult, 'json');
} else if ($controller->ParamHandler->isAjax()) {
- $response = $controller->RestResponse->ajaxFailResponse('RequestProcessor', "{$scope}.{$action}", $processResult['data'], $message, $processResult['errors']);
+ $response = $controller->RestResponse->ajaxFailResponse('InboxProcessor', "{$scope}.{$action}", $processResult['data'], $message, $processResult['errors']);
} else {
$controller->Flash->error($message);
if (!is_null($redirect)) {
@@ -180,7 +193,7 @@ class GenericRequestProcessor
$requestData['action'] = $this->action;
$requestData['description'] = $this->description;
$request = $this->generateRequest($requestData);
- $savedRequest = $this->Inbox->save($request);
+ $savedRequest = $this->Inbox->createEntry($request);
return $this->genActionResult(
$savedRequest,
$savedRequest !== false,
diff --git a/libraries/default/InboxProcessors/LocalToolInboxProcessor.php b/libraries/default/InboxProcessors/LocalToolInboxProcessor.php
new file mode 100644
index 0000000..8852f9e
--- /dev/null
+++ b/libraries/default/InboxProcessors/LocalToolInboxProcessor.php
@@ -0,0 +1,418 @@
+Broods = TableRegistry::getTableLocator()->get('Broods');
+ $this->LocalTools = TableRegistry::getTableLocator()->get('LocalTools');
+ }
+
+ public function create($requestData)
+ {
+ return parent::create($requestData);
+ }
+
+ protected function updateProcessingTemplate($request)
+ {
+ $connectorName = $request->connector['connector'];
+ $processingTemplatePath = sprintf('%s/%s/%s.php', $this->scope, $connectorName, $this->action);
+ $file = new File($this->processingTemplatesDirectory . DS . $processingTemplatePath);
+ if ($file->exists()) {
+ $this->processingTemplate = str_replace('.php', '', $processingTemplatePath);
+ }
+ $file->close();
+ }
+
+ protected function validateConnectorName($requestData)
+ {
+ if (empty($requestData['data']['connectorName'])) {
+ throw new NotFoundException('Error while validating request data. Connector name is missing.');
+ }
+ $connector = $this->getConnectorFromClassname($requestData['data']['connectorName']);
+ if (is_null($connector)) {
+ throw new NotFoundException(__('Error while validating request data. Unkown connector `{0}`', $requestData['data']['connectorName']));
+ }
+ }
+
+ protected function getIssuerBrood($request)
+ {
+ $brood = $this->Broods->find()
+ ->where(['url' => $request['origin']])
+ ->first();
+ return $brood;
+ }
+
+ protected function getConnection($requestData)
+ {
+ $local_tool_id = $requestData['remote_tool_id']; // local_tool_id is actually the remote_tool_id for the sender
+ $connection = $this->LocalTools->find()->where(['id' => $local_tool_id])->first();
+ return $connection;
+ }
+
+ protected function filterAlignmentsForBrood($individual, $brood)
+ {
+ foreach ($individual->alignments as $i => $alignment) {
+ if ($alignment->organisation_id != $brood->organisation_id) {
+ unset($individual->alignments[$i]);
+ }
+ }
+ return $individual;
+ }
+
+ protected function getConnector($request)
+ {
+ try {
+ $connectorClasses = $this->LocalTools->getConnectors($request->local_tool_connector_name);
+ if (!empty($connectorClasses)) {
+ $connector = array_values($connectorClasses)[0];
+ }
+ } catch (NotFoundException $e) {
+ $connector = null;
+ }
+ return $connector;
+ }
+
+ protected function getConnectorMeta($request)
+ {
+ try {
+ $className = $request->local_tool_connector_name;
+ $connector = $this->getConnectorFromClassname($className);
+ $connectorMeta = $this->LocalTools->extractMeta([$className => $connector])[0];
+ } catch (NotFoundException $e) {
+ $connectorMeta = [];
+ }
+ return $connectorMeta;
+ }
+
+ protected function getConnectorFromClassname($className)
+ {
+ try {
+ $connectorClasses = $this->LocalTools->getConnectors($className);
+ if (!empty($connectorClasses)) {
+ $connector = array_values($connectorClasses)[0];
+ }
+ } catch (NotFoundException $e) {
+ $connector = null;
+ }
+ return $connector;
+ }
+
+ protected function getConnectorMetaFromClassname($className)
+ {
+ try {
+ $connector = $this->getConnectorFromClassname($className);
+ $connectorMeta = $this->LocalTools->extractMeta([$className => $connector])[0];
+ } catch (NotFoundException $e) {
+ $connectorMeta = [];
+ }
+ return $connectorMeta;
+ }
+
+ protected function attachRequestAssociatedData($request)
+ {
+ $request->brood = $this->getIssuerBrood($request);
+ $request->connector = $this->getConnectorMeta($request);
+ $request->individual = $request->user->individual;
+ $request->individual = $this->filterAlignmentsForBrood($request->individual, $request->brood);
+ return $request;
+ }
+
+ protected function genBroodParam($remoteCerebrate, $connection, $connector, $requestData)
+ {
+ $local_tool_id = $requestData['remote_tool_id']; // local_tool_id is actually the remote_tool_id for the sender
+ $remote_tool_id = $requestData['local_tool_id']; // remote_tool_id is actually the local_tool_id for the sender
+ $remote_org = $this->Broods->Organisations->find()->where(['id' => $remoteCerebrate->organisation_id])->first();
+ return [
+ 'remote_tool' => [
+ 'id' => $remote_tool_id,
+ 'connector' => $connector->connectorName,
+ 'name' => $requestData['tool_name'],
+ ],
+ 'remote_org' => $remote_org,
+ 'remote_tool_data' => $requestData,
+ 'remote_cerebrate' => $remoteCerebrate,
+ 'connection' => $connection,
+ 'connector' => [$connector->connectorName => $connector],
+ ];
+ }
+
+ protected function addBaseValidatorRules($validator)
+ {
+ return $validator
+ ->requirePresence('connectorName')
+ ->notEmpty('connectorName', 'The connector name must be provided')
+ ->requirePresence('cerebrateURL')
+ ->notEmpty('cerebrateURL', 'A url must be provided')
+ ->requirePresence('local_tool_id')
+ ->numeric('local_tool_id', 'A local_tool_id must be provided')
+ ->requirePresence('remote_tool_id')
+ ->numeric('remote_tool_id', 'A remote_tool_id must be provided');
+ // ->add('url', 'validFormat', [
+ // 'rule' => 'url',
+ // 'message' => 'URL must be valid'
+ // ]);
+ }
+}
+
+class IncomingConnectionRequestProcessor extends LocalToolInboxProcessor implements GenericInboxProcessorActionI {
+ public $action = 'IncomingConnectionRequest';
+ protected $description;
+
+ public function __construct() {
+ parent::__construct();
+ $this->description = __('Handle Phase I of inter-connection when another cerebrate instance performs the request.');
+ }
+
+ protected function addValidatorRules($validator)
+ {
+ return $this->addBaseValidatorRules($validator);
+ }
+
+ public function create($requestData) {
+ $this->validateConnectorName($requestData);
+ $this->validateRequestData($requestData);
+ $connectorMeta = $this->getConnectorMetaFromClassname($requestData['data']['connectorName']);
+ $requestData['title'] = __('Request for {0} Inter-connection', $connectorMeta['name']);
+ return parent::create($requestData);
+ }
+
+ public function getViewVariables($request)
+ {
+ $request = $this->attachRequestAssociatedData($request);
+ return [
+ 'request' => $request,
+ 'progressStep' => 0,
+ ];
+ }
+
+ public function process($id, $requestData, $inboxRequest)
+ {
+ /**
+ * /!\ Should how should sent message be? be fire and forget? Only for delined?
+ */
+ $interConnectionResult = [];
+ $remoteCerebrate = $this->getIssuerBrood($inboxRequest);
+ $connector = $this->getConnector($inboxRequest);
+ if (!empty($requestData['is_discard'])) { // -> declined
+ $connectorResult = $this->declineConnection($connector, $remoteCerebrate, $inboxRequest['data']); // Fire-and-forget?
+ $connectionSuccessfull = !empty($connectorResult['success']);
+ $resultTitle = __('Could not sent declined message to `{0}`\'s for {1}', $inboxRequest['origin'], $inboxRequest['local_tool_name']);
+ $errors = [];
+ if ($connectionSuccessfull) {
+ $resultTitle = __('Declined message successfully sent to `{0}`\'s for {1}', $inboxRequest['origin'], $inboxRequest['local_tool_name']);
+ $this->discard($id, $inboxRequest);
+ }
+ } else {
+ $errors = [];
+ $connectorResult = [];
+ $thrownErrorMessage = '';
+ try {
+ $connectorResult = $this->acceptConnection($connector, $remoteCerebrate, $inboxRequest['data']);
+ $connectionSuccessfull = !empty($connectorResult['success']);
+ } catch (\Throwable $th) {
+ $connectionSuccessfull = false;
+ $thrownErrorMessage = $th->getMessage();
+ }
+ $resultTitle = $connectorResult['message'] ?? __('Could not inter-connect `{0}`\'s {1}', $inboxRequest['origin'], $inboxRequest['local_tool_name']);
+ $errors = $connectorResult['errors'] ?? $thrownErrorMessage;
+ if ($connectionSuccessfull) {
+ $resultTitle = __('Interconnection for `{0}`\'s {1} created', $inboxRequest['origin'], $inboxRequest['local_tool_name']);
+ }
+ if ($connectionSuccessfull || !empty($connectorResult['placed_in_outbox'])) {
+ $this->discard($id, $inboxRequest);
+ }
+ }
+ return $this->genActionResult(
+ $connectorResult,
+ $connectionSuccessfull,
+ $resultTitle,
+ $errors
+ );
+ }
+
+ public function discard($id, $requestData)
+ {
+ return parent::discard($id, $requestData);
+ }
+
+ protected function acceptConnection($connector, $remoteCerebrate, $requestData)
+ {
+ $connection = $this->getConnection($requestData);
+ $params = $this->genBroodParam($remoteCerebrate, $connection, $connector, $requestData);
+ $connectorResult = $connector->acceptConnectionWrapper($params);
+ $response = $this->sendAcceptedRequestToRemote($params, $connectorResult);
+ return $response;
+ }
+
+ protected function declineConnection($connector, $remoteCerebrate, $requestData)
+ {
+ $connection = $this->getConnection($requestData);
+ $params = $this->genBroodParam($remoteCerebrate, $connection, $connector, $requestData);
+ $connectorResult = $connector->declineConnectionWrapper($params);
+ $response = $this->sendDeclinedRequestToRemote($params, $connectorResult);
+ return $response;
+ }
+
+ protected function sendAcceptedRequestToRemote($params, $connectorResult)
+ {
+ $response = $this->Broods->sendLocalToolAcceptedRequest($params, $connectorResult);
+ return $response;
+ }
+
+ protected function sendDeclinedRequestToRemote($remoteCerebrate, $connectorResult)
+ {
+ $response = $this->Broods->sendLocalToolDeclinedRequest($params, $connectorResult);
+ return $response;
+ }
+}
+
+class AcceptedRequestProcessor extends LocalToolInboxProcessor implements GenericInboxProcessorActionI {
+ public $action = 'AcceptedRequest';
+ protected $description;
+
+ public function __construct() {
+ parent::__construct();
+ $this->description = __('Handle Phase II of inter-connection when initial request has been accepted by the remote cerebrate.');
+ }
+
+ protected function addValidatorRules($validator)
+ {
+ return $this->addBaseValidatorRules($validator);
+ }
+
+ public function create($requestData) {
+ $this->validateConnectorName($requestData);
+ $this->validateRequestData($requestData);
+ $connectorMeta = $this->getConnectorMetaFromClassname($requestData['data']['connectorName']);
+ $requestData['title'] = __('Inter-connection for {0} has been accepted', $connectorMeta['name']);
+ return parent::create($requestData);
+ }
+
+ public function getViewVariables($request)
+ {
+ $request = $this->attachRequestAssociatedData($request);
+ return [
+ 'request' => $request,
+ 'progressStep' => 1,
+ ];
+ }
+
+ public function process($id, $requestData, $inboxRequest)
+ {
+ $connector = $this->getConnector($inboxRequest);
+ $remoteCerebrate = $this->getIssuerBrood($inboxRequest);
+
+ $errors = [];
+ $connectorResult = [];
+ $thrownErrorMessage = '';
+ try {
+ $connectorResult = $this->finaliseConnection($connector, $remoteCerebrate, $inboxRequest['data']);
+ $connectionSuccessfull = !empty($connectorResult['success']);
+ } catch (\Throwable $th) {
+ $connectionSuccessfull = false;
+ $errors = $th->getMessage();
+ }
+ $resultTitle = __('Could not finalise inter-connection for `{0}`\'s {1}', $inboxRequest['origin'], $inboxRequest['local_tool_name']);
+ $errors = $connectorResult['errors'] ?? $thrownErrorMessage;
+ if ($connectionSuccessfull) {
+ $resultTitle = __('Interconnection for `{0}`\'s {1} finalised', $inboxRequest['origin'], $inboxRequest['local_tool_name']);
+ $this->discard($id, $requestData);
+ }
+ return $this->genActionResult(
+ $connectorResult,
+ $connectionSuccessfull,
+ $resultTitle,
+ $errors
+ );
+ }
+
+ public function discard($id, $requestData)
+ {
+ return parent::discard($id, $requestData);
+ }
+
+ protected function finaliseConnection($connector, $remoteCerebrate, $requestData)
+ {
+ $connection = $this->getConnection($requestData);
+ $params = $this->genBroodParam($remoteCerebrate, $connection, $connector, $requestData);
+ $connectorResult = $connector->finaliseConnectionWrapper($params);
+ return [
+ 'success' => true
+ ];
+ }
+}
+
+class DeclinedRequestProcessor extends LocalToolInboxProcessor implements GenericInboxProcessorActionI {
+ public $action = 'DeclinedRequest';
+ protected $description;
+
+ public function __construct() {
+ parent::__construct();
+ $this->description = __('Handle Phase II of MISP inter-connection when initial request has been declined by the remote cerebrate.');
+ }
+
+ protected function addValidatorRules($validator)
+ {
+ return $this->addBaseValidatorRules($validator);
+ }
+
+ public function create($requestData) {
+ $this->validateConnectorName($requestData);
+ $this->validateRequestData($requestData);
+ $connectorMeta = $this->getConnectorMetaFromClassname($requestData['data']['connectorName']);
+ $requestData['title'] = __('Declined inter-connection for {0}', $connectorMeta['name']);
+ return parent::create($requestData);
+ }
+
+ public function getViewVariables($request)
+ {
+ $request = $this->attachRequestAssociatedData($request);
+ return [
+ 'request' => $request,
+ 'progressStep' => 1,
+ 'progressVariant' => 'danger',
+ 'steps' => [
+ 1 => ['icon' => 'times', 'text' => __('Request Declined'), 'confirmButton' => __('Clean-up')],
+ 2 => ['icon' => 'trash', 'text' => __('Clean-up')],
+ ]
+ ];
+ }
+
+ public function process($id, $requestData, $inboxRequest)
+ {
+ $connectionSuccessfull = false;
+ $interConnectionResult = [];
+ if ($connectionSuccessfull) {
+ $this->discard($id, $requestData);
+ }
+ return $this->genActionResult(
+ $interConnectionResult,
+ $connectionSuccessfull,
+ $connectionSuccessfull ? __('Interconnection for `{0}`\'s {1} finalised', $requestData['origin'], $requestData['local_tool_name']) : __('Could not inter-connect `{0}`\'s {1}', $requestData['origin'], $requestData['local_tool_name']),
+ []
+ );
+ }
+ public function discard($id, $requestData)
+ {
+ return parent::discard($id, $requestData);
+ }
+}
diff --git a/libraries/default/RequestProcessors/ProposalRequestProcessor.php b/libraries/default/InboxProcessors/ProposalInboxProcessor.php
similarity index 82%
rename from libraries/default/RequestProcessors/ProposalRequestProcessor.php
rename to libraries/default/InboxProcessors/ProposalInboxProcessor.php
index 3421edf..01b6bb7 100644
--- a/libraries/default/RequestProcessors/ProposalRequestProcessor.php
+++ b/libraries/default/InboxProcessors/ProposalInboxProcessor.php
@@ -1,9 +1,9 @@
Users->Individuals->newEntity([
diff --git a/libraries/default/InboxProcessors/templates/LocalTool/GenericRequest.php b/libraries/default/InboxProcessors/templates/LocalTool/GenericRequest.php
new file mode 100644
index 0000000..15ea91b
--- /dev/null
+++ b/libraries/default/InboxProcessors/templates/LocalTool/GenericRequest.php
@@ -0,0 +1,147 @@
+ __('Request Sent'),
+ 'icon' => 'paper-plane',
+ 'title' => __(''),
+ 'confirmButton' => __('Accept Request'),
+ 'canDiscard' => true,
+ ],
+ [
+ 'text' => __('Request Accepted'),
+ 'icon' => 'check-square',
+ 'title' => __(''),
+ 'confirmButton' => __('Finalise Connection')
+ ],
+ [
+ 'text' => __('Connection Done'),
+ 'icon' => 'exchange-alt',
+ 'title' => __(''),
+ ]
+];
+
+$footerButtons = [];
+
+$progressVariant = !empty($progressVariant) ? $progressVariant : 'info';
+$finalSteps = array_replace($defaultSteps, $steps ?? []);
+$currentStep = $finalSteps[$progressStep];
+$progress = $this->Bootstrap->progressTimeline([
+ 'variant' => $progressVariant,
+ 'selected' => !empty($progressStep) ? $progressStep : 0,
+ 'steps' => $finalSteps,
+]);
+
+$footerButtons[] = [
+ 'clickFunction' => 'cancel',
+ 'variant' => 'secondary',
+ 'text' => __('Cancel'),
+];
+if (!empty($currentStep['canDiscard'])) {
+ $footerButtons[] = [
+ 'clickFunction' => 'discard',
+ 'variant' => 'danger',
+ 'text' => __('Decline Request'),
+ ];
+}
+$footerButtons[] = [
+ 'clickFunction' => 'accept',
+ 'text' => $currentStep['confirmButton'] ?? __('Submit'),
+];
+
+$table = $this->Bootstrap->table(['small' => true, 'bordered' => false, 'striped' => false, 'hover' => false], [
+ 'fields' => [
+ ['key' => 'created', 'label' => __('Date'), 'formatter' => function($value, $row) {
+ return $value->i18nFormat('yyyy-MM-dd HH:mm:ss');
+ }],
+ ['key' => 'connector', 'label' => __('Tool Name'), 'formatter' => function($connector, $row) {
+ return sprintf('%s',
+ $this->Url->build(['controller' => 'localTools', 'action' => 'viewConnector', $connector['name']]),
+ sprintf('%s (v%s)', h($connector['name']), h($connector['connector_version']))
+ );
+ }],
+ ['key' => 'brood', 'label' => __('Brood'), 'formatter' => function($brood, $row) {
+ return sprintf('%s',
+ $this->Url->build(['controller' => 'broods', 'action' => 'view', $brood['id']]),
+ h($brood['name'])
+ );
+ }],
+ ['key' => 'individual', 'label' => __('Individual'), 'formatter' => function($individual, $row) {
+ return sprintf('%s',
+ $this->Url->build(['controller' => 'users', 'action' => 'view', $individual['id']]),
+ h($individual['email'])
+ );
+ }],
+ ['key' => 'individual.alignments', 'label' => __('Alignment'), 'formatter' => function($alignments, $row) {
+ $html = '';
+ foreach ($alignments as $alignment) {
+ $html .= sprintf('
',
+ h($alignment['type']),
+ $this->Url->build(['controller' => 'users', 'action' => 'view', $alignment['organisation']['id']]),
+ h($alignment['organisation']['name'])
+ );
+ }
+ return $html;
+ }],
+ ],
+ 'items' => [$request->toArray()],
+]);
+$form = $this->element('genericElements/Form/genericForm', [
+ 'entity' => null,
+ 'ajax' => false,
+ 'raw' => true,
+ 'data' => [
+ 'model' => 'Inbox',
+ 'fields' => [],
+ 'submit' => [
+ 'action' => $this->request->getParam('action')
+ ]
+ ]
+]);
+$localToolHTML = $this->fetch('content', sprintf('%s
', $form));;
+
+$requestData = $this->Bootstrap->collapse(
+ [
+ 'title' => __('Inter-connection data'),
+ 'open' => true,
+ ],
+ sprintf('%s
', json_encode($request['data'], JSON_PRETTY_PRINT))
+);
+
+$bodyHtml = sprintf('%s',
+ $table,
+ $requestData,
+ $localToolHTML
+);
+
+echo $this->Bootstrap->modal([
+ 'title' => __('Interconnection Request for {0}', h($request->local_tool_connector_name)),
+ 'size' => 'xl',
+ 'type' => 'custom',
+ 'bodyHtml' => sprintf('%s
%s
',
+ $progress,
+ $bodyHtml
+ ),
+ 'footerButtons' => $footerButtons
+]);
+
+?>
+
+
\ No newline at end of file
diff --git a/libraries/default/InboxProcessors/templates/LocalTool/MispConnector/IncomingConnectionRequest.php b/libraries/default/InboxProcessors/templates/LocalTool/MispConnector/IncomingConnectionRequest.php
new file mode 100644
index 0000000..52bfd82
--- /dev/null
+++ b/libraries/default/InboxProcessors/templates/LocalTool/MispConnector/IncomingConnectionRequest.php
@@ -0,0 +1,21 @@
+extend('LocalTool/GenericRequest');
+$form = $this->element('genericElements/Form/genericForm', [
+ 'entity' => null,
+ 'ajax' => false,
+ 'raw' => true,
+ 'data' => [
+ 'model' => 'Inbox',
+ 'fields' => [
+ [
+ 'field' => 'is_discard',
+ 'type' => 'checkbox',
+ 'default' => false
+ ]
+ ],
+ 'submit' => [
+ 'action' => $this->request->getParam('action')
+ ]
+ ]
+]);
+echo sprintf('%s
', $form);
\ No newline at end of file
diff --git a/libraries/default/RequestProcessors/templates/User/registration.php b/libraries/default/InboxProcessors/templates/User/Registration.php
similarity index 100%
rename from libraries/default/RequestProcessors/templates/User/registration.php
rename to libraries/default/InboxProcessors/templates/User/Registration.php
diff --git a/libraries/default/OutboxProcessors/BroodsOutboxProcessor.php b/libraries/default/OutboxProcessors/BroodsOutboxProcessor.php
new file mode 100644
index 0000000..946e548
--- /dev/null
+++ b/libraries/default/OutboxProcessors/BroodsOutboxProcessor.php
@@ -0,0 +1,137 @@
+Broods->find()
+ ->where(['id' => $broodId])
+ ->first();
+ return $brood;
+ }
+
+ protected function getLocalTool($toolId)
+ {
+ $tool = $this->LocalTools->find()
+ ->where(['id' => $toolId])
+ ->first();
+ return $tool;
+ }
+
+ protected function getConnector($className)
+ {
+ try {
+ $connectorClasses = $this->LocalTools->getConnectors($className);
+ if (!empty($connectorClasses)) {
+ $connector = array_values($connectorClasses)[0];
+ }
+ } catch (NotFoundException $e) {
+ $connector = null;
+ }
+ return $connector;
+ }
+
+ protected function setRemoteToolConnectionStatus(Object $brood, Object $outboxRequest, String $status): void
+ {
+ $connector = $this->getConnector($outboxRequest->data['remote_tool']['connector']);
+ $connection = $this->getLocalTool($outboxRequest->data['local_tool_id']);
+ $connectorParams = [
+ 'connection' => $connection,
+ 'remote_tool' => $outboxRequest->data['remote_tool'],
+ 'remote_cerebrate' => $brood,
+ ];
+ $connector->remoteToolConnectionStatus($connectorParams, constant(get_class($connector) . '::' . $status));
+ }
+}
+
+class ResendFailedMessageProcessor extends BroodsOutboxProcessor implements GenericOutboxProcessorActionI {
+ public $action = 'ResendFailedMessage';
+ protected $description;
+
+ public function __construct() {
+ parent::__construct();
+ $this->description = __('Handle re-sending messages that failed to be received from other cerebrate instances.');
+ $this->Broods = TableRegistry::getTableLocator()->get('Broods');
+ $this->LocalTools = \Cake\ORM\TableRegistry::getTableLocator()->get('LocalTools');
+ }
+
+ protected function addValidatorRules($validator)
+ {
+ return $validator;
+ }
+
+ public function getViewVariables($request)
+ {
+ $request->brood = $this->getIssuerBrood($request['data']['brood_id']);
+ $request->individual = $request->user->individual;
+ $request->localTool = $this->getLocalTool($request['data']['local_tool_id']);
+ $request->remoteTool = $request['data']['remote_tool'];
+ return [
+ 'request' => $request,
+ ];
+ }
+
+ public function create($requestData) {
+ $this->validateRequestData($requestData);
+ $brood = $requestData['brood'];
+ $requestData['title'] = __('Issue while sending message to Cerebrate instance `{0}` using `{1}`', $brood->name, sprintf('%s.%s', $requestData['model'], $requestData['action']));
+ return parent::create($requestData);
+ }
+
+ public function process($id, $requestData, $outboxRequest)
+ {
+ $brood = $this->getIssuerBrood((int) $outboxRequest->data['brood_id']);
+ if (!empty($requestData['is_delete'])) { // -> declined
+ $success = true;
+ $messageSucess = __('Message successfully deleted');
+ $messageFail = '';
+ $this->setRemoteToolConnectionStatus($brood, $outboxRequest, 'STATE_CANCELLED');
+ } else {
+ $url = $outboxRequest->data['url'];
+ $dataSent = $outboxRequest->data['sent'];
+ $response = $this->Broods->sendRequest($brood, $url, true, $dataSent);
+ $jsonReply = $response->getJson();
+ $success = !empty($jsonReply['success']);
+ $messageSuccess = __('Message successfully sent to `{0}`', $brood->name);
+ $messageFail = __('Could not send message to `{0}`.', $brood->name);
+ if ($success) {
+ $this->setRemoteToolConnectionStatus($brood, $outboxRequest, $outboxRequest->data['next_connector_state']);
+ } else {
+ $this->setRemoteToolConnectionStatus($brood, $outboxRequest, 'STATE_SENDING_ERROR');
+ }
+ }
+ if ($success) {
+ $this->discard($id, $requestData);
+ }
+ return $this->genActionResult(
+ [],
+ $success,
+ $success ? $messageSuccess : $messageFail,
+ []
+ );
+ }
+
+ public function discard($id, $requestData)
+ {
+ return parent::discard($id, $requestData);
+ }
+}
diff --git a/libraries/default/OutboxProcessors/GenericOutboxProcessor.php b/libraries/default/OutboxProcessors/GenericOutboxProcessor.php
new file mode 100644
index 0000000..c230465
--- /dev/null
+++ b/libraries/default/OutboxProcessors/GenericOutboxProcessor.php
@@ -0,0 +1,218 @@
+Outbox = TableRegistry::getTableLocator()->get('Outbox');
+ if ($registerActions) {
+ $this->registerActionInProcessor();
+ }
+ $this->assignProcessingTemplate();
+ }
+
+ private function assignProcessingTemplate()
+ {
+ $processingTemplatePath = $this->getProcessingTemplatePath();
+ $file = new File($this->processingTemplatesDirectory . DS . $processingTemplatePath);
+ if ($file->exists()) {
+ $this->processingTemplate = str_replace('.php', '', $processingTemplatePath);
+ }
+ $file->close();
+ }
+
+ protected function updateProcessingTemplate($request)
+ {
+ }
+
+ public function getRegisteredActions()
+ {
+ return $this->registeredActions;
+ }
+ public function getScope()
+ {
+ return $this->scope;
+ }
+ public function getDescription()
+ {
+ return $this->description ?? '';
+ }
+
+ protected function getProcessingTemplatePath()
+ {
+ return sprintf('%s/%s.php',
+ $this->scope,
+ $this->action
+ );
+ }
+
+ public function getProcessingTemplate()
+ {
+ return $this->processingTemplate;
+ }
+
+ public function render($request=[], Cake\Http\ServerRequest $serverRequest)
+ {
+ $viewVariables = $this->getViewVariables($request);
+ $this->updateProcessingTemplate($request);
+ $processingTemplate = $this->getProcessingTemplate();
+ $builder = new ViewBuilder();
+ $builder->disableAutoLayout()
+ ->setClassName('Monad')
+ ->setTemplate($processingTemplate);
+ $view = $builder->build($viewVariables);
+ $view->setRequest($serverRequest);
+ return $view->render();
+ }
+
+ protected function generateRequest($requestData)
+ {
+ $request = $this->Outbox->newEmptyEntity();
+ $request = $this->Outbox->patchEntity($request, $requestData);
+ if ($request->getErrors()) {
+ throw new Exception(__('Could not create request.{0}Reason: {1}', PHP_EOL, json_encode($request->getErrors())), 1);
+ }
+ return $request;
+ }
+
+ protected function validateRequestData($requestData)
+ {
+ $errors = [];
+ if (!isset($requestData['data'])) {
+ $errors[] = __('No request data provided');
+ }
+ $validator = new Validator();
+ if (method_exists($this, 'addValidatorRules')) {
+ $validator = $this->addValidatorRules($validator);
+ $errors = $validator->validate($requestData['data']);
+ }
+ if (!empty($errors)) {
+ throw new Exception('Error while validating request data. ' . json_encode($errors), 1);
+ }
+ }
+
+ protected function registerActionInProcessor()
+ {
+ foreach ($this->registeredActions as $i => $action) {
+ $className = "{$action}Processor";
+ $reflection = new ReflectionClass($className);
+ if ($reflection->isAbstract() || $reflection->isInterface()) {
+ throw new Exception(__('Cannot create instance of %s, as it is abstract or is an interface'));
+ }
+ $this->{$action} = $reflection->newInstance();
+ }
+ }
+
+ protected function getViewVariablesConfirmModal($id, $title='', $question='', $actionName='')
+ {
+ return [
+ 'title' => !empty($title) ? $title : __('Process request {0}', $id),
+ 'question' => !empty($question) ? $question : __('Confirm request {0}', $id),
+ 'actionName' => !empty($actionName) ? $actionName : __('Confirm'),
+ 'path' => ['controller' => 'outbox', 'action' => 'process', $id]
+ ];
+ }
+
+ public function getViewVariables($request)
+ {
+ return $this->getViewVariablesConfirmModal($request->id, '', '', '');
+ }
+
+ protected function genActionResult($data, $success, $message, $errors=[])
+ {
+ return [
+ 'data' => $data,
+ 'success' => $success,
+ 'message' => $message,
+ 'errors' => $errors,
+ ];
+ }
+
+ public function genHTTPReply($controller, $processResult, $redirect=null)
+ {
+ $scope = $this->scope;
+ $action = $this->action;
+ if ($processResult['success']) {
+ $message = !empty($processResult['message']) ? $processResult['message'] : __('Request {0} successfully processed.', $id);
+ if ($controller->ParamHandler->isRest()) {
+ $response = $controller->RestResponse->viewData($processResult, 'json');
+ } else if ($controller->ParamHandler->isAjax()) {
+ $response = $controller->RestResponse->ajaxSuccessResponse('OutboxProcessor', "{$scope}.{$action}", $processResult['data'], $message);
+ } else {
+ $controller->Flash->success($message);
+ if (!is_null($redirect)) {
+ $response = $controller->redirect($redirect);
+ } else {
+ $response = $controller->redirect(['action' => 'index']);
+ }
+ }
+ } else {
+ $message = !empty($processResult['message']) ? $processResult['message'] : __('Request {0} could not be processed.', $id);
+ if ($controller->ParamHandler->isRest()) {
+ $response = $controller->RestResponse->viewData($processResult, 'json');
+ } else if ($controller->ParamHandler->isAjax()) {
+ $response = $controller->RestResponse->ajaxFailResponse('OutboxProcessor', "{$scope}.{$action}", $processResult['data'], $message, $processResult['errors']);
+ } else {
+ $controller->Flash->error($message);
+ if (!is_null($redirect)) {
+ $response = $controller->redirect($redirect);
+ } else {
+ $response = $controller->redirect(['action' => 'index']);
+ }
+ }
+ }
+
+ return $response;
+ }
+
+ public function checkLoading()
+ {
+ return 'Assimilation successful!';
+ }
+
+ public function create($requestData)
+ {
+ $user_id = Router::getRequest()->getSession()->read('Auth.id');
+ $requestData['scope'] = $this->scope;
+ $requestData['action'] = $this->action;
+ $requestData['description'] = $this->description;
+ $requestData['user_id'] = $user_id;
+ $request = $this->generateRequest($requestData);
+ $savedRequest = $this->Outbox->createEntry($request);
+ return $this->genActionResult(
+ $savedRequest,
+ $savedRequest !== false,
+ __('{0} request for {1} created', $this->scope, $this->action),
+ $request->getErrors()
+ );
+ }
+
+ public function discard($id, $requestData)
+ {
+ $request = $this->Outbox->get($id);
+ $this->Outbox->delete($request);
+ return $this->genActionResult(
+ [],
+ true,
+ __('{0}.{1} request #{2} discarded', $this->scope, $this->action, $id)
+ );
+ }
+}
diff --git a/libraries/default/OutboxProcessors/TemplateOutoxProcessor.php.template b/libraries/default/OutboxProcessors/TemplateOutoxProcessor.php.template
new file mode 100644
index 0000000..8c5a506
--- /dev/null
+++ b/libraries/default/OutboxProcessors/TemplateOutoxProcessor.php.template
@@ -0,0 +1,65 @@
+description = __('~to-be-defined~');
+ $this->Users = TableRegistry::getTableLocator()->get('Users');
+ }
+
+ protected function addValidatorRules($validator)
+ {
+ return $validator;
+ }
+
+ public function create($requestData) {
+ $this->validateRequestData($requestData);
+ $requestData['title'] = __('~to-be-defined~');
+ return parent::create($requestData);
+ }
+
+ public function process($id, $requestData, $inboxRequest)
+ {
+ $proposalAccepted = false;
+ $saveResult = [];
+ if ($proposalAccepted) {
+ $this->discard($id, $requestData);
+ }
+ return $this->genActionResult(
+ $saveResult,
+ $proposalAccepted,
+ $proposalAccepted ? __('success') : __('fail'),
+ []
+ );
+ }
+
+ public function discard($id, $requestData)
+ {
+ return parent::discard($id, $requestData);
+ }
+}
\ No newline at end of file
diff --git a/libraries/default/OutboxProcessors/templates/Broods/ResendFailedMessage.php b/libraries/default/OutboxProcessors/templates/Broods/ResendFailedMessage.php
new file mode 100644
index 0000000..9962b15
--- /dev/null
+++ b/libraries/default/OutboxProcessors/templates/Broods/ResendFailedMessage.php
@@ -0,0 +1,143 @@
+ 'cancel',
+ 'variant' => 'secondary',
+ 'text' => __('Cancel'),
+ ],
+ [
+ 'clickFunction' => 'deleteEntry',
+ 'variant' => 'danger',
+ 'text' => __('Delete Message'),
+ ],
+ [
+ 'clickFunction' => 'resendMessage',
+ 'text' => __('Re-Send Message'),
+ ]
+];
+
+$tools = sprintf(
+'
+ %s
+ %s
+ %s
+
',
+ sprintf('%s%s',
+ sprintf('/localTools/view/%s', h($request['localTool']->id)),
+ h($request['localTool']->description),
+ h($request['localTool']->name),
+ __('(local tool)')
+ ),
+ sprintf('', $this->FontAwesome->getClass('long-arrow-alt-right')),
+ sprintf('%s%s',
+ sprintf('/localTools/broodTools/%s', h($request['data']['remote_tool']['id'])),
+ h($request['data']['remote_tool']['description'] ?? ''),
+ h($request['data']['remote_tool']['name']),
+ __('(remote tool)')
+ )
+);
+
+
+$table = $this->Bootstrap->table(['small' => true, 'bordered' => false, 'striped' => false, 'hover' => false], [
+ 'fields' => [
+ ['key' => 'created', 'label' => __('Date'), 'formatter' => function($value, $row) {
+ return $value->i18nFormat('yyyy-MM-dd HH:mm:ss');
+ }],
+ ['key' => 'brood', 'label' => __('Brood'), 'formatter' => function($brood, $row) {
+ return sprintf('%s',
+ $this->Url->build(['controller' => 'broods', 'action' => 'view', $brood['id']]),
+ h($brood['name'])
+ );
+ }],
+ ['key' => 'individual', 'label' => __('Individual'), 'formatter' => function($individual, $row) {
+ return sprintf('%s',
+ $this->Url->build(['controller' => 'users', 'action' => 'view', $individual['id']]),
+ h($individual['email'])
+ );
+ }],
+ ['key' => 'individual.alignments', 'label' => __('Alignment'), 'formatter' => function($alignments, $row) {
+ $html = '';
+ foreach ($alignments as $alignment) {
+ $html .= sprintf('',
+ h($alignment['type']),
+ $this->Url->build(['controller' => 'users', 'action' => 'view', $alignment['organisation']['id']]),
+ h($alignment['organisation']['name'])
+ );
+ }
+ return $html;
+ }],
+ ],
+ 'items' => [$request->toArray()],
+]);
+
+$requestData = $this->Bootstrap->collapse([
+ 'title' => __('Message data'),
+ 'open' => true,
+ ],
+ sprintf('%s
', json_encode($request['data']['sent'], JSON_PRETTY_PRINT))
+);
+
+$rows = sprintf('%s | %s |
', __('URL'), h($request['data']['url']));
+$rows .= sprintf('%s | %s |
', __('Reason'), h($request['data']['reason']['message']) ?? '');
+$rows .= sprintf('%s | %s |
', __('Errors'), h(json_encode($request['data']['reason']['errors'])) ?? '');
+$table2 = sprintf('', $rows);
+
+$form = $this->element('genericElements/Form/genericForm', [
+ 'entity' => null,
+ 'ajax' => false,
+ 'raw' => true,
+ 'data' => [
+ 'model' => 'Inbox',
+ 'fields' => [
+ [
+ 'field' => 'is_delete',
+ 'type' => 'checkbox',
+ 'default' => false
+ ]
+ ],
+ 'submit' => [
+ 'action' => $this->request->getParam('action')
+ ]
+ ]
+]);
+$form = sprintf('%s
', $form);
+
+$messageSent = $this->Bootstrap->card([
+ 'headerHTML' => __('Message Sent'),
+ 'bodyHTML' => sprintf('%s%s', $table2, $requestData),
+ 'bodyClass' => 'p-2',
+]);
+
+$bodyHtml = sprintf('%s',
+ $tools,
+ $table,
+ $messageSent,
+ $form
+);
+
+echo $this->Bootstrap->modal([
+ 'title' => $request['title'],
+ 'size' => 'xl',
+ 'type' => 'custom',
+ 'bodyHtml' => sprintf('%s
',
+ $bodyHtml
+ ),
+ 'footerButtons' => $footerButtons
+]);
+
+?>
+
+
\ No newline at end of file
diff --git a/libraries/default/RequestProcessors/BroodRequestProcessor.php b/libraries/default/RequestProcessors/BroodRequestProcessor.php
deleted file mode 100644
index 13a0c33..0000000
--- a/libraries/default/RequestProcessors/BroodRequestProcessor.php
+++ /dev/null
@@ -1,108 +0,0 @@
-description = __('Handle tool interconnection request from other cerebrate instance');
- $this->Broods = TableRegistry::getTableLocator()->get('Broods');
- }
-
- protected function addValidatorRules($validator)
- {
- return $validator;
- }
-
- public function create($requestData) {
- $this->validateRequestData($requestData);
- $requestData['title'] = __('Cerebrate instance {0} requested interconnection for tool {1}', 'Insert brood name', 'Insert tool name');
- return parent::create($requestData);
- }
-
- public function process($id, $requestData)
- {
- $connectionSuccessfull = false;
- $interConnectionResult = [];
- if ($connectionSuccessfull) {
- $this->discard($id, $requestData);
- }
- return $this->genActionResult(
- $interConnectionResult,
- $connectionSuccessfull,
- $connectionSuccessfull ? __('Interconnection for `{0}` created', 'Insert tool name') : __('Could interconnect tool `{0}`.', 'Insert tool name'),
- []
- );
- }
-
- public function discard($id, $requestData)
- {
- return parent::discard($id, $requestData);
- }
-}
-
-class OneWaySynchronizationProcessor extends BroodRequestProcessor implements GenericProcessorActionI {
- public $action = 'OneWaySynchronization';
- protected $description;
-
- public function __construct() {
- parent::__construct();
- $this->description = __('Handle cerebrate connection request for another cerebrate instance');
- $this->Broods = TableRegistry::getTableLocator()->get('Broods');
- }
-
- protected function addValidatorRules($validator)
- {
- return $validator;
- }
-
- public function create($requestData) {
- $this->validateRequestData($requestData);
- $requestData['title'] = __('Cerebrate instance {0} requested interconnection', 'Insert cerebrate name');
- return parent::create($requestData);
- }
-
- public function process($id, $requestData)
- {
- $connectionSuccessfull = false;
- $interConnectionResult = [];
- if ($connectionSuccessfull) {
- $this->discard($id, $requestData);
- }
- return $this->genActionResult(
- $interConnectionResult,
- $connectionSuccessfull,
- $connectionSuccessfull ? __('Interconnection with `{0}` created', 'Insert cerebrate name') : __('Could interconnect with `{0}`.', 'Insert cerebrate name'),
- []
- );
- }
-
- public function discard($id, $requestData)
- {
- return parent::discard($id, $requestData);
- }
-}
\ No newline at end of file
diff --git a/libraries/default/meta_fields/enisa-csirt-inventory.json b/libraries/default/meta_fields/enisa-csirt-inventory.json
new file mode 100644
index 0000000..9171a5c
--- /dev/null
+++ b/libraries/default/meta_fields/enisa-csirt-inventory.json
@@ -0,0 +1,74 @@
+{
+ "description": "Template based on the ENISA's CSIRTs inventory",
+ "metaFields": [
+ {
+ "field": "ISO 3166-1 Code",
+ "type": "text",
+ "regex": "[a-z]{2,3}"
+ },
+ {
+ "field": "website",
+ "type": "text",
+ "regex": "(http(s)?:\\\/\\\/.)?(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{2,256}\\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_\\+.~#?&\/\/=]*)"
+ },
+ {
+ "field": "enisa-geo-group",
+ "type": "text"
+ },
+ {
+ "field": "is-approved",
+ "type": "boolean"
+ },
+ {
+ "field": "first-member-type",
+ "type": "text"
+ },
+ {
+ "field": "team-name",
+ "type": "text"
+ },
+ {
+ "field": "oes-coverage",
+ "type": "text"
+ },
+ {
+ "field": "enisa-tistatus",
+ "type": "text"
+ },
+ {
+ "field": "csirt-network-status",
+ "type": "text"
+ },
+ {
+ "field": "constituency",
+ "type": "text"
+ },
+ {
+ "field": "establishment",
+ "type": "text"
+ },
+ {
+ "field": "email",
+ "type": "text",
+ "regex": "(?:[a-z0-9!#$%&'*+\/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+\/=?^_`{|}~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)\\])"
+ },
+ {
+ "field": "country-name",
+ "type": "text"
+ },
+ {
+ "field": "short-team-name",
+ "type": "text"
+ },
+ {
+ "field": "key",
+ "type": "text"
+ }
+ ],
+ "name": "ENISA CSIRT Network",
+ "namespace": "cnw",
+ "scope": "organisation",
+ "source": "enisa.europa.eu/topics/csirts-in-europe/csirt-inventory/certs-by-country-interactive-map",
+ "uuid": "089c68c7-d97e-4f21-a798-159cd10f7864",
+ "version": 1
+}
\ No newline at end of file
diff --git a/src/Application.php b/src/Application.php
index 8eb1704..2da0df1 100644
--- a/src/Application.php
+++ b/src/Application.php
@@ -21,6 +21,7 @@ use Cake\Core\Exception\MissingPluginException;
use Cake\Error\Middleware\ErrorHandlerMiddleware;
use Cake\Http\BaseApplication;
use Cake\Http\MiddlewareQueue;
+use Cake\Http\Middleware\BodyParserMiddleware;
use Cake\Routing\Middleware\AssetMiddleware;
use Cake\Routing\Middleware\RoutingMiddleware;
use Authentication\AuthenticationService;
@@ -28,7 +29,6 @@ use Authentication\AuthenticationServiceInterface;
use Authentication\AuthenticationServiceProviderInterface;
use Authentication\Middleware\AuthenticationMiddleware;
use Psr\Http\Message\ServerRequestInterface;
-
/**
* Application setup class.
*
@@ -87,7 +87,8 @@ class Application extends BaseApplication implements AuthenticationServiceProvid
// using it's second constructor argument:
// `new RoutingMiddleware($this, '_cake_routes_')`
->add(new RoutingMiddleware($this))
- ->add(new AuthenticationMiddleware($this));
+ ->add(new AuthenticationMiddleware($this))
+ ->add(new BodyParserMiddleware());
return $middlewareQueue;
}
diff --git a/src/Command/ImporterCommand.php b/src/Command/ImporterCommand.php
index 30cdc3d..c79ba76 100644
--- a/src/Command/ImporterCommand.php
+++ b/src/Command/ImporterCommand.php
@@ -161,7 +161,7 @@ class ImporterCommand extends Command
'valueField' => 'id'
])->where(['meta_template_id' => $metaTemplate->id])->toArray();
} else {
- $this->io->error("Unkown template for UUID $metaTemplateUUID");
+ $this->io->error("Unkown template for UUID {$config['metaTemplateUUID']}");
die(1);
}
}
diff --git a/src/Controller/AppController.php b/src/Controller/AppController.php
index 70116d9..5d49631 100644
--- a/src/Controller/AppController.php
+++ b/src/Controller/AppController.php
@@ -91,6 +91,9 @@ class AppController extends Controller
$this->loadModel('Users');
$this->Users->checkForNewInstance();
$this->authApiUser();
+ if ($this->ParamHandler->isRest()) {
+ $this->Security->setConfig('unlockedActions', [$this->request->getParam('action')]);
+ }
$this->ACL->setPublicInterfaces();
if (!empty($this->request->getAttribute('identity'))) {
$user = $this->Users->get($this->request->getAttribute('identity')->getIdentifier(), [
@@ -112,6 +115,10 @@ class AppController extends Controller
$this->Security->setConfig('validatePost', false);
}
$this->Security->setConfig('unlockedActions', ['index']);
+ if ($this->ParamHandler->isRest()) {
+ $this->Security->setConfig('unlockedActions', [$this->request->getParam('action')]);
+ $this->Security->setConfig('validatePost', false);
+ }
$this->ACL->checkAccess();
$this->set('menu', $this->ACL->getMenu());
diff --git a/src/Controller/BroodsController.php b/src/Controller/BroodsController.php
index d954f77..94afeda 100644
--- a/src/Controller/BroodsController.php
+++ b/src/Controller/BroodsController.php
@@ -156,10 +156,31 @@ class BroodsController extends AppController
}
}
+ public function downloadSharingGroup($brood_id, $sg_id)
+ {
+ $result = $this->Broods->downloadSharingGroup($brood_id, $sg_id, $this->ACL->getUser()['id']);
+ $success = __('Sharing group fetched from remote.');
+ $fail = __('Could not save the remote sharing group');
+ if ($this->ParamHandler->isRest()) {
+ if ($result) {
+ return $this->RestResponse->saveSuccessResponse('Brood', 'downloadSharingGroup', $brood_id, 'json', $success);
+ } else {
+ return $this->RestResponse->saveFailResponse('Brood', 'downloadSharingGroup', $brood_id, $fail, 'json');
+ }
+ } else {
+ if ($result) {
+ $this->Flash->success($success);
+ } else {
+ $this->Flash->error($fail);
+ }
+ $this->redirect($this->referer());
+ }
+ }
+
public function interconnectTools()
{
- $this->requestProcessor = TableRegistry::getTableLocator()->get('RequestProcessor');
- $processor = $this->requestProcessor->getProcessor('Brood', 'ToolInterconnection');
+ $this->InboxProcessors = TableRegistry::getTableLocator()->get('InboxProcessors');
+ $processor = $this->InboxProcessors->getProcessor('Brood', 'ToolInterconnection');
$data = [
'origin' => '127.0.0.1',
'comment' => 'Test comment',
diff --git a/src/Controller/Component/ACLComponent.php b/src/Controller/Component/ACLComponent.php
index eec94ce..8367536 100644
--- a/src/Controller/Component/ACLComponent.php
+++ b/src/Controller/Component/ACLComponent.php
@@ -650,18 +650,56 @@ class ACLComponent extends Component
'url' => '/inbox/index',
'label' => __('Inbox')
],
+ 'outbox' => [
+ 'url' => '/outbox/index',
+ 'label' => __('Outbox')
+ ],
'view' => [
'url' => '/inbox/view/{{id}}',
- 'label' => __('View Meta Template'),
+ 'label' => __('View Message'),
'actions' => ['delete', 'edit', 'view'],
'skipTopMenu' => 1
],
'delete' => [
'url' => '/inbox/delete/{{id}}',
- 'label' => __('Delete Meta Template'),
+ 'label' => __('Delete Message'),
'actions' => ['delete', 'edit', 'view'],
'skipTopMenu' => 1,
'popup' => 1
+ ],
+ 'listProcessors' => [
+ 'url' => '/inbox/listProcessors',
+ 'label' => __('List Inbox Processors'),
+ 'skipTopMenu' => 1
+ ]
+ ]
+ ],
+ 'Outbox' => [
+ 'label' => __('Outbox'),
+ 'url' => '/outbox/index',
+ 'children' => [
+ 'index' => [
+ 'url' => '/outbox/index',
+ 'label' => __('Outbox'),
+ 'skipTopMenu' => 1
+ ],
+ 'view' => [
+ 'url' => '/outbox/view/{{id}}',
+ 'label' => __('View Message'),
+ 'actions' => ['delete', 'edit', 'view'],
+ 'skipTopMenu' => 1
+ ],
+ 'delete' => [
+ 'url' => '/outbox/delete/{{id}}',
+ 'label' => __('Delete Message'),
+ 'actions' => ['delete', 'edit', 'view'],
+ 'skipTopMenu' => 1,
+ 'popup' => 1
+ ],
+ 'listProcessors' => [
+ 'url' => '/outbox/listProcessors',
+ 'label' => __('List Outbox Processors'),
+ 'skipTopMenu' => 1
]
]
],
@@ -722,7 +760,17 @@ class ACLComponent extends Component
'skipTopMenu' => 1
]
]
- ]
+ ],
+ 'Instance' => [
+ __('Instance'),
+ 'url' => '/instance/home',
+ 'children' => [
+ 'migration' => [
+ 'url' => '/instance/migrationIndex',
+ 'label' => __('Database migration')
+ ]
+ ]
+ ],
],
'Cerebrate' => [
'Roles' => [
@@ -756,10 +804,6 @@ class ACLComponent extends Component
'url' => '/instance/home',
'label' => __('Home')
],
- 'migration' => [
- 'url' => '/instance/migrationIndex',
- 'label' => __('Database migration')
- ]
]
],
'Users' => [
diff --git a/src/Controller/Component/CRUDComponent.php b/src/Controller/Component/CRUDComponent.php
index 31de16c..86bb1ec 100644
--- a/src/Controller/Component/CRUDComponent.php
+++ b/src/Controller/Component/CRUDComponent.php
@@ -7,9 +7,12 @@ use Cake\Error\Debugger;
use Cake\Utility\Hash;
use Cake\Utility\Inflector;
use Cake\View\ViewBuilder;
+use Cake\Http\Exception\MethodNotAllowedException;
+use Cake\Http\Exception\NotFoundException;
class CRUDComponent extends Component
{
+ protected $components = ['RestResponse'];
public function initialize(array $config): void
{
@@ -58,7 +61,7 @@ class CRUDComponent extends Component
$data = $this->Table->{$options['afterFind']}($data);
}
}
- $this->Controller->restResponsePayload = $this->Controller->RestResponse->viewData($data, 'json');
+ $this->Controller->restResponsePayload = $this->RestResponse->viewData($data, 'json');
} else {
$this->Controller->loadComponent('Paginator');
$data = $this->Controller->Paginator->paginate($query);
@@ -126,7 +129,8 @@ class CRUDComponent extends Component
}
if ($this->request->is('post')) {
$patchEntityParams = [
- 'associated' => []
+ 'associated' => [],
+ 'accessibleFields' => $data->getAccessibleFieldForNew(),
];
if (!empty($params['id'])) {
unset($params['id']);
@@ -147,9 +151,9 @@ class CRUDComponent extends Component
} else if ($this->Controller->ParamHandler->isAjax()) {
if (!empty($params['displayOnSuccess'])) {
$displayOnSuccess = $this->renderViewInVariable($params['displayOnSuccess'], ['entity' => $data]);
- $this->Controller->ajaxResponsePayload = $this->Controller->RestResponse->ajaxSuccessResponse($this->ObjectAlias, 'add', $savedData, $message, ['displayOnSuccess' => $displayOnSuccess]);
+ $this->Controller->ajaxResponsePayload = $this->RestResponse->ajaxSuccessResponse($this->ObjectAlias, 'add', $savedData, $message, ['displayOnSuccess' => $displayOnSuccess]);
} else {
- $this->Controller->ajaxResponsePayload = $this->Controller->RestResponse->ajaxSuccessResponse($this->ObjectAlias, 'add', $savedData, $message);
+ $this->Controller->ajaxResponsePayload = $this->RestResponse->ajaxSuccessResponse($this->ObjectAlias, 'add', $savedData, $message);
}
} else {
$this->Controller->Flash->success($message);
@@ -168,8 +172,9 @@ class CRUDComponent extends Component
empty($validationMessage) ? '' : PHP_EOL . __('Reason:{0}', $validationMessage)
);
if ($this->Controller->ParamHandler->isRest()) {
+ $this->Controller->restResponsePayload = $this->RestResponse->viewData($message, 'json');
} else if ($this->Controller->ParamHandler->isAjax()) {
- $this->Controller->ajaxResponsePayload = $this->Controller->RestResponse->ajaxFailResponse($this->ObjectAlias, 'add', $data, $message, $validationMessage);
+ $this->Controller->ajaxResponsePayload = $this->RestResponse->ajaxFailResponse($this->ObjectAlias, 'add', $data, $message, $validationMessage);
} else {
$this->Controller->Flash->error($message);
}
@@ -246,7 +251,7 @@ class CRUDComponent extends Component
if ($this->Controller->ParamHandler->isRest()) {
$this->Controller->restResponsePayload = $this->RestResponse->viewData($savedData, 'json');
} else if ($this->Controller->ParamHandler->isAjax()) {
- $this->Controller->ajaxResponsePayload = $this->Controller->RestResponse->ajaxSuccessResponse($this->ObjectAlias, 'edit', $savedData, $message);
+ $this->Controller->ajaxResponsePayload = $this->RestResponse->ajaxSuccessResponse($this->ObjectAlias, 'edit', $savedData, $message);
} else {
$this->Controller->Flash->success($message);
if (empty($params['redirect'])) {
@@ -263,7 +268,7 @@ class CRUDComponent extends Component
);
if ($this->Controller->ParamHandler->isRest()) {
} else if ($this->Controller->ParamHandler->isAjax()) {
- $this->Controller->ajaxResponsePayload = $this->Controller->RestResponse->ajaxFailResponse($this->ObjectAlias, 'edit', $data, $message, $data->getErrors());
+ $this->Controller->ajaxResponsePayload = $this->RestResponse->ajaxFailResponse($this->ObjectAlias, 'edit', $data, $message, $data->getErrors());
} else {
$this->Controller->Flash->error($message);
}
@@ -333,38 +338,122 @@ class CRUDComponent extends Component
$data = $params['afterFind']($data);
}
if ($this->Controller->ParamHandler->isRest()) {
- $this->Controller->restResponsePayload = $this->Controller->RestResponse->viewData($data, 'json');
+ $this->Controller->restResponsePayload = $this->RestResponse->viewData($data, 'json');
}
$this->Controller->set('entity', $data);
}
- public function delete(int $id): void
+ public function delete($id=false): void
{
- if (empty($id)) {
- throw new NotFoundException(__('Invalid {0}.', $this->ObjectAlias));
- }
- $data = $this->Table->get($id);
- if ($this->request->is('post') || $this->request->is('delete')) {
- if ($this->Table->delete($data)) {
- $message = __('{0} deleted.', $this->ObjectAlias);
- if ($this->Controller->ParamHandler->isRest()) {
- $this->Controller->restResponsePayload = $this->RestResponse->viewData($data, 'json');
- } else if ($this->Controller->ParamHandler->isAjax()) {
- $this->Controller->ajaxResponsePayload = $this->Controller->RestResponse->ajaxSuccessResponse($this->ObjectAlias, 'delete', $data, $message);
- } else {
- $this->Controller->Flash->success($message);
- $this->Controller->redirect($this->Controller->referer());
+ if ($this->request->is('get')) {
+ if(!empty($id)) {
+ $data = $this->Table->get($id);
+ $this->Controller->set('id', $data['id']);
+ $this->Controller->set('data', $data);
+ $this->Controller->set('bulkEnabled', false);
+ } else {
+ $this->Controller->set('bulkEnabled', true);
+ }
+ } else if ($this->request->is('post') || $this->request->is('delete')) {
+ $ids = $this->getIdsOrFail($id);
+ $isBulk = count($ids) > 1;
+ $bulkSuccesses = 0;
+ foreach ($ids as $id) {
+ $data = $this->Table->get($id);
+ $success = $this->Table->delete($data);
+ $success = true;
+ if ($success) {
+ $bulkSuccesses++;
}
}
+ $message = $this->getMessageBasedOnResult(
+ $bulkSuccesses == count($ids),
+ $isBulk,
+ __('{0} deleted.', $this->ObjectAlias),
+ __('All {0} have been deleted.', Inflector::pluralize($this->ObjectAlias)),
+ __('Could not delete {0}.', $this->ObjectAlias),
+ __('{0} / {1} {2} have been deleted.',
+ $bulkSuccesses,
+ count($ids),
+ Inflector::pluralize($this->ObjectAlias)
+ )
+ );
+ $this->setResponseForController('delete', $bulkSuccesses, $message, $data);
}
$this->Controller->set('metaGroup', 'ContactDB');
$this->Controller->set('scope', 'users');
- $this->Controller->set('id', $data['id']);
- $this->Controller->set('data', $data);
$this->Controller->viewBuilder()->setLayout('ajax');
$this->Controller->render('/genericTemplates/delete');
}
+ public function setResponseForController($action, $success, $message, $data=[], $errors=null)
+ {
+ if ($success) {
+ if ($this->Controller->ParamHandler->isRest()) {
+ $this->Controller->restResponsePayload = $this->RestResponse->viewData($data, 'json');
+ } elseif ($this->Controller->ParamHandler->isAjax()) {
+ $this->Controller->ajaxResponsePayload = $this->RestResponse->ajaxSuccessResponse($this->ObjectAlias, $action, $data, $message);
+ } else {
+ $this->Controller->Flash->success($message);
+ $this->Controller->redirect($this->Controller->referer());
+ }
+ } else {
+ if ($this->Controller->ParamHandler->isRest()) {
+ $this->Controller->restResponsePayload = $this->RestResponse->viewData($data, 'json');
+ } elseif ($this->Controller->ParamHandler->isAjax()) {
+ $this->Controller->ajaxResponsePayload = $this->RestResponse->ajaxFailResponse($this->ObjectAlias, $action, $data, $message, !is_null($errors) ? $errors : $data->getErrors());
+ } else {
+ $this->Controller->Flash->error($message);
+ $this->Controller->redirect($this->Controller->referer());
+ }
+ }
+ }
+
+ private function getMessageBasedOnResult($isSuccess, $isBulk, $messageSingleSuccess, $messageBulkSuccess, $messageSingleFailure, $messageBulkFailure)
+ {
+ if ($isSuccess) {
+ $message = $isBulk ? $messageBulkSuccess : $messageSingleSuccess;
+ } else {
+ $message = $isBulk ? $messageBulkFailure : $messageSingleFailure;
+ }
+ return $message;
+ }
+
+ /**
+ * getIdsOrFail
+ *
+ * @param mixed $id
+ * @return Array The ID converted to a list or the list of provided IDs from the request
+ * @throws NotFoundException when no ID could be found
+ */
+ public function getIdsOrFail($id=false): Array
+ {
+ $params = $this->Controller->ParamHandler->harvestParams(['ids']);
+ if (!empty($params['ids'])) {
+ $params['ids'] = json_decode($params['ids']);
+ }
+ $ids = [];
+ if (empty($id)) {
+ if (empty($params['ids'])) {
+ throw new NotFoundException(__('Invalid {0}.', $this->ObjectAlias));
+ }
+ $ids = $params['ids'];
+ } else {
+ $id = $this->getInteger($id);
+ if (!is_null($id)) {
+ $ids = [$id];
+ } else {
+ throw new NotFoundException(__('Invalid {0}.', $this->ObjectAlias));
+ }
+ }
+ return $ids;
+ }
+
+ private function getInteger($value)
+ {
+ return is_numeric($value) ? intval($value) : null;
+ }
+
protected function massageFilters(array $params): array
{
$massagedFilters = [
@@ -600,7 +689,7 @@ class CRUDComponent extends Component
if ($this->Controller->ParamHandler->isRest()) {
$this->Controller->restResponsePayload = $this->RestResponse->viewData($data, 'json');
} else if ($this->Controller->ParamHandler->isAjax()) {
- $this->Controller->ajaxResponsePayload = $this->Controller->RestResponse->ajaxSuccessResponse($this->ObjectAlias, 'toggle', $savedData, $message);
+ $this->Controller->ajaxResponsePayload = $this->RestResponse->ajaxSuccessResponse($this->ObjectAlias, 'toggle', $savedData, $message);
} else {
$this->Controller->Flash->success($message);
if (empty($params['redirect'])) {
@@ -618,7 +707,7 @@ class CRUDComponent extends Component
);
if ($this->Controller->ParamHandler->isRest()) {
} else if ($this->Controller->ParamHandler->isAjax()) {
- $this->Controller->ajaxResponsePayload = $this->Controller->RestResponse->ajaxFailResponse($this->ObjectAlias, 'toggle', $message, $validationMessage);
+ $this->Controller->ajaxResponsePayload = $this->RestResponse->ajaxFailResponse($this->ObjectAlias, 'toggle', $message, $validationMessage);
} else {
$this->Controller->Flash->error($message);
if (empty($params['redirect'])) {
@@ -644,10 +733,10 @@ class CRUDComponent extends Component
if ($this->request->is('post')) {
$data[$fieldName] = $data[$fieldName] ? true : false;
$this->Table->save($data);
- $this->Controller->restResponsePayload = $this->Controller->RestResponse->viewData(['value' => $data[$fieldName]], 'json');
+ $this->Controller->restResponsePayload = $this->RestResponse->viewData(['value' => $data[$fieldName]], 'json');
} else {
if ($this->Controller->ParamHandler->isRest()) {
- $this->Controller->restResponsePayload = $this->Controller->RestResponse->viewData(['value' => $data[$fieldName]], 'json');
+ $this->Controller->restResponsePayload = $this->RestResponse->viewData(['value' => $data[$fieldName]], 'json');
} else {
$this->Controller->set('fieldName', $fieldName);
$this->Controller->set('currentValue', $data[$fieldName]);
diff --git a/src/Controller/InboxController.php b/src/Controller/InboxController.php
index 6c0f978..4b8a788 100644
--- a/src/Controller/InboxController.php
+++ b/src/Controller/InboxController.php
@@ -6,6 +6,7 @@ use App\Controller\AppController;
use Cake\Database\Expression\QueryExpression;
use Cake\Event\EventInterface;
use Cake\ORM\TableRegistry;
+use Cake\Utility\Inflector;
use Cake\Utility\Hash;
use Cake\Utility\Text;
use Cake\Http\Exception\NotFoundException;
@@ -32,7 +33,6 @@ class InboxController extends AppController
'contextFilters' => [
'fields' => [
'scope',
- 'action',
]
],
'contain' => ['Users']
@@ -57,17 +57,46 @@ class InboxController extends AppController
}
}
- public function delete($id)
+ public function delete($id=false)
{
- if ($this->request->is('post')) {
- $request = $this->Inbox->get($id);
- $this->requestProcessor = TableRegistry::getTableLocator()->get('RequestProcessor');
- $processor = $this->requestProcessor->getProcessor($request->scope, $request->action);
- $discardResult = $processor->discard($id, $request);
- return $processor->genHTTPReply($this, $discardResult);
+ if ($this->request->is('post')) { // cannot rely on CRUD's delete as inbox's processor discard function is responsible to handle their messages
+ $ids = $this->CRUD->getIdsOrFail($id);
+ $discardSuccesses = 0;
+ $discardResults = [];
+ $discardErrors = [];
+ foreach ($ids as $id) {
+ $request = $this->Inbox->get($id, ['contain' => ['Users' => ['Individuals' => ['Alignments' => 'Organisations']]]]);
+ $this->inboxProcessors = TableRegistry::getTableLocator()->get('InboxProcessors');
+ $processor = $this->inboxProcessors->getProcessor($request->scope, $request->action);
+ $discardResult = $processor->discard($id, $request);
+ $discardResults[] = $discardResult;
+ if ($discardResult['success']) {
+ $discardSuccesses++;
+ } else {
+ $discardErrors[] = $discardResult;
+ }
+ }
+ if (count($ids) == 1) {
+ return $processor->genHTTPReply($this, $discardResult);
+ } else {
+ $success = $discardSuccesses == count($ids);
+ $message = __('{0} {1} have been discarded.',
+ $discardSuccesses == count($ids) ? __('All') : sprintf('%s / %s', $discardSuccesses, count($ids)),
+ sprintf('%s %s', Inflector::singularize($this->Inbox->getAlias()), __('messages'))
+ );
+ $this->CRUD->setResponseForController('delete', $success, $message, $discardResults, $discardResults);
+ $responsePayload = $this->CRUD->getResponsePayload();
+ if (!empty($responsePayload)) {
+ return $responsePayload;
+ }
+ }
}
$this->set('deletionTitle', __('Discard request'));
- $this->set('deletionText', __('Are you sure you want to discard request #{0}?', $id));
+ if (!empty($id)) {
+ $this->set('deletionText', __('Are you sure you want to discard request #{0}?', $id));
+ } else {
+ $this->set('deletionText', __('Are you sure you want to discard the selected requests?'));
+ }
$this->set('deletionConfirm', __('Discard'));
$this->CRUD->delete($id);
$responsePayload = $this->CRUD->getResponsePayload();
@@ -78,54 +107,78 @@ class InboxController extends AppController
public function process($id)
{
- $request = $this->Inbox->get($id);
+ $request = $this->Inbox->get($id, ['contain' => ['Users' => ['Individuals' => ['Alignments' => 'Organisations']]]]);
$scope = $request->scope;
$action = $request->action;
- $this->requestProcessor = TableRegistry::getTableLocator()->get('RequestProcessor');
- $processor = $this->requestProcessor->getProcessor($request->scope, $request->action);
+ $this->InboxProcessors = TableRegistry::getTableLocator()->get('InboxProcessors');
+ if ($scope == 'LocalTool') {
+ $processor = $this->InboxProcessors->getLocalToolProcessor($action, $request->local_tool_name);
+ } else {
+ $processor = $this->InboxProcessors->getProcessor($scope, $action);
+ }
if ($this->request->is('post')) {
- $processResult = $processor->process($id, $this->request->getData());
+ $processResult = $processor->process($id, $this->request->getData(), $request);
return $processor->genHTTPReply($this, $processResult);
} else {
- $renderedView = $processor->render($request);
+ $renderedView = $processor->render($request, $this->request);
return $this->response->withStringBody($renderedView);
}
}
public function listProcessors()
{
- $this->requestProcessor = TableRegistry::getTableLocator()->get('RequestProcessor');
- $requestProcessors = $this->requestProcessor->listProcessors();
+ $this->inboxProcessors = TableRegistry::getTableLocator()->get('InboxProcessors');
+ $processors = $this->inboxProcessors->listProcessors();
if ($this->ParamHandler->isRest()) {
- return $this->RestResponse->viewData($requestProcessors, 'json');
+ return $this->RestResponse->viewData($processors, 'json');
}
$data = [];
- foreach ($requestProcessors as $scope => $processors) {
- foreach ($processors as $processor) {
+ foreach ($processors as $scope => $scopedProcessors) {
+ foreach ($scopedProcessors as $processor) {
$data[] = [
'enabled' => $processor->enabled,
'scope' => $scope,
- 'action' => $processor->action
+ 'action' => $processor->action,
+ 'description' => isset($processor->getDescription) ? $processor->getDescription() : null,
+ 'notice' => $processor->notice ?? null,
+ 'error' => $processor->error ?? null,
];
}
}
- $this->set('title', 'Available request processors');
- $this->set('fields', [
- [
- 'name' => 'Enabled',
- 'data_path' => 'enabled',
- 'element' => 'boolean'
- ],
- [
- 'name' => 'Processor scope',
- 'data_path' => 'scope',
- ],
- [
- 'name' => 'Processor action',
- 'data_path' => 'action',
- ]
- ]);
$this->set('data', $data);
- $this->render('/genericTemplates/index_simple');
+ }
+
+ public function createEntry($scope, $action)
+ {
+ if (!$this->request->is('post')) {
+ throw new MethodNotAllowedException(__('Only POST method is accepted'));
+ }
+ $entryData = [
+ 'origin' => $this->request->clientIp(),
+ 'user_id' => $this->ACL->getUser()['id'],
+ ];
+ $entryData['data'] = $this->request->getData() ?? [];
+ $this->inboxProcessors = TableRegistry::getTableLocator()->get('InboxProcessors');
+ if ($scope == 'LocalTool') {
+ $this->validateLocalToolRequestEntry($entryData);
+ $entryData['origin'] = $entryData['data']['cerebrateURL'];
+ $processor = $this->inboxProcessors->getLocalToolProcessor($action, $entryData['data']['connectorName']);
+ $errors = $this->Inbox->checkUserBelongsToBroodOwnerOrg($this->ACL->getUser(), $entryData);
+ if (!empty($errors)) {
+ $message = __('Could not create inbox message');
+ return $this->RestResponse->ajaxFailResponse(Inflector::singularize($this->Inbox->getAlias()), 'createEntry', [], $message, $errors);
+ }
+ } else {
+ $processor = $this->inboxProcessors->getProcessor($scope, $action);
+ }
+ $creationResult = $this->inboxProcessors->createInboxEntry($processor, $entryData);
+ return $processor->genHTTPReply($this, $creationResult);
+ }
+
+ private function validateLocalToolRequestEntry($entryData)
+ {
+ if (empty($entryData['data']['connectorName']) || empty($entryData['data']['cerebrateURL'])) {
+ throw new MethodNotAllowedException(__('Could not create entry. Tool name or URL is missing'));
+ }
}
}
diff --git a/src/Controller/LocalToolsController.php b/src/Controller/LocalToolsController.php
index 9d4a20c..00bb446 100644
--- a/src/Controller/LocalToolsController.php
+++ b/src/Controller/LocalToolsController.php
@@ -186,11 +186,9 @@ class LocalToolsController extends AppController
},
'afterFind' => function($data) {
foreach ($data as $connector) {
- $connector = [
- 'id' => $connector['id'],
- 'name' => $connector['name'],
- 'connector' => $connector['connector']
- ];
+ $connectorById = $this->LocalTools->getConnectorByConnectionId($connector['id']);
+ $className = array_keys($connectorById)[0];
+ $connector['connectorName'] = $className;
}
return $data;
}
@@ -205,6 +203,9 @@ class LocalToolsController extends AppController
{
$this->loadModel('Broods');
$tools = $this->Broods->queryLocalTools($id);
+ foreach ($tools as $k => $tool) {
+ $tools[$k]['local_tools'] = $this->LocalTools->appendLocalToolConnections($id, $tool);
+ }
if ($this->ParamHandler->isRest()) {
return $this->RestResponse->viewData($tools, 'json');
}
@@ -219,18 +220,37 @@ class LocalToolsController extends AppController
'cerebrate_id' => $cerebrate_id,
'remote_tool_id' => $remote_tool_id
];
+ $this->loadModel('Broods');
+ $remoteCerebrate = $this->Broods->find()->where(['id' => $params['cerebrate_id']])->first();
if ($this->request->is(['post', 'put'])) {
$postParams = $this->ParamHandler->harvestParams(['local_tool_id']);
if (empty($postParams['local_tool_id'])) {
throw new MethodNotAllowedException(__('No local tool ID supplied.'));
}
$params['local_tool_id'] = $postParams['local_tool_id'];
- $result = $this->LocalTools->encodeConnection($params);
- // Send message to remote inbox
- debug($result);
+ $encodingResult = $this->LocalTools->encodeConnection($params);
+ $inboxResult = $encodingResult['inboxResult'];
+ if ($inboxResult['success']) {
+ if ($this->ParamHandler->isRest()) {
+ $response = $this->RestResponse->viewData($inboxResult, 'json');
+ } else if ($this->ParamHandler->isAjax()) {
+ $response = $this->RestResponse->ajaxSuccessResponse('LocalTool', 'connectionRequest', [], $inboxResult['message']);
+ } else {
+ $this->Flash->success($inboxResult['message']);
+ $response = $this->redirect(['action' => 'broodTools', $cerebrate_id]);
+ }
+ } else {
+ if ($this->ParamHandler->isRest()) {
+ $response = $this->RestResponse->viewData($inboxResult, 'json');
+ } else if ($this->ParamHandler->isAjax()) {
+ $response = $this->RestResponse->ajaxFailResponse('LocalTool', 'connectionRequest', [], $inboxResult['message'], $inboxResult['errors']);
+ } else {
+ $this->Flash->error($inboxResult['message']);
+ $response = $this->redirect($this->referer());
+ }
+ }
+ return $response;
} else {
- $this->loadModel('Broods');
- $remoteCerebrate = $this->Broods->find()->where(['id' => $params['cerebrate_id']])->first();
$remoteTool = $this->LocalTools->getRemoteToolById($params);
$local_tools = $this->LocalTools->encodeConnectionChoice($params);
if (empty($local_tools)) {
@@ -243,4 +263,34 @@ class LocalToolsController extends AppController
]);
}
}
+
+ public function connectLocal($local_tool_id)
+ {
+ $params = [
+ 'local_tool_id' => $local_tool_id
+ ];
+ $local_tool = $this->LocalTools->fetchConnection($local_tool_id);
+ if ($this->request->is(['post', 'put'])) {
+ $postParams = $this->ParamHandler->harvestParams(['target_tool_id']);
+ if (empty($postParams['target_tool_id'])) {
+ throw new MethodNotAllowedException(__('No target tool ID supplied.'));
+ }
+
+ $params['target_tool_id'] = $postParams['target_tool_id'];
+ $result = $this->LocalTools->encodeLocalConnection($params);
+ // Send message to remote inbox
+ debug($result);
+ } else {
+ $target_tools = $this->LocalTools->findConnectable($local_tool);
+ debug($target_tools);
+ if (empty($target_tools)) {
+ throw new NotFoundException(__('No tools found to connect.'));
+ }
+ $this->set('data', [
+ 'remoteCerebrate' => $remoteCerebrate,
+ 'remoteTool' => $remoteTool,
+ 'local_tools' => $local_tools
+ ]);
+ }
+ }
}
diff --git a/src/Controller/OutboxController.php b/src/Controller/OutboxController.php
new file mode 100644
index 0000000..946a9ea
--- /dev/null
+++ b/src/Controller/OutboxController.php
@@ -0,0 +1,129 @@
+set('metaGroup', 'Administration');
+ }
+
+
+ public function index()
+ {
+ $this->CRUD->index([
+ 'filters' => $this->filters,
+ 'quickFilters' => ['scope', 'action', ['title' => true], ['comment' => true]],
+ 'contextFilters' => [
+ 'fields' => [
+ 'scope',
+ ]
+ ],
+ 'contain' => ['Users']
+ ]);
+ $responsePayload = $this->CRUD->getResponsePayload();
+ if (!empty($responsePayload)) {
+ return $responsePayload;
+ }
+ }
+
+ public function filtering()
+ {
+ $this->CRUD->filtering();
+ }
+
+ public function view($id)
+ {
+ $this->CRUD->view($id);
+ $responsePayload = $this->CRUD->getResponsePayload();
+ if (!empty($responsePayload)) {
+ return $responsePayload;
+ }
+ }
+
+ public function delete($id=false)
+ {
+ $this->set('deletionTitle', __('Confirm message deletion'));
+ if (!empty($id)) {
+ $this->set('deletionText', __('Are you sure you want to delete message #{0}?', $id));
+ } else {
+ $this->set('deletionText', __('Are you sure you want to delete the selected messages?'));
+ }
+ $this->set('deletionConfirm', __('Delete'));
+ $this->CRUD->delete($id);
+ $responsePayload = $this->CRUD->getResponsePayload();
+ if (!empty($responsePayload)) {
+ return $responsePayload;
+ }
+ }
+
+ public function process($id)
+ {
+ $request = $this->Outbox->get($id, ['contain' => ['Users' => ['Individuals' => ['Alignments' => 'Organisations']]]]);
+ $scope = $request->scope;
+ $action = $request->action;
+ $this->outboxProcessors = TableRegistry::getTableLocator()->get('OutboxProcessors');
+ $processor = $this->outboxProcessors->getProcessor($scope, $action);
+ if ($this->request->is('post')) {
+ $processResult = $processor->process($id, $this->request->getData(), $request);
+ return $processor->genHTTPReply($this, $processResult);
+ } else {
+ $renderedView = $processor->render($request, $this->request);
+ return $this->response->withStringBody($renderedView);
+ }
+ }
+
+ public function listProcessors()
+ {
+ $this->OutboxProcessors = TableRegistry::getTableLocator()->get('OutboxProcessors');
+ $processors = $this->OutboxProcessors->listProcessors();
+ if ($this->ParamHandler->isRest()) {
+ return $this->RestResponse->viewData($processors, 'json');
+ }
+ $data = [];
+ foreach ($processors as $scope => $scopedProcessors) {
+ foreach ($scopedProcessors as $processor) {
+ $data[] = [
+ 'enabled' => $processor->enabled,
+ 'scope' => $scope,
+ 'action' => $processor->action,
+ 'description' => isset($processor->getDescription) ? $processor->getDescription() : null,
+ 'notice' => $processor->notice ?? null,
+ 'error' => $processor->error ?? null,
+ ];
+ }
+ }
+ $this->set('data', $data);
+ }
+
+ public function createEntry($scope, $action)
+ {
+ if (!$this->request->is('post')) {
+ throw new MethodNotAllowedException(__('Only POST method is accepted'));
+ }
+ $entryData = [
+ 'user_id' => $this->ACL->getUser()['id'],
+ ];
+ $entryData['data'] = $this->request->getData() ?? [];
+ $this->OutboxProcessors = TableRegistry::getTableLocator()->get('OutboxProcessors');
+ $processor = $this->OutboxProcessors->getProcessor($scope, $action);
+ $creationResult = $this->OutboxProcessors->createOutboxEntry($processor, $entryData);
+ return $processor->genHTTPReply($this, $creationResult);
+ }
+}
diff --git a/src/Controller/UsersController.php b/src/Controller/UsersController.php
index 8a40151..9126e87 100644
--- a/src/Controller/UsersController.php
+++ b/src/Controller/UsersController.php
@@ -138,8 +138,8 @@ class UsersController extends AppController
public function register()
{
- $this->requestProcessor = TableRegistry::getTableLocator()->get('RequestProcessor');
- $processor = $this->requestProcessor->getProcessor('User', 'Registration');
+ $this->InboxProcessors = TableRegistry::getTableLocator()->get('InboxProcessors');
+ $processor = $this->InboxProcessors->getProcessor('User', 'Registration');
$data = [
'origin' => '127.0.0.1',
'comment' => 'Hi there!, please create an account',
diff --git a/src/Lib/default/local_tool_connectors/CommonConnectorTools.php b/src/Lib/default/local_tool_connectors/CommonConnectorTools.php
index 13184c5..7e865c0 100644
--- a/src/Lib/default/local_tool_connectors/CommonConnectorTools.php
+++ b/src/Lib/default/local_tool_connectors/CommonConnectorTools.php
@@ -7,11 +7,19 @@ class CommonConnectorTools
{
public $description = '';
public $name = '';
+ public $connectorName = '';
public $exposedFunctions = [
'diagnostics'
];
public $version = '???';
+ const STATE_INITIAL = 'Request issued';
+ const STATE_ACCEPT = 'Request accepted';
+ const STATE_CONNECTED = 'Connected';
+ const STATE_SENDING_ERROR = 'Error while sending request';
+ const STATE_CANCELLED = 'Request cancelled';
+ const STATE_DECLINED = 'Request declined by remote';
+
public function addExposedFunction(string $functionName): void
{
$this->exposedFunctions[] = $functionName;
@@ -49,11 +57,57 @@ class CommonConnectorTools
return true;
}
- public function encodeConnection(array $params): array
+ public function remoteToolConnectionStatus(array $params, string $status): void
{
- $result = $this->encodeConnection($params);
+ $remoteToolConnections = \Cake\ORM\TableRegistry::getTableLocator()->get('RemoteToolConnections');
+ $remoteToolConnection = $remoteToolConnections->find()->where(
+ [
+ 'local_tool_id' => $params['connection']['id'],
+ 'remote_tool_id' => $params['remote_tool']['id'],
+ 'brood_id' => $params['remote_cerebrate']['id']
+ ]
+ )->first();
+ if (empty($remoteToolConnection)) {
+ $data = $remoteToolConnections->newEmptyEntity();
+ $entry = [
+ 'local_tool_id' => $params['connection']['id'],
+ 'remote_tool_id' => $params['remote_tool']['id'],
+ 'remote_tool_name' => $params['remote_tool']['name'],
+ 'brood_id' => $params['remote_cerebrate']['id'],
+ 'name' => '',
+ 'settings' => '',
+ 'status' => $status,
+ 'created' => time(),
+ 'modified' => time()
+ ];
+ $data = $remoteToolConnections->patchEntity($data, $entry);
+ $remoteToolConnections->save($data);
+ } else {
+ $data = $remoteToolConnections->patchEntity($remoteToolConnection, ['status' => $status, 'modified' => time()]);
+ $remoteToolConnections->save($data);
+ }
+ }
+
+ public function initiateConnectionWrapper(array $params): array
+ {
+ $result = $this->initiateConnection($params);
+ $this->remoteToolConnectionStatus($params, self::STATE_INITIAL);
return $result;
}
+
+ public function acceptConnectionWrapper(array $params): array
+ {
+ $result = $this->acceptConnection($params);
+ $this->remoteToolConnectionStatus($params, self::STATE_ACCEPT);
+ return $result;
+ }
+
+ public function finaliseConnectionWrapper(array $params): bool
+ {
+ $result = $this->finaliseConnection($params);
+ $this->remoteToolConnectionStatus($params, self::STATE_CONNECTED);
+ return false;
+ }
}
?>
diff --git a/src/Lib/default/local_tool_connectors/MispConnector.php b/src/Lib/default/local_tool_connectors/MispConnector.php
index be202e5..01a372b 100644
--- a/src/Lib/default/local_tool_connectors/MispConnector.php
+++ b/src/Lib/default/local_tool_connectors/MispConnector.php
@@ -11,6 +11,7 @@ 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 $connectorName = 'MispConnector';
public $name = 'MISP';
public $exposedFunctions = [
@@ -71,6 +72,28 @@ class MispConnector extends CommonConnectorTools
'value'
],
'redirect' => 'serverSettingsAction'
+ ],
+ 'serversAction' => [
+ 'type' => 'index',
+ 'scope' => 'child',
+ 'params' => [
+ 'quickFilter',
+ 'limit',
+ 'page',
+ 'sort',
+ 'direction'
+ ]
+ ],
+ 'usersAction' => [
+ 'type' => 'index',
+ 'scope' => 'child',
+ 'params' => [
+ 'quickFilter',
+ 'limit',
+ 'page',
+ 'sort',
+ 'direction'
+ ]
]
];
public $version = '0.1';
@@ -89,17 +112,54 @@ class MispConnector extends CommonConnectorTools
}
}
- public function health(Object $connection): array
+ private function genHTTPClient(Object $connection, array $options=[]): Object
{
$settings = json_decode($connection->settings, true);
- $http = new Client();
- $response = $http->post($settings['url'] . '/users/view/me.json', '{}', [
+ $defaultOptions = [
'headers' => [
- 'AUTHORIZATION' => $settings['authkey'],
- 'Accept' => 'Application/json',
- 'Content-type' => 'Application/json'
- ]
- ]);
+ 'Authorization' => $settings['authkey'],
+ ],
+ ];
+ if (empty($options['type'])) {
+ $options['type'] = 'json';
+ }
+ if (!empty($settings['skip_ssl'])) {
+ $options['ssl_verify_peer'] = false;
+ $options['ssl_verify_host'] = false;
+ $options['ssl_verify_peer_name'] = false;
+ $options['ssl_allow_self_signed'] = true;
+ }
+ $options = array_merge($defaultOptions, $options);
+ $http = new Client($options);
+ return $http;
+ }
+
+ public function HTTPClientGET(String $relativeURL, Object $connection, array $data=[], array $options=[]): Object
+ {
+ $settings = json_decode($connection->settings, true);
+ $http = $this->genHTTPClient($connection, $options);
+ $url = sprintf('%s%s', $settings['url'], $relativeURL);
+ return $http->get($url, $data, $options);
+ }
+
+ public function HTTPClientPOST(String $relativeURL, Object $connection, $data, array $options=[]): Object
+ {
+ $settings = json_decode($connection->settings, true);
+ $http = $this->genHTTPClient($connection, $options);
+ $url = sprintf('%s%s', $settings['url'], $relativeURL);
+ return $http->post($url, $data, $options);
+ }
+
+ public function health(Object $connection): array
+ {
+ try {
+ $response = $this->HTTPClientPOST('/users/view/me.json', $connection, '{}');
+ } catch (\Exception $e) {
+ return [
+ 'status' => 0,
+ 'message' => __('Connection issue.')
+ ];
+ }
$responseCode = $response->getStatusCode();
if ($response->isOk()) {
$status = 1;
@@ -123,8 +183,6 @@ class MispConnector extends CommonConnectorTools
if (empty($params['connection'])) {
throw new NotFoundException(__('No connection object received.'));
}
- $settings = json_decode($params['connection']->settings, true);
- $http = new Client();
if (!empty($params['sort'])) {
$list = explode('.', $params['sort']);
$params['sort'] = end($list);
@@ -133,13 +191,7 @@ class MispConnector extends CommonConnectorTools
$params['limit'] = 50;
}
$url = $this->urlAppendParams($url, $params);
- $response = $http->get($settings['url'] . $url, false, [
- 'headers' => [
- 'AUTHORIZATION' => $settings['authkey'],
- 'Accept' => 'application/json',
- 'Content-type' => 'application/json'
- ]
- ]);
+ $response = $this->HTTPClientGET($url, $params['connection']);
if ($response->isOk()) {
return $response;
} else {
@@ -155,20 +207,12 @@ class MispConnector extends CommonConnectorTools
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'
- ]);
+ $response = $this->HTTPClientPOST($url, $params['connection'], json_encode($params['body']));
if ($response->isOk()) {
return $response;
} else {
- throw new NotFoundException(__('Could not post to the requested resource.'));
+ throw new NotFoundException(__('Could not post to the requested resource. Remote returned:') . PHP_EOL . $response->getStringBody());
}
}
@@ -248,6 +292,7 @@ class MispConnector extends CommonConnectorTools
'name' => __('Value'),
'sort' => 'value',
'data_path' => 'value',
+ 'options' => 'options'
],
[
'name' => __('Type'),
@@ -288,6 +333,172 @@ class MispConnector extends CommonConnectorTools
}
}
+ public function serversAction(array $params): array
+ {
+ $params['validParams'] = [
+ 'limit' => 'limit',
+ 'page' => 'page',
+ 'quickFilter' => 'searchall'
+ ];
+ $urlParams = h($params['connection']['id']) . '/serversAction';
+ $response = $this->getData('/servers/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,
+ 'quickFilter' => 'value'
+ ]
+ ]
+ ],
+ 'fields' => [
+ [
+ 'name' => 'Id',
+ 'sort' => 'Server.id',
+ 'data_path' => 'Server.id',
+ ],
+ [
+ 'name' => 'Name',
+ 'sort' => 'Server.name',
+ 'data_path' => 'Server.name',
+ ],
+ [
+ 'name' => 'Url',
+ 'sort' => 'Server.url',
+ 'data_path' => 'Server.url'
+ ],
+ [
+ 'name' => 'Pull',
+ 'sort' => 'Server.pull',
+ 'element' => 'function',
+ 'function' => function($row, $context) {
+ $pull = $context->Hash->extract($row, 'Server.pull')[0];
+ $pull_rules = $context->Hash->extract($row, 'Server.pull_rules')[0];
+ $pull_rules = json_encode(json_decode($pull_rules, true), JSON_PRETTY_PRINT);
+ echo sprintf(
+ '',
+ h($pull_rules),
+ $pull ? 'check' : 'times'
+ );
+ }
+ ],
+ [
+ 'name' => 'Push',
+ 'element' => 'function',
+ 'function' => function($row, $context) {
+ $push = $context->Hash->extract($row, 'Server.push')[0];
+ $push_rules = $context->Hash->extract($row, 'Server.push_rules')[0];
+ $push_rules = json_encode(json_decode($push_rules, true), JSON_PRETTY_PRINT);
+ echo sprintf(
+ '',
+ h($push_rules),
+ $push ? 'check' : 'times'
+ );
+ }
+ ],
+ [
+ 'name' => 'Caching',
+ 'element' => 'boolean',
+ 'data_path' => 'Server.caching_enabled'
+ ]
+ ],
+ 'title' => false,
+ 'description' => false,
+ 'pull' => 'right'
+ ]
+ ];
+ } else {
+ return [];
+ }
+ }
+
+ public function usersAction(array $params): array
+ {
+ $params['validParams'] = [
+ 'limit' => 'limit',
+ 'page' => 'page',
+ 'quickFilter' => 'searchall'
+ ];
+ $urlParams = h($params['connection']['id']) . '/usersAction';
+ $response = $this->getData('/admin/users/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,
+ 'quickFilter' => 'value'
+ ]
+ ]
+ ],
+ 'fields' => [
+ [
+ 'name' => 'Id',
+ 'sort' => 'User.id',
+ 'data_path' => 'User.id',
+ ],
+ [
+ 'name' => 'Organisation',
+ 'sort' => 'Organisation.name',
+ 'data_path' => 'Organisation.name',
+ ],
+ [
+ 'name' => 'Email',
+ 'sort' => 'User.email',
+ 'data_path' => 'User.email',
+ ],
+ [
+ 'name' => 'Role',
+ 'sort' => 'Role.name',
+ 'data_path' => 'Role.name'
+ ]
+ ],
+ 'actions' => [
+ [
+ 'open_modal' => '/localTools/action/' . h($params['connection']['id']) . '/editUser?id={{0}}',
+ 'modal_params_data_path' => ['User.id'],
+ 'icon' => 'edit',
+ 'reload_url' => '/localTools/action/' . h($params['connection']['id']) . '/editAction'
+ ],
+ [
+ 'open_modal' => '/localTools/action/' . h($params['connection']['id']) . '/deleteUser?id={{0}}',
+ 'modal_params_data_path' => ['User.id'],
+ 'icon' => 'trash',
+ 'reload_url' => '/localTools/action/' . h($params['connection']['id']) . '/serversAction'
+ ]
+ ],
+ 'title' => false,
+ 'description' => false,
+ 'pull' => 'right'
+ ]
+ ];
+ } else {
+ return [];
+ }
+ }
+
+
public function organisationsAction(array $params): array
{
$params['validParams'] = [
@@ -515,18 +726,30 @@ class MispConnector extends CommonConnectorTools
'boolean' => 'checkbox',
'numeric' => 'number'
];
- $fields = [
- [
- 'field' => 'value',
- 'label' => __('Value'),
- 'default' => h($response['value']),
- 'type' => $types[$response['type']]
- ],
- ];
+ if (!empty($response['options'])) {
+ $fields = [
+ [
+ 'field' => 'value',
+ 'label' => __('Value'),
+ 'default' => h($response['value']),
+ 'type' => 'dropdown',
+ 'options' => $response['options']
+ ]
+ ];
+ } else {
+ $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']),
+ 'description' => __('Modify setting ({0}) on selected MISP instance(s).', $params['setting']),
'fields' => $fields,
'submit' => [
'action' => $params['request']->getParam('action')
@@ -540,18 +763,60 @@ class MispConnector extends CommonConnectorTools
if ($response->getStatusCode() == 200) {
return ['success' => 1, 'message' => __('Setting saved.')];
} else {
- return ['success' => 0, 'message' => __('Could not save the setting.')];
+ return ['success' => 0, 'message' => __('Could not update.')];
}
}
throw new MethodNotAllowedException(__('Invalid http request type for the given action.'));
}
- public function encodeConnectionAction(array $params): array
+ public function initiateConnection(array $params): array
{
- if (empty($params['org_uuid'])) {
- throw new MethodNotAllowedException(__('No org uuid passed, cannot encode connection.'));
- }
- return [];
+ $params['connection_settings'] = json_decode($params['connection']['settings'], true);
+ $params['misp_organisation'] = $this->getSetOrg($params);
+ $params['sync_user'] = $this->createSyncUser($params, true);
+ return [
+ 'email' => $params['sync_user']['email'],
+ 'user_id' => $params['sync_user']['id'],
+ 'authkey' => $params['sync_user']['authkey'],
+ 'url' => $params['connection_settings']['url'],
+ ];
+ }
+
+ public function acceptConnection(array $params): array
+ {
+ $params['sync_user_enabled'] = true;
+ $params['connection_settings'] = json_decode($params['connection']['settings'], true);
+ $params['misp_organisation'] = $this->getSetOrg($params);
+ $params['sync_user'] = $this->createSyncUser($params, false);
+ $serverParams = $params;
+ $serverParams['body'] = [
+ 'authkey' => $params['remote_tool_data']['authkey'],
+ 'url' => $params['remote_tool_data']['url'],
+ 'name' => !empty($params['remote_tool_data']['tool_name']) ? $params['remote_tool_data']['tool_name'] : sprintf('MISP for %s', $params['remote_tool_data']['url']),
+ 'remote_org_id' => $params['misp_organisation']['id']
+ ];
+ $params['sync_connection'] = $this->addServer($serverParams);
+ return [
+ 'email' => $params['sync_user']['email'],
+ 'authkey' => $params['sync_user']['authkey'],
+ 'url' => $params['connection_settings']['url'],
+ 'reflected_user_id' => $params['remote_tool_data']['user_id'] // request initiator Cerebrate to enable the MISP user
+ ];
+ }
+
+ public function finaliseConnection(array $params): bool
+ {
+ $params['misp_organisation'] = $this->getSetOrg($params);
+ $user = $this->enableUser($params, intval($params['remote_tool_data']['reflected_user_id']));
+ $serverParams = $params;
+ $serverParams['body'] = [
+ 'authkey' => $params['remote_tool_data']['authkey'],
+ 'url' => $params['remote_tool_data']['url'],
+ 'name' => !empty($params['remote_tool_data']['tool_name']) ? $params['remote_tool_data']['tool_name'] : sprintf('MISP for %s', $params['remote_tool_data']['url']),
+ 'remote_org_id' => $params['misp_organisation']['id']
+ ];
+ $params['sync_connection'] = $this->addServer($serverParams);
+ return true;
}
private function getSetOrg(array $params): array
@@ -562,6 +827,7 @@ class MispConnector extends CommonConnectorTools
$organisation = $response->getJson()['Organisation'];
if (!$organisation['local']) {
$organisation['local'] = 1;
+ $params['body'] = $organisation;
$response = $this->postData('/admin/organisations/edit/' . $organisation['id'], $params);
if (!$response->isOk()) {
throw new MethodNotAllowedException(__('Could not update the organisation in MISP.'));
@@ -583,39 +849,70 @@ class MispConnector extends CommonConnectorTools
return $organisation;
}
- private function createSyncUser(array $params): array
+ private function createSyncUser(array $params, $disabled=true): array
{
$params['softError'] = 1;
- $username = sprintf(
- 'sync_%s@%s',
- \Cake\Utility\Security::randomString(8),
- parse_url($params['remote_cerebrate']['url'])['host']
- );
- $params['body'] = [
- 'email' => $username,
+ $user = [
+ 'email' => 'sync_%s@' . parse_url($params['remote_cerebrate']['url'])['host'],
'org_id' => $params['misp_organisation']['id'],
'role_id' => empty($params['connection_settings']['role_id']) ? 5 : $params['connection_settings']['role_id'],
- 'disabled' => 1,
+ 'disabled' => $disabled,
'change_pw' => 0,
'termsaccepted' => 1
];
+ return $this->createUser($user, $params);
+ }
+
+ private function enableUser(array $params, int $userID): array
+ {
+ $params['softError'] = 1;
+ $user = [
+ 'disabled' => false,
+ ];
+ return $this->updateUser($userID, $user, $params);
+ }
+
+ private function addServer(array $params): array
+ {
+ if (
+ empty($params['body']['authkey']) ||
+ empty($params['body']['url']) ||
+ empty($params['body']['remote_org_id']) ||
+ empty($params['body']['name'])
+ ) {
+ throw new MethodNotAllowedException(__('Required data missing from the sync connection object. The following fields are required: [name, url, authkey, org_id].'));
+ }
+ $response = $this->postData('/servers/add', $params);
+ if (!$response->isOk()) {
+ throw new MethodNotAllowedException(__('Could not add Server in MISP.'));
+ }
+ return $response->getJson()['Server'];
+ }
+
+ private function createUser(array $user, array $params): array
+ {
+ if (strpos($user['email'], '%s') !== false) {
+ $user['email'] = sprintf(
+ $user['email'],
+ \Cake\Utility\Security::randomString(8)
+ );
+ }
+ $params['body'] = $user;
$response = $this->postData('/admin/users/add', $params);
if (!$response->isOk()) {
- throw new MethodNotAllowedException(__('Could not update the organisation in MISP.'));
+ throw new MethodNotAllowedException(__('Could not add the user in MISP.'));
}
return $response->getJson()['User'];
}
- public function connectToRemoteTool(array $params): array
+ private function updateUser(int $userID, array $user, array $params): array
{
- $params['connection_settings'] = json_decode($params['connection']['settings'], true);
- $params['misp_organisation'] = $this->getSetOrg($params);
- $params['sync_user'] = $this->createSyncUser($params);
- return [
- 'email' => $params['sync_user']['email'],
- 'authkey' => $params['sync_user']['authkey'],
- 'url' => $params['connection_settings']['url']
- ];
+ $params['body'] = $user;
+ $response = $this->postData(sprintf('/admin/users/edit/%s', $userID), $params);
+ if (!$response->isOk()) {
+ throw new MethodNotAllowedException(__('Could not edit the user in MISP.'));
+ }
+ return $response->getJson()['User'];
}
}
diff --git a/src/Lib/default/local_tool_interconnectors/CommonTools.php b/src/Lib/default/local_tool_interconnectors/CommonTools.php
new file mode 100644
index 0000000..cb50bc0
--- /dev/null
+++ b/src/Lib/default/local_tool_interconnectors/CommonTools.php
@@ -0,0 +1,24 @@
+connects;
+ }
+}
+
+?>
diff --git a/src/Lib/default/local_tool_interconnectors/MispToMispInterconnector.php b/src/Lib/default/local_tool_interconnectors/MispToMispInterconnector.php
new file mode 100644
index 0000000..ef61de6
--- /dev/null
+++ b/src/Lib/default/local_tool_interconnectors/MispToMispInterconnector.php
@@ -0,0 +1,20 @@
+
diff --git a/src/Model/Entity/AppModel.php b/src/Model/Entity/AppModel.php
index d4704fe..84c86b3 100644
--- a/src/Model/Entity/AppModel.php
+++ b/src/Model/Entity/AppModel.php
@@ -6,5 +6,8 @@ use Cake\ORM\Entity;
class AppModel extends Entity
{
-
+ public function getAccessibleFieldForNew(): array
+ {
+ return $this->_accessibleOnNew ?? [];
+ }
}
diff --git a/src/Model/Entity/Inbox.php b/src/Model/Entity/Inbox.php
index a8f546e..820e29d 100644
--- a/src/Model/Entity/Inbox.php
+++ b/src/Model/Entity/Inbox.php
@@ -7,5 +7,14 @@ use Cake\ORM\Entity;
class Inbox extends AppModel
{
+ protected $_virtual = ['local_tool_connector_name'];
+ protected function _getLocalToolConnectorName()
+ {
+ $localConnectorName = null;
+ if (!empty($this->data) && !empty($this->data['connectorName'])) {
+ $localConnectorName = $this->data['connectorName'];
+ }
+ return $localConnectorName;
+ }
}
diff --git a/src/Model/Entity/Individual.php b/src/Model/Entity/Individual.php
index ef8e5db..a14e339 100644
--- a/src/Model/Entity/Individual.php
+++ b/src/Model/Entity/Individual.php
@@ -7,5 +7,13 @@ use Cake\ORM\Entity;
class Individual extends AppModel
{
+ protected $_accessible = [
+ '*' => true,
+ 'id' => false,
+ 'uuid' => false,
+ ];
+ protected $_accessibleOnNew = [
+ 'uuid' => true,
+ ];
}
diff --git a/src/Model/Entity/Organisation.php b/src/Model/Entity/Organisation.php
index 89fb838..bc78378 100644
--- a/src/Model/Entity/Organisation.php
+++ b/src/Model/Entity/Organisation.php
@@ -7,5 +7,13 @@ use Cake\ORM\Entity;
class Organisation extends AppModel
{
+ protected $_accessible = [
+ '*' => true,
+ 'id' => false,
+ 'uuid' => false,
+ ];
+ protected $_accessibleOnNew = [
+ 'uuid' => true,
+ ];
}
diff --git a/src/Model/Entity/RemoteToolConnection.php b/src/Model/Entity/RemoteToolConnection.php
new file mode 100644
index 0000000..164ddd8
--- /dev/null
+++ b/src/Model/Entity/RemoteToolConnection.php
@@ -0,0 +1,11 @@
+ true,
+ 'id' => false,
+ 'uuid' => false,
+ 'organisation_id' => false,
+ 'user_id' => false,
+ ];
+
+ protected $_accessibleOnNew = [
+ 'uuid' => true,
+ 'organisation_id' => true,
+ 'user_id' => true,
+ ];
+}
diff --git a/src/Model/Table/BroodsTable.php b/src/Model/Table/BroodsTable.php
index 98a62ae..4502a76 100644
--- a/src/Model/Table/BroodsTable.php
+++ b/src/Model/Table/BroodsTable.php
@@ -5,7 +5,10 @@ namespace App\Model\Table;
use App\Model\Table\AppTable;
use Cake\ORM\Table;
use Cake\Validation\Validator;
+use Cake\Core\Configure;
use Cake\Http\Client;
+use Cake\Http\Client\Response;
+use Cake\Http\Exception\NotFoundException;
use Cake\ORM\TableRegistry;
use Cake\Error\Debugger;
@@ -26,18 +29,40 @@ class BroodsTable extends AppTable
return $validator;
}
+ public function genHTTPClient(Object $brood, array $options=[]): Object
+ {
+ $defaultOptions = [
+ 'headers' => [
+ 'Authorization' => $brood->authkey,
+ ],
+ ];
+ if (empty($options['type'])) {
+ $options['type'] = 'json';
+ }
+ $options = array_merge($defaultOptions, $options);
+ $http = new Client($options);
+ return $http;
+ }
+
+ public function HTTPClientGET(String $relativeURL, Object $brood, array $data=[], array $options=[]): Object
+ {
+ $http = $this->genHTTPClient($brood, $options);
+ $url = sprintf('%s%s', $brood->url, $relativeURL);
+ return $http->get($url, $data, $options);
+ }
+
+ public function HTTPClientPOST(String $relativeURL, Object $brood, $data, array $options=[]): Object
+ {
+ $http = $this->genHTTPClient($brood, $options);
+ $url = sprintf('%s%s', $brood->url, $relativeURL);
+ return $http->post($url, $data, $options);
+ }
+
public function queryStatus($id)
{
$brood = $this->find()->where(['id' => $id])->first();
- $http = new Client();
$start = microtime(true);
- $response = $http->get($brood['url'] . '/instance/status.json', [], [
- 'headers' => [
- 'Authorization' => $brood['authkey'],
- 'Accept' => 'Application/json',
- 'Content-type' => 'Application/json'
- ]
- ]);
+ $response = $this->HTTPClientGET('/instance/status.json', $brood);
$ping = ((int)(100 * (microtime(true) - $start)));
$errors = [
403 => [
@@ -81,15 +106,8 @@ class BroodsTable extends AppTable
if (empty($brood)) {
throw new NotFoundException(__('Brood not found'));
}
- $http = new Client();
$filterQuery = empty($filter) ? '' : '?quickFilter=' . urlencode($filter);
- $response = $http->get($brood['url'] . '/' . $scope . '/index.json' . $filterQuery , [], [
- 'headers' => [
- 'Authorization' => $brood['authkey'],
- 'Accept' => 'Application/json',
- 'Content-type' => 'Application/json'
- ]
- ]);
+ $response = $this->HTTPClientGET(sprintf('/%s/index.json%s', $scope, $filterQuery), $brood);
if ($response->isOk()) {
return $response->getJson();
} else {
@@ -97,6 +115,7 @@ class BroodsTable extends AppTable
}
}
+ // TODO: Delete this function?
public function downloadAndCapture($brood_id, $object_id, $scope, $path)
{
$query = $this->find();
@@ -104,14 +123,7 @@ class BroodsTable extends AppTable
if (empty($brood)) {
throw new NotFoundException(__('Brood not found'));
}
- $http = new Client();
- $response = $http->get($brood['url'] . '/' . $scope . '/view/' . $org_id . '/index.json' , [], [
- 'headers' => [
- 'Authorization' => $brood['authkey'],
- 'Accept' => 'Application/json',
- 'Content-type' => 'Application/json'
- ]
- ]);
+ $response = $this->HTTPClientGET(sprintf('/%s/view/%s.json', $scope, $org_id), $brood);
if ($response->isOk()) {
$org = $response->getJson();
$this->Organisation = TableRegistry::getTableLocator()->get('Organisations');
@@ -129,14 +141,7 @@ class BroodsTable extends AppTable
if (empty($brood)) {
throw new NotFoundException(__('Brood not found'));
}
- $http = new Client();
- $response = $http->get($brood['url'] . '/organisations/view/' . $org_id . '/index.json' , [], [
- 'headers' => [
- 'Authorization' => $brood['authkey'],
- 'Accept' => 'Application/json',
- 'Content-type' => 'Application/json'
- ]
- ]);
+ $response = $this->HTTPClientGET(sprintf('/organisations/view/%s.json', $org_id), $brood);
if ($response->isOk()) {
$org = $response->getJson();
$this->Organisation = TableRegistry::getTableLocator()->get('Organisations');
@@ -154,18 +159,29 @@ class BroodsTable extends AppTable
if (empty($brood)) {
throw new NotFoundException(__('Brood not found'));
}
- $http = new Client();
- $response = $http->get($brood['url'] . '/individuals/view/' . $individual_id . '/index.json' , [], [
- 'headers' => [
- 'Authorization' => $brood['authkey'],
- 'Accept' => 'Application/json',
- 'Content-type' => 'Application/json'
- ]
- ]);
+ $response = $this->HTTPClientGET(sprintf('/individuals/view/%s.json', $individual_id), $brood);
if ($response->isOk()) {
- $org = $response->getJson();
- $this->Individual = TableRegistry::getTableLocator()->get('Individual');
- $result = $this->Individual->captureIndividual($individual);
+ $individual = $response->getJson();
+ $this->Individuals = TableRegistry::getTableLocator()->get('Individuals');
+ $result = $this->Individuals->captureIndividual($individual);
+ return $result;
+ } else {
+ return false;
+ }
+ }
+
+ public function downloadSharingGroup($brood_id, $sg_id, $user_id)
+ {
+ $query = $this->find();
+ $brood = $query->where(['id' => $brood_id])->first();
+ if (empty($brood)) {
+ throw new NotFoundException(__('Brood not found'));
+ }
+ $response = $this->HTTPClientGET(sprintf('/sharing-groups/view/%s.json', $sg_id), $brood);
+ if ($response->isOk()) {
+ $individual = $response->getJson();
+ $this->SharingGroups = TableRegistry::getTableLocator()->get('SharingGroups');
+ $result = $this->SharingGroups->captureSharingGroup($individual, $user_id);
return $result;
} else {
return false;
@@ -179,17 +195,161 @@ class BroodsTable extends AppTable
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'
- ]);
+ $response = $this->HTTPClientGET('/localTools/exposedTools', $brood);
if ($response->isOk()) {
return $response->getJson();
} else {
return false;
}
}
+
+ public function sendRequest($brood, $urlPath, $methodPost = true, $data = []): Response
+ {
+ if ($methodPost) {
+ $response = $this->HTTPClientPOST($urlPath, $brood, json_encode($data));
+ } else {
+ $response = $this->HTTPClientGET($urlPath, $brood, $data);
+ }
+ return $response;
+ }
+
+ private function injectRequiredData($params, $data): Array
+ {
+ $data['connectorName'] = $params['remote_tool']['connector'];
+ $data['cerebrateURL'] = Configure::read('App.fullBaseUrl');
+ $data['local_tool_id'] = $params['connection']['id'];
+ $data['remote_tool_id'] = $params['remote_tool']['id'];
+ $data['tool_name'] = $params['connection']['name'];
+ return $data;
+ }
+
+ public function sendLocalToolConnectionRequest($params, $data): array
+ {
+ $url = '/inbox/createEntry/LocalTool/IncomingConnectionRequest';
+ $data = $this->injectRequiredData($params, $data);
+ try {
+ $response = $this->sendRequest($params['remote_cerebrate'], $url, true, $data);
+ $jsonReply = $response->getJson();
+ if (empty($jsonReply['success'])) {
+ $jsonReply = $this->handleMessageNotCreated($params['remote_cerebrate'], $url, $data, 'LocalTool', 'IncomingConnectionRequest', $response, $params, 'STATE_INITIAL');
+ }
+ } catch (NotFoundException $e) {
+ $jsonReply = $this->handleSendingFailed($params['remote_cerebrate'], $url, $data, 'LocalTool', 'IncomingConnectionRequest', $e, $params, 'STATE_INITIAL');
+ }
+ return $jsonReply;
+ }
+
+ public function sendLocalToolAcceptedRequest($params, $data): array
+ {
+ $url = '/inbox/createEntry/LocalTool/AcceptedRequest';
+ $data = $this->injectRequiredData($params, $data);
+ try {
+ $response = $this->sendRequest($params['remote_cerebrate'], $url, true, $data);
+ $jsonReply = $response->getJson();
+ if (empty($jsonReply['success'])) {
+ $jsonReply = $this->handleMessageNotCreated($params['remote_cerebrate'], $url, $data, 'LocalTool', 'AcceptedRequest', $response, $params, 'STATE_CONNECTED');
+ } else {
+ $this->setRemoteToolConnectionStatus($params, 'STATE_CONNECTED');
+ }
+ } catch (NotFoundException $e) {
+ $jsonReply = $this->handleSendingFailed($params['remote_cerebrate'], $url, $data, 'LocalTool', 'AcceptedRequest', $e, $params, 'STATE_CONNECTED');
+ }
+ return $jsonReply;
+ }
+
+ public function sendLocalToolDeclinedRequest($params, $data): array
+ {
+ $url = '/inbox/createEntry/LocalTool/DeclinedRequest';
+ $data = $this->injectRequiredData($params, $data);
+ try {
+ $response = $this->sendRequest($params['remote_cerebrate'], $url, true, $data);
+ $jsonReply = $response->getJson();
+ if (empty($jsonReply['success'])) {
+ $jsonReply = $this->handleMessageNotCreated($params['remote_cerebrate'], $url, $data, 'LocalTool', 'AcceptedRequest', $response, $params, 'STATE_DECLINED');
+ }
+ } catch (NotFoundException $e) {
+ $jsonReply = $this->handleSendingFailed($params['remote_cerebrate'], $url, $data, 'LocalTool', 'AcceptedRequest', $e, $params, 'STATE_DECLINED');
+ }
+ return $jsonReply;
+ }
+
+ /**
+ * handleSendingFailed - Handle the case if the request could not be sent or if the remote rejected the connection request
+ *
+ * @param Object $response
+ * @return array
+ */
+ private function handleSendingFailed($brood, $url, $data, $model, $action, $e, $params, $next_connector_state): array
+ {
+ $connector = $params['connector'][$params['remote_tool']['connector']];
+ $reason = [
+ 'message' => __('Failed to send message to remote cerebrate. It has been placed in the outbox.'),
+ 'errors' => [$e->getMessage()],
+ ];
+ $outboxSaveResult = $this->saveErrorInOutbox($brood, $url, $data, $reasonMessage, $params, $next_connector_state);
+ $connector->remoteToolConnectionStatus($params, $connector::STATE_SENDING_ERROR);
+ $creationResult = [
+ 'success' => false,
+ 'message' => $reason['message'],
+ 'errors' => $reason['errors'],
+ 'placed_in_outbox' => !empty($outboxSaveResult['success']),
+ ];
+ return $creationResult;
+ }
+
+ /**
+ * handleMessageNotCreated - Handle the case if the request was sent but the remote brood did not save the message in the inbox
+ *
+ * @param Object $response
+ * @return array
+ */
+ private function handleMessageNotCreated($brood, $url, $data, $model, $action, $response, $params, $next_connector_state): array
+ {
+ $connector = $params['connector'][$params['remote_tool']['connector']];
+ $responseErrors = $response->getStringBody();
+ if (!is_null($response->getJson())) {
+ $responseErrors = $response->getJson()['errors'] ?? $response->getJson()['message'];
+ }
+ $reason = [
+ 'message' => __('Message rejected by the remote cerebrate. It has been placed in the outbox.'),
+ 'errors' => [$responseErrors],
+ ];
+ $outboxSaveResult = $this->saveErrorInOutbox($brood, $url, $data, $reason, $model, $action, $params, $next_connector_state);
+ $connector->remoteToolConnectionStatus($params, $connector::STATE_SENDING_ERROR);
+ $creationResult = [
+ 'success' => false,
+ 'message' => $reason['message'],
+ 'errors' => $reason['errors'],
+ 'placed_in_outbox' => !empty($outboxSaveResult['success']),
+ ];
+ return $creationResult;
+ }
+
+ private function saveErrorInOutbox($brood, $url, $data, $reason, $model, $action, $params, $next_connector_state): array
+ {
+ $this->OutboxProcessors = TableRegistry::getTableLocator()->get('OutboxProcessors');
+ $processor = $this->OutboxProcessors->getProcessor('Broods', 'ResendFailedMessage');
+ $entryData = [
+ 'data' => [
+ 'sent' => $data,
+ 'url' => $url,
+ 'brood_id' => $brood->id,
+ 'reason' => $reason,
+ 'local_tool_id' => $params['connection']['id'],
+ 'remote_tool' => $params['remote_tool'],
+ 'next_connector_state' => $next_connector_state,
+ ],
+ 'brood' => $brood,
+ 'model' => $model,
+ 'action' => $action,
+ ];
+ $creationResult = $processor->create($entryData);
+ return $creationResult;
+ }
+
+ private function setRemoteToolConnectionStatus($params, String $status): void
+ {
+ $connector = $params['connector'][$params['remote_tool']['connector']];
+ $connector->remoteToolConnectionStatus($params, constant(get_class($connector) . '::' . $status));
+ }
}
diff --git a/src/Model/Table/InboxProcessorsTable.php b/src/Model/Table/InboxProcessorsTable.php
new file mode 100644
index 0000000..4bb1aea
--- /dev/null
+++ b/src/Model/Table/InboxProcessorsTable.php
@@ -0,0 +1,156 @@
+ [
+ 'ToolInterconnection' => false,
+ 'OneWaySynchronization' => false,
+ ],
+ 'Proposal' => [
+ 'ProposalEdit' => false,
+ ],
+ 'Synchronisation' => [
+ 'DataExchange' => false,
+ ],
+ 'User' => [
+ 'Registration' => true,
+ ],
+ ];
+
+ public function initialize(array $config): void
+ {
+ parent::initialize($config);
+ $this->loadProcessors();
+ }
+
+ public function getProcessor($scope, $action=null)
+ {
+ if (isset($this->inboxProcessors[$scope])) {
+ if (is_null($action)) {
+ return $this->inboxProcessors[$scope];
+ } else if (!empty($this->inboxProcessors[$scope]->{$action})) {
+ return $this->inboxProcessors[$scope]->{$action};
+ } else {
+ throw new \Exception(__('Processor {0}.{1} not found', $scope, $action));
+ }
+ }
+ throw new MissingInboxProcessorException(__('Processor not found'));
+ }
+
+ public function getLocalToolProcessor($action, $connectorName)
+ {
+ $scope = "LocalTool";
+ $specificScope = "{$connectorName}LocalTool";
+ try { // try to get specific processor for module name or fall back to generic local tool processor
+ $processor = $this->getProcessor($specificScope, $action);
+ } catch (MissingInboxProcessorException $e) {
+ $processor = $this->getProcessor($scope, $action);
+ }
+ return $processor;
+ }
+
+ public function listProcessors($scope=null)
+ {
+ if (is_null($scope)) {
+ return $this->inboxProcessors;
+ } else {
+ if (isset($this->inboxProcessors[$scope])) {
+ return $this->inboxProcessors[$scope];
+ } else {
+ throw new MissingInboxProcessorException(__('Processors for {0} not found', $scope));
+ }
+ }
+ }
+
+ private function loadProcessors()
+ {
+ $processorDir = new Folder($this->processorsDirectory);
+ $processorFiles = $processorDir->find('.*InboxProcessor\.php', true);
+ foreach ($processorFiles as $processorFile) {
+ if ($processorFile == 'GenericInboxProcessor.php') {
+ continue;
+ }
+ $processorMainClassName = str_replace('.php', '', $processorFile);
+ $processorMainClassNameShort = str_replace('InboxProcessor.php', '', $processorFile);
+ $processorMainClass = $this->getProcessorClass($processorDir->pwd() . DS . $processorFile, $processorMainClassName);
+ if (is_object($processorMainClass)) {
+ $this->inboxProcessors[$processorMainClassNameShort] = $processorMainClass;
+ foreach ($this->inboxProcessors[$processorMainClassNameShort]->getRegisteredActions() as $registeredAction) {
+ $scope = $this->inboxProcessors[$processorMainClassNameShort]->getScope();
+ if (!empty($this->enabledProcessors[$scope][$registeredAction])) {
+ $this->inboxProcessors[$processorMainClassNameShort]->{$registeredAction}->enabled = true;
+ } else {
+ $this->inboxProcessors[$processorMainClassNameShort]->{$registeredAction}->enabled = false;
+ }
+ }
+ } else {
+ $this->inboxProcessors[$processorMainClassNameShort] = new \stdClass();
+ $this->inboxProcessors[$processorMainClassNameShort]->{$registeredAction} = new \stdClass();
+ $this->inboxProcessors[$processorMainClassNameShort]->{$registeredAction}->action = "N/A";
+ $this->inboxProcessors[$processorMainClassNameShort]->{$registeredAction}->enabled = false;
+ $this->inboxProcessors[$processorMainClassNameShort]->{$registeredAction}->error = $processorMainClass;
+ }
+ }
+ }
+
+ /**
+ * getProcessorClass
+ *
+ * @param string $filePath
+ * @param string $processorMainClassName
+ * @return object|string Object loading success, string containing the error if failure
+ */
+ private function getProcessorClass($filePath, $processorMainClassName)
+ {
+ try {
+ require_once($filePath);
+ try {
+ $reflection = new \ReflectionClass($processorMainClassName);
+ } catch (\ReflectionException $e) {
+ return $e->getMessage();
+ }
+ $processorMainClass = $reflection->newInstance(true);
+ if ($processorMainClass->checkLoading() === 'Assimilation successful!') {
+ return $processorMainClass;
+ }
+ } catch (Exception $e) {
+ return $e->getMessage();
+ }
+ }
+
+ /**
+ * createInboxEntry
+ *
+ * @param Object|Array $processor can either be the processor object or an array containing data to fetch it
+ * @param Array $data
+ * @return Array
+ */
+ public function createInboxEntry($processor, $data)
+ {
+ if (!is_object($processor) && !is_array($processor)) {
+ throw new MethodNotAllowedException(__("Invalid processor passed"));
+ }
+ if (is_array($processor)) {
+ if (empty($processor['scope']) || empty($processor['action'])) {
+ throw new MethodNotAllowedException(__("Invalid data passed. Missing either `scope` or `action`"));
+ }
+ $processor = $this->getProcessor('User', 'Registration');
+ }
+ return $processor->create($data);
+ }
+}
diff --git a/src/Model/Table/InboxTable.php b/src/Model/Table/InboxTable.php
index 93a7f22..21d6da6 100644
--- a/src/Model/Table/InboxTable.php
+++ b/src/Model/Table/InboxTable.php
@@ -7,6 +7,7 @@ use Cake\Database\Type;
use Cake\ORM\Table;
use Cake\ORM\RulesChecker;
use Cake\Validation\Validator;
+use Cake\Http\Exception\NotFoundException;
Type::map('json', 'Cake\Database\Type\JsonType');
@@ -44,7 +45,7 @@ class InboxTable extends AppTable
->notEmptyString('title')
->notEmptyString('origin')
->datetime('created')
-
+
->requirePresence([
'scope' => ['message' => __('The field `scope` is required')],
'action' => ['message' => __('The field `action` is required')],
@@ -62,4 +63,33 @@ class InboxTable extends AppTable
return $rules;
}
+
+ public function checkUserBelongsToBroodOwnerOrg($user, $entryData) {
+ $this->Broods = \Cake\ORM\TableRegistry::getTableLocator()->get('Broods');
+ $this->Individuals = \Cake\ORM\TableRegistry::getTableLocator()->get('Individuals');
+ $errors = [];
+ $brood = $this->Broods->find()
+ ->where(['url' => $entryData['origin']])
+ ->first();
+ if (empty($brood)) {
+ $errors[] = __('Unkown brood `{0}`', $entryData['data']['cerebrateURL']);
+ }
+
+ $found = false;
+ foreach ($user->individual->organisations as $organisations) {
+ if ($organisations->id == $brood->organisation_id) {
+ $found = true;
+ }
+ }
+ if (!$found) {
+ $errors[] = __('User `{0}` is not part of the brood\'s organisation. Make sure `{0}` is aligned with the organisation owning the brood.', $user->individual->email);
+ }
+ return $errors;
+ }
+
+ public function createEntry($entryData)
+ {
+ $savedEntry = $this->save($entryData);
+ return $savedEntry;
+ }
}
diff --git a/src/Model/Table/IndividualsTable.php b/src/Model/Table/IndividualsTable.php
index d0df577..da73d92 100644
--- a/src/Model/Table/IndividualsTable.php
+++ b/src/Model/Table/IndividualsTable.php
@@ -55,26 +55,20 @@ class IndividualsTable extends AppTable
return null;
}
if (empty($existingIndividual)) {
- $entity = $this->newEntity($individual, ['associated' => []]);
- if (!$this->save($entity)) {
- return null;
- }
- $individual = $entity;
+ $entityToSave = $this->newEmptyEntity();
+ $this->patchEntity($entityToSave, $individual, [
+ 'accessibleFields' => $entityToSave->getAccessibleFieldForNew()
+ ]);
} else {
- $reserved = ['id', 'uuid', 'metaFields'];
- foreach ($individual as $field => $value) {
- if (in_array($field, $reserved)) {
- continue;
- }
- $existingIndividual->$field = $value;
- }
- if (!$this->save($existingIndividual, ['associated' => false])) {
- return null;
- }
- $individual = $existingIndividua;
+ $this->patchEntity($existingIndividual, $individual);
+ $entityToSave = $existingIndividual;
}
- $this->postCaptureActions($individual);
- return $individual->id;
+ $savedEntity = $this->save($entityToSave, ['associated' => false]);
+ if (!$savedEntity) {
+ return null;
+ }
+ $this->postCaptureActions($savedEntity);
+ return $savedEntity->id;
}
public function postCaptureActions($individual): void
diff --git a/src/Model/Table/LocalToolsTable.php b/src/Model/Table/LocalToolsTable.php
index f5e1b46..9d818ef 100644
--- a/src/Model/Table/LocalToolsTable.php
+++ b/src/Model/Table/LocalToolsTable.php
@@ -73,6 +73,12 @@ class LocalToolsTable extends AppTable
throw new NotFoundException(__('Invalid connector module action requested.'));
}
+ public function getConnectorByToolName($toolName): array
+ {
+ $toolName = sprintf('%sConnector', ucfirst(strtolower($toolName)));
+ return $this->getConnectors($toolName);
+ }
+
public function getConnectors(string $name = null): array
{
$connectors = [];
@@ -95,6 +101,29 @@ class LocalToolsTable extends AppTable
return $connectors;
}
+ public function getInterconnectors(string $name = null): array
+ {
+ $connectors = [];
+ $dirs = [
+ ROOT . '/src/Lib/default/local_tool_interconnectors',
+ ROOT . '/src/Lib/custom/local_tool_interconnectors'
+ ];
+ foreach ($dirs as $dir) {
+ $dir = new Folder($dir);
+ $files = $dir->find('.*Interconnector\.php');
+ foreach ($files as $file) {
+ require_once($dir->pwd() . '/'. $file);
+ $className = substr($file, 0, -4);
+ $classNamespace = '\\' . $className . '\\' . $className;
+ $tempClass = new $classNamespace;
+ if (empty($name) || $tempClass->getConnectors()[0] === $name) {
+ $connectors[$tempClass->getConnectors()[0]][] = new $classNamespace;
+ }
+ }
+ }
+ return $connectors;
+ }
+
public function extractMeta(array $connector_classes, bool $includeConnections = false): array
{
$connectors = [];
@@ -197,6 +226,17 @@ class LocalToolsTable extends AppTable
}
public function encodeConnection(array $params): array
+ {
+ $params = $this->buildConnectionParams($params);
+ $localResult = $params['connector'][$params['remote_tool']['connector']]->initiateConnectionWrapper($params);
+ $inboxResult = $this->sendEncodedConnection($params, $localResult);
+ return [
+ 'inboxResult' => $inboxResult,
+ 'localResult' => $localResult
+ ];
+ }
+
+ public function buildConnectionParams(array $params): array
{
$remote_tool = $this->getRemoteToolById($params);
$broods = \Cake\ORM\TableRegistry::getTableLocator()->get('Broods');
@@ -207,13 +247,54 @@ class LocalToolsTable extends AppTable
if (empty($connector[$remote_tool['connector']])) {
throw new NotFoundException(__('No valid connector found for the remote tool.'));
}
- $result = $connector[$remote_tool['connector']]->connectToRemoteTool([
+ return [
'remote_cerebrate' => $remote_cerebrate,
'remote_org' => $remote_org,
'remote_tool' => $remote_tool,
'connector' => $connector,
- 'connection' => $connection
- ]);
- return $result;
+ 'connection' => $connection,
+ //'message' =>
+ ];
+ }
+
+ public function appendLocalToolConnections(int $brood_id, array $tool): array
+ {
+ $remoteToolConnections = \Cake\ORM\TableRegistry::getTableLocator()->get('RemoteToolConnections');
+ $connections = $remoteToolConnections->find()->where(['remote_tool_id' => $tool['id'], 'brood_id' => $brood_id])->toArray();
+ $local_tools = [];
+ foreach ($connections as $k => $connection) {
+ $temp = $this->find()->where(['id' => $connection['local_tool_id']])->select(['id', 'name'])->enableHydration(false)->first();
+ $temp['status'] = $connection['status'];
+ $local_tools[] = $temp;
+ }
+ return $local_tools;
+ }
+
+ public function sendEncodedConnection($params, $encodedConnection)
+ {
+ $this->Broods = \Cake\ORM\TableRegistry::getTableLocator()->get('Broods');
+ $jsonReply = $this->Broods->sendLocalToolConnectionRequest($params, $encodedConnection);
+ return $jsonReply;
+ }
+
+ public function findConnectable($local_tool): array
+ {
+ $connectors = $this->getInterconnectors($local_tool['connector']);
+ $validTargets = [];
+ if (!empty($connectors)) {
+ foreach ($connectors[$local_tool['connector']] as $connector) {
+ $validTargets[$connector['connects'][1]] = 1;
+ }
+ }
+
+ }
+
+ public function fetchConnection($id): object
+ {
+ $connection = $this->find()->where(['id' => $id])->first();
+ if (empty($connection)) {
+ throw new NotFoundException(__('Local tool not found.'));
+ }
+ return $connection;
}
}
diff --git a/src/Model/Table/OrganisationsTable.php b/src/Model/Table/OrganisationsTable.php
index c23ee7b..8587864 100644
--- a/src/Model/Table/OrganisationsTable.php
+++ b/src/Model/Table/OrganisationsTable.php
@@ -55,9 +55,6 @@ class OrganisationsTable extends AppTable
public function captureOrg($org): ?int
{
- if (!empty($org['id'])) {
- unset($org['id']);
- }
if (!empty($org['uuid'])) {
$existingOrg = $this->find()->where([
'uuid' => $org['uuid']
@@ -66,27 +63,20 @@ class OrganisationsTable extends AppTable
return null;
}
if (empty($existingOrg)) {
- $data = $this->newEmptyEntity();
- $data = $this->patchEntity($data, $org, ['associated' => []]);
- if (!$this->save($data)) {
- return null;
- }
- $savedOrg = $data;
+ $entityToSave = $this->newEmptyEntity();
+ $this->patchEntity($entityToSave, $org, [
+ 'accessibleFields' => $entityToSave->getAccessibleFieldForNew()
+ ]);
} else {
- $reserved = ['id', 'uuid', 'metaFields'];
- foreach ($org as $field => $value) {
- if (in_array($field, $reserved)) {
- continue;
- }
- $existingOrg->$field = $value;
- }
- if (!$this->save($existingOrg)) {
- return null;
- }
- $savedOrg = $existingOrg;
+ $this->patchEntity($existingOrg, $org);
+ $entityToSave = $existingOrg;
}
- $this->postCaptureActions($savedOrg->id, $org);
- return $savedOrg->id;
+ $savedEntity = $this->save($entityToSave, ['associated' => false]);
+ if (!$savedEntity) {
+ return null;
+ }
+ $this->postCaptureActions($savedEntity->id, $org);
+ return $savedEntity->id;
}
public function postCaptureActions($id, $org)
diff --git a/src/Model/Table/OutboxProcessorsTable.php b/src/Model/Table/OutboxProcessorsTable.php
new file mode 100644
index 0000000..da26692
--- /dev/null
+++ b/src/Model/Table/OutboxProcessorsTable.php
@@ -0,0 +1,136 @@
+ [
+ 'ResendFailedMessageProcessor' => true,
+ ],
+ ];
+
+ public function initialize(array $config): void
+ {
+ parent::initialize($config);
+ if (empty($this->outboxProcessors)) {
+ $this->loadProcessors();
+ }
+ }
+
+ public function getProcessor($scope, $action=null)
+ {
+ if (isset($this->outboxProcessors[$scope])) {
+ if (is_null($action)) {
+ return $this->outboxProcessors[$scope];
+ } else if (!empty($this->outboxProcessors[$scope]->{$action})) {
+ return $this->outboxProcessors[$scope]->{$action};
+ } else {
+ throw new \Exception(__('Processor {0}.{1} not found', $scope, $action));
+ }
+ }
+ throw new MissingOutboxProcessorException(__('Processor not found'));
+ }
+
+ public function listProcessors($scope=null)
+ {
+ if (is_null($scope)) {
+ return $this->outboxProcessors;
+ } else {
+ if (isset($this->outboxProcessors[$scope])) {
+ return $this->outboxProcessors[$scope];
+ } else {
+ throw new MissingOutboxProcessorException(__('Processors for {0} not found', $scope));
+ }
+ }
+ }
+
+ private function loadProcessors()
+ {
+ $processorDir = new Folder($this->processorsDirectory);
+ $processorFiles = $processorDir->find('.*OutboxProcessor\.php', true);
+ foreach ($processorFiles as $processorFile) {
+ if ($processorFile == 'GenericOutboxProcessor.php') {
+ continue;
+ }
+ $processorMainClassName = str_replace('.php', '', $processorFile);
+ $processorMainClassNameShort = str_replace('OutboxProcessor.php', '', $processorFile);
+ $processorMainClass = $this->getProcessorClass($processorDir->pwd() . DS . $processorFile, $processorMainClassName);
+ if (is_object($processorMainClass)) {
+ $this->outboxProcessors[$processorMainClassNameShort] = $processorMainClass;
+ foreach ($this->outboxProcessors[$processorMainClassNameShort]->getRegisteredActions() as $registeredAction) {
+ $scope = $this->outboxProcessors[$processorMainClassNameShort]->getScope();
+ if (!empty($this->enabledProcessors[$scope][$registeredAction])) {
+ $this->outboxProcessors[$processorMainClassNameShort]->{$registeredAction}->enabled = true;
+ } else {
+ $this->outboxProcessors[$processorMainClassNameShort]->{$registeredAction}->enabled = false;
+ }
+ }
+ } else {
+ $this->outboxProcessors[$processorMainClassNameShort] = new \stdClass();
+ $this->outboxProcessors[$processorMainClassNameShort]->{$registeredAction} = new \stdClass();
+ $this->outboxProcessors[$processorMainClassNameShort]->{$registeredAction}->action = "N/A";
+ $this->outboxProcessors[$processorMainClassNameShort]->{$registeredAction}->enabled = false;
+ $this->outboxProcessors[$processorMainClassNameShort]->{$registeredAction}->error = $processorMainClass;
+ }
+ }
+ }
+
+ /**
+ * getProcessorClass
+ *
+ * @param string $filePath
+ * @param string $processorMainClassName
+ * @return object|string Object loading success, string containing the error if failure
+ */
+ private function getProcessorClass($filePath, $processorMainClassName)
+ {
+ try {
+ require_once($filePath);
+ try {
+ $reflection = new \ReflectionClass($processorMainClassName);
+ } catch (\ReflectionException $e) {
+ return $e->getMessage();
+ }
+ $processorMainClass = $reflection->newInstance(true);
+ if ($processorMainClass->checkLoading() === 'Assimilation successful!') {
+ return $processorMainClass;
+ }
+ } catch (Exception $e) {
+ return $e->getMessage();
+ }
+ }
+
+ /**
+ * createOutboxEntry
+ *
+ * @param Object|Array $processor can either be the processor object or an array containing data to fetch it
+ * @param Array $data
+ * @return Array
+ */
+ public function createOutboxEntry($processor, $data)
+ {
+ if (!is_object($processor) && !is_array($processor)) {
+ throw new MethodNotAllowedException(__("Invalid processor passed"));
+ }
+ if (is_array($processor)) {
+ if (empty($processor['scope']) || empty($processor['action'])) {
+ throw new MethodNotAllowedException(__("Invalid data passed. Missing either `scope` or `action`"));
+ }
+ $processor = $this->getProcessor('User', 'Registration');
+ }
+ return $processor->create($data);
+ }
+}
diff --git a/src/Model/Table/OutboxTable.php b/src/Model/Table/OutboxTable.php
new file mode 100644
index 0000000..05ed8fc
--- /dev/null
+++ b/src/Model/Table/OutboxTable.php
@@ -0,0 +1,70 @@
+addBehavior('UUID');
+ $this->addBehavior('Timestamp', [
+ 'events' => [
+ 'Model.beforeSave' => [
+ 'created' => 'new'
+ ]
+ ]
+ ]);
+
+ $this->belongsTo('Users');
+ $this->setDisplayField('title');
+ }
+
+ protected function _initializeSchema(TableSchemaInterface $schema): TableSchemaInterface
+ {
+ $schema->setColumnType('data', 'json');
+
+ return $schema;
+ }
+
+ public function validationDefault(Validator $validator): Validator
+ {
+ $validator
+ ->notEmptyString('scope')
+ ->notEmptyString('action')
+ ->notEmptyString('title')
+ ->datetime('created')
+
+ ->requirePresence([
+ 'scope' => ['message' => __('The field `scope` is required')],
+ 'action' => ['message' => __('The field `action` is required')],
+ 'title' => ['message' => __('The field `title` is required')],
+ ], 'create');
+ return $validator;
+ }
+
+ public function buildRules(RulesChecker $rules): RulesChecker
+ {
+ $rules->add($rules->existsIn('user_id', 'Users'), [
+ 'message' => 'The provided `user_id` does not exist'
+ ]);
+
+ return $rules;
+ }
+
+ public function createEntry($entryData, $user = null)
+ {
+ $savedEntry = $this->save($entryData);
+ return $savedEntry;
+ }
+}
diff --git a/src/Model/Table/RemoteToolConnectionsTable.php b/src/Model/Table/RemoteToolConnectionsTable.php
new file mode 100644
index 0000000..7e8cf21
--- /dev/null
+++ b/src/Model/Table/RemoteToolConnectionsTable.php
@@ -0,0 +1,27 @@
+BelongsTo(
+ 'LocalTools'
+ );
+ $this->setDisplayField('id');
+ }
+
+ public function validationDefault(Validator $validator): Validator
+ {
+ return $validator;
+ }
+}
diff --git a/src/Model/Table/RequestProcessorTable.php b/src/Model/Table/RequestProcessorTable.php
deleted file mode 100644
index e76fdab..0000000
--- a/src/Model/Table/RequestProcessorTable.php
+++ /dev/null
@@ -1,95 +0,0 @@
- [
- 'ToolInterconnection' => false,
- 'OneWaySynchronization' => false,
- ],
- 'Proposal' => [
- 'ProposalEdit' => false,
- ],
- 'Synchronisation' => [
- 'DataExchange' => false,
- ],
- 'User' => [
- 'Registration' => true,
- ],
- ];
-
- public function initialize(array $config): void
- {
- parent::initialize($config);
- $this->loadProcessors();
- }
-
- public function getProcessor($scope, $action=null)
- {
- if (isset($this->requestProcessors[$scope])) {
- if (is_null($action)) {
- return $this->requestProcessors[$scope];
- } else if (!empty($this->requestProcessors[$scope]->{$action})) {
- return $this->requestProcessors[$scope]->{$action};
- } else {
- throw new \Exception(__('Processor {0}.{1} not found', $scope, $action));
- }
- }
- throw new \Exception(__('Processor not found'), 1);
- }
-
- public function listProcessors($scope=null)
- {
- if (is_null($scope)) {
- return $this->requestProcessors;
- } else {
- if (isset($this->requestProcessors[$scope])) {
- return $this->requestProcessors[$scope];
- } else {
- throw new \Exception(__('Processors for {0} not found', $scope));
- }
- }
- }
-
- private function loadProcessors()
- {
- $processorDir = new Folder($this->processorsDirectory);
- $processorFiles = $processorDir->find('.*RequestProcessor\.php', true);
- foreach ($processorFiles as $processorFile) {
- if ($processorFile == 'GenericRequestProcessor.php') {
- continue;
- }
- $processorMainClassName = str_replace('.php', '', $processorFile);
- $processorMainClassNameShort = str_replace('RequestProcessor.php', '', $processorFile);
- $processorMainClass = $this->getProcessorClass($processorDir->pwd() . DS . $processorFile, $processorMainClassName);
- if ($processorMainClass !== false) {
- $this->requestProcessors[$processorMainClassNameShort] = $processorMainClass;
- foreach ($this->requestProcessors[$processorMainClassNameShort]->getRegisteredActions() as $registeredAction) {
- $scope = $this->requestProcessors[$processorMainClassNameShort]->getScope();
- if (!empty($this->enabledProcessors[$scope][$registeredAction])) {
- $this->requestProcessors[$processorMainClassNameShort]->{$registeredAction}->enabled = true;
- } else {
- $this->requestProcessors[$processorMainClassNameShort]->{$registeredAction}->enabled = false;
- }
- }
- }
- }
- }
-
- private function getProcessorClass($filePath, $processorMainClassName)
- {
- require_once($filePath);
- $reflection = new \ReflectionClass($processorMainClassName);
- $processorMainClass = $reflection->newInstance(true);
- if ($processorMainClass->checkLoading() === 'Assimilation successful!') {
- return $processorMainClass;
- }
- }
-}
diff --git a/src/Model/Table/SharingGroupsTable.php b/src/Model/Table/SharingGroupsTable.php
index 41cf20c..6f18157 100644
--- a/src/Model/Table/SharingGroupsTable.php
+++ b/src/Model/Table/SharingGroupsTable.php
@@ -46,9 +46,6 @@ class SharingGroupsTable extends AppTable
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']
@@ -57,41 +54,31 @@ class SharingGroupsTable extends AppTable
return null;
}
if (empty($existingSG)) {
- $data = $this->newEmptyEntity();
+ $entityToSave = $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;
+ $this->patchEntity($entityToSave, $input, [
+ 'accessibleFields' => $entityToSave->getAccessibleFieldForNew()
+ ]);
} 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->patchEntity($existingSG, $input);
+ $entityToSave = $existingSG;
}
- $this->postCaptureActions($savedSG->id, $input);
- return $savedSG->id;
+ $savedEntity = $this->save($entityToSave, ['associated' => false]);
+ if (!$savedEntity) {
+ return null;
+ }
+ $this->postCaptureActions($savedEntity, $input);
+ return $savedEntity->id;
}
- public function postCaptureActions($id, $input): void
+ public function postCaptureActions($savedEntity, $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);
+ $this->SharingGroupOrgs->link($savedEntity, $orgs);
}
}
diff --git a/src/View/Helper/BootstrapHelper.php b/src/View/Helper/BootstrapHelper.php
index b63e5f6..b5502d2 100644
--- a/src/View/Helper/BootstrapHelper.php
+++ b/src/View/Helper/BootstrapHelper.php
@@ -42,12 +42,15 @@
namespace App\View\Helper;
use Cake\View\Helper;
+use Cake\Utility\Hash;
use Cake\Utility\Inflector;
use Cake\Utility\Security;
use InvalidArgumentException;
class BootstrapHelper extends Helper
{
+ public $helpers = ['FontAwesome'];
+
public function tabs($options)
{
$bsTabs = new BootstrapTabs($options);
@@ -80,14 +83,50 @@ class BootstrapHelper extends Helper
public function modal($options)
{
- $bsButton = new BoostrapModal($options);
- return $bsButton->modal();
+ $bsModal = new BoostrapModal($options);
+ return $bsModal->modal();
+ }
+
+ public function card($options)
+ {
+ $bsCard = new BoostrapCard($options);
+ return $bsCard->card();
+ }
+
+ public function progress($options)
+ {
+ $bsProgress = new BoostrapProgress($options);
+ return $bsProgress->progress();
+ }
+
+ public function collapse($options, $content)
+ {
+ $bsCollapse = new BoostrapCollapse($options, $content, $this);
+ return $bsCollapse->collapse();
+ }
+
+ public function progressTimeline($options)
+ {
+ $bsProgressTimeline = new BoostrapProgressTimeline($options, $this);
+ return $bsProgressTimeline->progressTimeline();
}
}
class BootstrapGeneric
{
public static $variants = ['primary', 'secondary', 'success', 'danger', 'warning', 'info', 'light', 'dark', 'white', 'transparent'];
+ public static $textClassByVariants = [
+ 'primary' => 'text-white',
+ 'secondary' => 'text-white',
+ 'success' => 'text-white',
+ 'danger' => 'text-white',
+ 'warning' => 'text-black',
+ 'info' => 'text-white',
+ 'light' => 'text-black',
+ 'dark' => 'text-white',
+ 'white' => 'text-black',
+ 'transparent' => 'text-black'
+ ];
protected $allowedOptionValues = [];
protected $options = [];
@@ -146,6 +185,11 @@ class BootstrapGeneric
'arial-hidden' => 'true'
], '×'));
}
+
+ protected static function getTextClassForVariant($variant)
+ {
+ return !empty(self::$textClassByVariants[$variant]) ? self::$textClassByVariants[$variant] : 'text-black';
+ }
}
class BootstrapTabs extends BootstrapGeneric
@@ -543,7 +587,7 @@ class BoostrapTable extends BootstrapGeneric {
} else {
$key = $field;
}
- $cellValue = $row[$key];
+ $cellValue = Hash::get($row, $key);
$html .= $this->genCell($cellValue, $field, $row);
}
} else { // indexed array
@@ -571,7 +615,7 @@ class BoostrapTable extends BootstrapGeneric {
private function genCaption()
{
- return $this->genNode('caption', [], h($this->caption));
+ return !empty($this->caption) ? $this->genNode('caption', [], h($this->caption)) : '';
}
}
@@ -730,7 +774,7 @@ class BoostrapModal extends BootstrapGeneric {
function __construct($options) {
$this->allowedOptionValues = [
'size' => ['sm', 'lg', 'xl', ''],
- 'type' => ['ok-only','confirm','confirm-success','confirm-warning','confirm-danger'],
+ 'type' => ['ok-only','confirm','confirm-success','confirm-warning','confirm-danger', 'custom'],
'variant' => array_merge(BootstrapGeneric::$variants, ['']),
];
$this->processOptions($options);
@@ -796,7 +840,10 @@ class BoostrapModal extends BootstrapGeneric {
private function genFooter()
{
- $footer = $this->openNode('div', ['class' => array_merge(['modal-footer'], $this->options['footerClass'])]);
+ $footer = $this->openNode('div', [
+ 'class' => array_merge(['modal-footer'], $this->options['footerClass']),
+ 'data-custom-footer' => $this->options['type'] == 'custom'
+ ]);
if (!empty($this->options['footerHtml'])) {
$footer .= $this->options['footerHtml'];
} else {
@@ -811,6 +858,8 @@ class BoostrapModal extends BootstrapGeneric {
return $this->getFooterOkOnly();
} else if (str_contains($this->options['type'], 'confirm')) {
return $this->getFooterConfirm();
+ } else if ($this->options['type'] == 'custom') {
+ return $this->getFooterCustom();
} else {
return $this->getFooterOkOnly();
}
@@ -849,10 +898,350 @@ class BoostrapModal extends BootstrapGeneric {
'text' => h($this->options['confirmText']),
'class' => 'modal-confirm-button',
'params' => [
- 'data-dismiss' => $this->options['confirmFunction'] ? '' : 'modal',
+ // 'data-dismiss' => $this->options['confirmFunction'] ? '' : 'modal',
'data-confirmFunction' => sprintf('%s', $this->options['confirmFunction'])
]
]))->button();
return $buttonCancel . $buttonConfirm;
}
+
+ private function getFooterCustom()
+ {
+ $buttons = [];
+ foreach ($this->options['footerButtons'] as $buttonConfig) {
+ $buttons[] = (new BoostrapButton([
+ 'variant' => h($buttonConfig['variant'] ?? 'primary'),
+ 'text' => h($buttonConfig['text']),
+ 'class' => 'modal-confirm-button',
+ 'params' => [
+ 'data-dismiss' => !empty($buttonConfig['clickFunction']) ? '' : 'modal',
+ 'data-clickFunction' => sprintf('%s', $buttonConfig['clickFunction'])
+ ]
+ ]))->button();
+ }
+ return implode('', $buttons);
+ }
}
+
+class BoostrapCard extends BootstrapGeneric
+{
+ private $defaultOptions = [
+ 'variant' => '',
+ 'headerText' => '',
+ 'footerText' => '',
+ 'bodyText' => '',
+ 'headerHTML' => '',
+ 'footerHTML' => '',
+ 'bodyHTML' => '',
+ 'headerClass' => '',
+ 'bodyClass' => '',
+ 'footerClass' => '',
+ ];
+
+ public function __construct($options)
+ {
+ $this->allowedOptionValues = [
+ 'variant' => array_merge(BootstrapGeneric::$variants, ['']),
+ ];
+ $this->processOptions($options);
+ }
+
+ private function processOptions($options)
+ {
+ $this->options = array_merge($this->defaultOptions, $options);
+ $this->checkOptionValidity();
+ }
+
+ public function card()
+ {
+ return $this->genCard();
+ }
+
+ private function genCard()
+ {
+ $card = $this->genNode('div', [
+ 'class' => [
+ 'card',
+ !empty($this->options['variant']) ? "bg-{$this->options['variant']}" : '',
+ !empty($this->options['variant']) ? $this->getTextClassForVariant($this->options['variant']) : '',
+ ],
+ ], implode('', [$this->genHeader(), $this->genBody(), $this->genFooter()]));
+ return $card;
+ }
+
+ private function genHeader()
+ {
+ if (empty($this->options['headerHTML']) && empty($this->options['headerText'])) {
+ return '';
+ }
+ $content = !empty($this->options['headerHTML']) ? $this->options['headerHTML'] : h($this->options['headerText']);
+ $header = $this->genNode('div', [
+ 'class' => [
+ 'card-header',
+ h($this->options['headerClass']),
+ ],
+ ], $content);
+ return $header;
+ }
+
+ private function genBody()
+ {
+ if (empty($this->options['bodyHTML']) && empty($this->options['bodyText'])) {
+ return '';
+ }
+ $content = !empty($this->options['bodyHTML']) ? $this->options['bodyHTML'] : h($this->options['bodyText']);
+ $body = $this->genNode('div', [
+ 'class' => [
+ 'card-body',
+ h($this->options['bodyClass']),
+ ],
+ ], $content);
+ return $body;
+ }
+
+ private function genFooter()
+ {
+ if (empty($this->options['footerHTML']) && empty($this->options['footerText'])) {
+ return '';
+ }
+ $content = !empty($this->options['footerHTML']) ? $this->options['footerHTML'] : h($this->options['footerText']);
+ $footer = $this->genNode('div', [
+ 'class' => [
+ 'card-footer',
+ h($this->options['footerClass']),
+ ],
+ ], $content);
+ return $footer;
+ }
+}
+
+class BoostrapProgress extends BootstrapGeneric {
+ private $defaultOptions = [
+ 'value' => 0,
+ 'total' => 100,
+ 'text' => '',
+ 'title' => '',
+ 'variant' => 'primary',
+ 'height' => '',
+ 'striped' => false,
+ 'animated' => false,
+ 'label' => true
+ ];
+
+ function __construct($options) {
+ $this->allowedOptionValues = [
+ 'variant' => BootstrapGeneric::$variants,
+ ];
+ $this->processOptions($options);
+ }
+
+ private function processOptions($options)
+ {
+ $this->options = array_merge($this->defaultOptions, $options);
+ $this->checkOptionValidity();
+ }
+
+ public function progress()
+ {
+ return $this->genProgress();
+ }
+
+ private function genProgress()
+ {
+ $percentage = round(100 * $this->options['value'] / $this->options['total']);
+ $heightStyle = !empty($this->options['height']) ? sprintf('height: %s;', h($this->options['height'])) : '';
+ $widthStyle = sprintf('width: %s%%;', $percentage);
+ $label = $this->options['label'] ? "{$percentage}%" : '';
+ $pb = $this->genNode('div', [
+ 'class' => [
+ 'progress-bar',
+ "bg-{$this->options['variant']}",
+ $this->options['striped'] ? 'progress-bar-striped' : '',
+ $this->options['animated'] ? 'progress-bar-animated' : '',
+ ],
+ 'role' => "progressbar",
+ 'aria-valuemin' => "0", 'aria-valuemax' => "100",'aria-valuenow' => $percentage,
+ 'style' => "${widthStyle}",
+ 'title' => $this->options['title']
+ ], $label);
+ $container = $this->genNode('div', [
+ 'class' => [
+ 'progress',
+ ],
+ 'style' => "${heightStyle}",
+ 'title' => h($this->options['title']),
+ ], $pb);
+ return $container;
+ }
+}
+
+class BoostrapCollapse extends BootstrapGeneric {
+ private $defaultOptions = [
+ 'text' => '',
+ 'open' => false,
+ ];
+
+ function __construct($options, $content, $btHelper) {
+ $this->allowedOptionValues = [];
+ $this->processOptions($options);
+ $this->content = $content;
+ $this->btHelper = $btHelper;
+ }
+
+ private function processOptions($options)
+ {
+ $this->options = array_merge($this->defaultOptions, $options);
+ $this->checkOptionValidity();
+ }
+
+ public function collapse()
+ {
+ return $this->genCollapse();
+ }
+
+ private function genControl()
+ {
+ $html = $this->genNode('a', [
+ 'class' => ['text-decoration-none'],
+ 'data-toggle' => 'collapse',
+ 'href' => '#collapseExample',
+ 'role' => 'button',
+ 'aria-expanded' => 'false',
+ 'aria-controls' => 'collapseExample',
+ ], h($this->options['title']));
+ return $html;
+ }
+
+ private function genContent()
+ {
+ $content = $this->genNode('div', [
+ 'class' => 'card',
+ ], $this->content);
+ $container = $this->genNode('div', [
+ 'class' => ['collapse', $this->options['open'] ? 'show' : ''],
+ 'id' => 'collapseExample',
+ ], $content);
+ return $container;
+ }
+
+ private function genCollapse()
+ {
+ $html = $this->genControl();
+ $html .= $this->genContent();
+ return $html;
+ }
+}
+
+class BoostrapProgressTimeline extends BootstrapGeneric {
+ private $defaultOptions = [
+ 'steps' => [],
+ 'selected' => 0,
+ 'variant' => 'info',
+ 'variantInactive' => 'secondary',
+ ];
+
+ function __construct($options, $btHelper) {
+ $this->allowedOptionValues = [
+ 'variant' => BootstrapGeneric::$variants,
+ 'variantInactive' => BootstrapGeneric::$variants,
+ ];
+ $this->processOptions($options);
+ $this->btHelper = $btHelper;
+ }
+
+ private function processOptions($options)
+ {
+ $this->options = array_merge($this->defaultOptions, $options);
+ $this->checkOptionValidity();
+ }
+
+ public function progressTimeline()
+ {
+ return $this->genProgressTimeline();
+ }
+
+ private function getStepIcon($step, $i, $nodeActive, $lineActive)
+ {
+ $icon = $this->genNode('b', [
+ 'class' => [
+ !empty($step['icon']) ? h($this->btHelper->FontAwesome->getClass($step['icon'])) : '',
+ $this->getTextClassForVariant($this->options['variant'])
+ ],
+ ], empty($step['icon']) ? h($i+1) : '');
+ $iconContainer = $this->genNode('span', [
+ 'class' => [
+ 'd-flex', 'align-items-center', 'justify-content-center',
+ 'rounded-circle',
+ $nodeActive ? "bg-{$this->options['variant']}" : "bg-{$this->options['variantInactive']}"
+ ],
+ 'style' => 'width:50px; height:50px'
+ ], $icon);
+ $li = $this->genNode('li', [
+ 'class' => [
+ 'd-flex', 'flex-column',
+ $nodeActive ? 'progress-active' : 'progress-inactive',
+ ],
+ ], $iconContainer);
+ $html = $li . $this->getHorizontalLine($i, $nodeActive, $lineActive);
+ return $html;
+ }
+
+ private function getHorizontalLine($i, $nodeActive, $lineActive)
+ {
+ $stepCount = count($this->options['steps']);
+ if ($i == $stepCount-1) {
+ return '';
+ }
+ $progressBar = (new BoostrapProgress([
+ 'label' => false,
+ 'value' => $nodeActive ? ($lineActive ? 100 : 50) : 0,
+ 'height' => '2px',
+ 'variant' => $this->options['variant']
+ ]))->progress();
+ $line = $this->genNode('span', [
+ 'class' => [
+ 'progress-line',
+ 'flex-grow-1', 'align-self-center',
+ $lineActive ? "bg-{$this->options['variant']}" : ''
+ ],
+ ], $progressBar);
+ return $line;
+ }
+
+ private function getStepText($step, $isActive)
+ {
+ return $this->genNode('li', [
+ 'class' => [
+ 'text-center',
+ 'font-weight-bold',
+ $isActive ? 'progress-active' : 'progress-inactive',
+ ],
+ ], h($step['text'] ?? ''));
+ }
+
+ private function genProgressTimeline()
+ {
+ $iconLis = '';
+ $textLis = '';
+ foreach ($this->options['steps'] as $i => $step) {
+ $nodeActive = $i <= $this->options['selected'];
+ $lineActive = $i < $this->options['selected'];
+ $iconLis .= $this->getStepIcon($step, $i, $nodeActive, $lineActive);
+ $textLis .= $this->getStepText($step, $nodeActive);
+ }
+ $ulIcons = $this->genNode('ul', [
+ 'class' => [
+ 'd-flex', 'justify-content-around',
+ ],
+ ], $iconLis);
+ $ulText = $this->genNode('ul', [
+ 'class' => [
+ 'd-flex', 'justify-content-between',
+ ],
+ ], $textLis);
+ $html = $this->genNode('div', [
+ 'class' => ['progress-timeline', 'mw-75', 'mx-auto']
+ ], $ulIcons . $ulText);
+ return $html;
+ }
+}
\ No newline at end of file
diff --git a/src/View/MonadView.php b/src/View/MonadView.php
index 6c72ef3..18f38d9 100644
--- a/src/View/MonadView.php
+++ b/src/View/MonadView.php
@@ -22,7 +22,8 @@ namespace App\View;
class MonadView extends AppView
{
private $additionalTemplatePaths = [
- ROOT . '/libraries/default/RequestProcessors/templates/',
+ ROOT . '/libraries/default/InboxProcessors/templates/',
+ ROOT . '/libraries/default/OutboxProcessors/templates/',
];
protected function _paths(?string $plugin = null, bool $cached = true): array
diff --git a/templates/EncryptionKeys/add.php b/templates/EncryptionKeys/add.php
index 3f1800e..a0b00dd 100644
--- a/templates/EncryptionKeys/add.php
+++ b/templates/EncryptionKeys/add.php
@@ -2,7 +2,7 @@
echo $this->element('genericElements/Form/genericForm', [
'data' => [
'title' => __('Add new encryption key'),
- 'description' => __('Alignments indicate that an individual belongs to an organisation in one way or another. The type of relationship is defined by the type field.'),
+ 'description' => __('Assign encryption keys to the user, used to securely communicate or validate messages coming from the user.'),
'model' => 'Organisations',
'fields' => [
[
diff --git a/templates/Inbox/index.php b/templates/Inbox/index.php
index 55eabd3..b9d02e1 100644
--- a/templates/Inbox/index.php
+++ b/templates/Inbox/index.php
@@ -8,6 +8,16 @@ echo $this->element('genericElements/IndexTable/index_table', [
'data' => $data,
'top_bar' => [
'children' => [
+ [
+ 'children' => [
+ [
+ 'text' => __('Discard requests'),
+ 'variant' => 'danger',
+ 'onclick' => 'discardRequests',
+ ]
+ ],
+ 'type' => 'multi_select_actions',
+ ],
[
'type' => 'context_filters',
'context_filters' => !empty($filteringContexts) ? $filteringContexts : []
@@ -23,6 +33,15 @@ echo $this->element('genericElements/IndexTable/index_table', [
]
],
'fields' => [
+ [
+ 'element' => 'selector',
+ 'class' => 'short',
+ 'data' => [
+ 'id' => [
+ 'value_path' => 'id'
+ ]
+ ]
+ ],
[
'name' => '#',
'sort' => 'id',
@@ -95,5 +114,62 @@ echo $this->element('genericElements/IndexTable/index_table', [
]
]
]);
-echo '';
?>
+
+
\ No newline at end of file
diff --git a/templates/Inbox/list_processors.php b/templates/Inbox/list_processors.php
new file mode 100644
index 0000000..fd93199
--- /dev/null
+++ b/templates/Inbox/list_processors.php
@@ -0,0 +1,54 @@
+element('genericElements/IndexTable/index_table', [
+ 'data' => [
+ 'skip_pagination' => true,
+ 'data' => $data,
+ 'top_bar' => [
+ 'children' => [
+ [
+ 'type' => 'context_filters',
+ 'context_filters' => !empty($filteringContexts) ? $filteringContexts : []
+ ],
+ [
+ 'type' => 'search',
+ 'button' => __('Filter'),
+ 'placeholder' => __('Enter value to search'),
+ 'data' => '',
+ 'searchKey' => 'value',
+ 'allowFilering' => true
+ ]
+ ]
+ ],
+ 'fields' => [
+ [
+ 'name' => __('Enabled'),
+ 'data_path' => 'enabled',
+ 'element' => 'boolean'
+ ],
+ [
+ 'name' => __('Processor scope'),
+ 'data_path' => 'scope',
+ ],
+ [
+ 'name' => __('Processor action'),
+ 'data_path' => 'action',
+ ],
+ [
+ 'name' => __('Description'),
+ 'data_path' => 'description',
+ ],
+ [
+ 'name' => __('Notice'),
+ 'data_path' => 'notice',
+ ],
+ [
+ 'name' => __('Error'),
+ 'data_path' => 'error',
+ ],
+ ],
+ 'title' => __('Available Inbox Request Processors'),
+ 'description' => __('The list of Inbox Request Processors available on this server.'),
+ 'actions' => [
+ ]
+ ]
+]);
\ No newline at end of file
diff --git a/templates/LocalTools/brood_tools.php b/templates/LocalTools/brood_tools.php
index ae41159..87a8508 100644
--- a/templates/LocalTools/brood_tools.php
+++ b/templates/LocalTools/brood_tools.php
@@ -29,6 +29,11 @@ echo $this->element('genericElements/IndexTable/index_table', [
[
'name' => __('Description'),
'data_path' => 'description',
+ ],
+ [
+ 'name' => __('Connected Local Tools'),
+ 'data_path' => 'local_tool',
+ 'element' => 'local_tools_status'
]
],
'title' => __('Local tools made available by the remote Cerebrate'),
@@ -37,8 +42,9 @@ echo $this->element('genericElements/IndexTable/index_table', [
'skip_pagination' => 1,
'actions' => [
[
- 'url' => '/localTools/connectionRequest',
- 'url_params_data_paths' => ['id'],
+ 'open_modal' => sprintf('/localTools/connectionRequest/%s/[onclick_params_data_path]', h($id)),
+ 'reload_url' => $this->Url->build(['action' => 'broodTools', $id]),
+ 'modal_params_data_path' => 'id',
'title' => 'Issue a connection request',
'icon' => 'plug'
]
diff --git a/templates/LocalTools/connection_request.php b/templates/LocalTools/connection_request.php
index 8f672e7..0b8a52e 100644
--- a/templates/LocalTools/connection_request.php
+++ b/templates/LocalTools/connection_request.php
@@ -16,7 +16,12 @@
'field' => 'local_tool_id',
'options' => $dropdown,
'type' => 'dropdown'
- ]
+ ],
+ [
+ 'field' => 'tool_name',
+ 'default' => $data['remoteTool']['connectorName'],
+ 'type' => 'hidden'
+ ],
],
'submit' => [
'action' => $this->request->getParam('action')
diff --git a/templates/LocalTools/connector_index.php b/templates/LocalTools/connector_index.php
index 8a2bf4e..5b2a57c 100644
--- a/templates/LocalTools/connector_index.php
+++ b/templates/LocalTools/connector_index.php
@@ -60,6 +60,11 @@ echo $this->element('genericElements/IndexTable/index_table', [
'url_params_data_paths' => ['id'],
'icon' => 'eye'
],
+ [
+ 'open_modal' => '/localTools/connectLocal/[onclick_params_data_path]',
+ 'modal_params_data_path' => 'id',
+ 'icon' => 'plug'
+ ],
[
'open_modal' => '/localTools/edit/[onclick_params_data_path]',
'modal_params_data_path' => 'id',
diff --git a/templates/Outbox/index.php b/templates/Outbox/index.php
new file mode 100644
index 0000000..c7ac4f9
--- /dev/null
+++ b/templates/Outbox/index.php
@@ -0,0 +1,151 @@
+Html->scriptBlock(sprintf(
+ 'var csrfToken = %s;',
+ json_encode($this->request->getAttribute('csrfToken'))
+));
+echo $this->element('genericElements/IndexTable/index_table', [
+ 'data' => [
+ 'data' => $data,
+ 'top_bar' => [
+ 'children' => [
+ [
+ 'children' => [
+ [
+ 'text' => __('Delete messages'),
+ 'variant' => 'danger',
+ 'onclick' => 'deleteMessages',
+ ]
+ ],
+ 'type' => 'multi_select_actions',
+ ],
+ [
+ 'type' => 'context_filters',
+ 'context_filters' => !empty($filteringContexts) ? $filteringContexts : []
+ ],
+ [
+ 'type' => 'search',
+ 'button' => __('Filter'),
+ 'placeholder' => __('Enter value to search'),
+ 'data' => '',
+ 'searchKey' => 'value',
+ 'allowFilering' => true
+ ]
+ ]
+ ],
+ 'fields' => [
+ [
+ 'element' => 'selector',
+ 'class' => 'short',
+ 'data' => [
+ 'id' => [
+ 'value_path' => 'id'
+ ]
+ ]
+ ],
+ [
+ 'name' => '#',
+ 'sort' => 'id',
+ 'data_path' => 'id',
+ ],
+ [
+ 'name' => 'created',
+ 'sort' => 'created',
+ 'data_path' => 'created',
+ 'element' => 'datetime'
+ ],
+ [
+ 'name' => 'scope',
+ 'sort' => 'scope',
+ 'data_path' => 'scope',
+ ],
+ [
+ 'name' => 'action',
+ 'sort' => 'action',
+ 'data_path' => 'action',
+ ],
+ [
+ 'name' => 'title',
+ 'sort' => 'title',
+ 'data_path' => 'title',
+ ],
+ [
+ 'name' => 'user',
+ 'sort' => 'user_id',
+ 'data_path' => 'user',
+ 'element' => 'user'
+ ],
+ [
+ 'name' => 'description',
+ 'sort' => 'description',
+ 'data_path' => 'description',
+ ],
+ [
+ 'name' => 'comment',
+ 'sort' => 'comment',
+ 'data_path' => 'comment',
+ ],
+ ],
+ 'title' => __('Outbox'),
+ 'description' => __('A list of requests to be manually processed'),
+ 'actions' => [
+ [
+ 'url' => '/outbox/view',
+ 'url_params_data_paths' => ['id'],
+ 'icon' => 'eye',
+ 'title' => __('View request')
+ ],
+ [
+ 'open_modal' => '/outbox/process/[onclick_params_data_path]',
+ 'modal_params_data_path' => 'id',
+ 'icon' => 'cogs',
+ 'title' => __('Process request')
+ ],
+ [
+ 'open_modal' => '/outbox/delete/[onclick_params_data_path]',
+ 'modal_params_data_path' => 'id',
+ 'icon' => 'trash',
+ 'title' => __('Discard request')
+ ],
+ ]
+ ]
+]);
+?>
+
+
diff --git a/templates/Outbox/list_processors.php b/templates/Outbox/list_processors.php
new file mode 100644
index 0000000..38c87f2
--- /dev/null
+++ b/templates/Outbox/list_processors.php
@@ -0,0 +1,54 @@
+element('genericElements/IndexTable/index_table', [
+ 'data' => [
+ 'skip_pagination' => true,
+ 'data' => $data,
+ 'top_bar' => [
+ 'children' => [
+ [
+ 'type' => 'context_filters',
+ 'context_filters' => !empty($filteringContexts) ? $filteringContexts : []
+ ],
+ [
+ 'type' => 'search',
+ 'button' => __('Filter'),
+ 'placeholder' => __('Enter value to search'),
+ 'data' => '',
+ 'searchKey' => 'value',
+ 'allowFilering' => true
+ ]
+ ]
+ ],
+ 'fields' => [
+ [
+ 'name' => __('Enabled'),
+ 'data_path' => 'enabled',
+ 'element' => 'boolean'
+ ],
+ [
+ 'name' => __('Processor scope'),
+ 'data_path' => 'scope',
+ ],
+ [
+ 'name' => __('Processor action'),
+ 'data_path' => 'action',
+ ],
+ [
+ 'name' => __('Description'),
+ 'data_path' => 'description',
+ ],
+ [
+ 'name' => __('Notice'),
+ 'data_path' => 'notice',
+ ],
+ [
+ 'name' => __('Error'),
+ 'data_path' => 'error',
+ ],
+ ],
+ 'title' => __('Available Outbox Request Processors'),
+ 'description' => __('The list of Outbox Request Processors available on this server.'),
+ 'actions' => [
+ ]
+ ]
+]);
\ No newline at end of file
diff --git a/templates/Outbox/view.php b/templates/Outbox/view.php
new file mode 100644
index 0000000..5e6b234
--- /dev/null
+++ b/templates/Outbox/view.php
@@ -0,0 +1,47 @@
+element(
+ '/genericElements/SingleViews/single_view',
+ [
+ 'data' => $entity,
+ 'fields' => [
+ [
+ 'key' => __('ID'),
+ 'path' => 'id'
+ ],
+ [
+ 'key' => 'created',
+ 'path' => 'created',
+ ],
+ [
+ 'key' => 'scope',
+ 'path' => 'scope',
+ ],
+ [
+ 'key' => 'action',
+ 'path' => 'action',
+ ],
+ [
+ 'key' => 'title',
+ 'path' => 'title',
+ ],
+ [
+ 'key' => 'user_id',
+ 'path' => 'user_id',
+ ],
+ [
+ 'key' => 'description',
+ 'path' => 'description',
+ ],
+ [
+ 'key' => 'comment',
+ 'path' => 'comment',
+ ],
+ [
+ 'key' => 'data',
+ 'path' => 'data',
+ 'type' => 'json'
+ ],
+ ],
+ 'children' => []
+ ]
+);
diff --git a/templates/element/genericElements/Form/genericForm.php b/templates/element/genericElements/Form/genericForm.php
index 9e3ba2e..5807da6 100644
--- a/templates/element/genericElements/Form/genericForm.php
+++ b/templates/element/genericElements/Form/genericForm.php
@@ -83,7 +83,7 @@
$submitButtonData['ajaxSubmit'] = $ajaxSubmit;
}
$ajaxFlashMessage = '';
- if ($ajax) {
+ if (!empty($ajax)) {
$ajaxFlashMessage = sprintf(
'',
$this->Flash->render()
diff --git a/templates/element/genericElements/Form/submitButton.php b/templates/element/genericElements/Form/submitButton.php
index 134e857..55062dd 100644
--- a/templates/element/genericElements/Form/submitButton.php
+++ b/templates/element/genericElements/Form/submitButton.php
@@ -1,5 +1,5 @@
';
+ echo '';
foreach ($actions as $action) {
if (isset($action['requirement']) && !$action['requirement']) {
continue;
diff --git a/templates/element/genericElements/IndexTable/Fields/function.php b/templates/element/genericElements/IndexTable/Fields/function.php
new file mode 100644
index 0000000..db22a56
--- /dev/null
+++ b/templates/element/genericElements/IndexTable/Fields/function.php
@@ -0,0 +1,3 @@
+
diff --git a/templates/element/genericElements/IndexTable/Fields/generic_field.php b/templates/element/genericElements/IndexTable/Fields/generic_field.php
index 2ef0980..abc8eca 100644
--- a/templates/element/genericElements/IndexTable/Fields/generic_field.php
+++ b/templates/element/genericElements/IndexTable/Fields/generic_field.php
@@ -18,6 +18,12 @@
);
} else {
$data = h($data);
+ if (!empty($field['options'])) {
+ $options = $this->Hash->extract($row, $field['options']);
+ if (!empty($options)) {
+ $data = h($options[$data]);
+ }
+ }
if (!empty($field['privacy'])) {
$data = sprintf(
'**************************************** ',
diff --git a/templates/element/genericElements/IndexTable/Fields/local_tools_status.php b/templates/element/genericElements/IndexTable/Fields/local_tools_status.php
new file mode 100644
index 0000000..118b915
--- /dev/null
+++ b/templates/element/genericElements/IndexTable/Fields/local_tools_status.php
@@ -0,0 +1,13 @@
+Hash->extract($row, 'local_tools');
+ $output = [];
+ foreach ($tools as $tool) {
+ $output[] = sprintf(
+ '%s: %s',
+ h($tool['id']),
+ h($tool['name']),
+ h($tool['status'])
+ );
+ }
+ echo implode(' ', $output);
+?>
diff --git a/templates/element/genericElements/IndexTable/Fields/selector.php b/templates/element/genericElements/IndexTable/Fields/selector.php
index f7c6530..006552c 100644
--- a/templates/element/genericElements/IndexTable/Fields/selector.php
+++ b/templates/element/genericElements/IndexTable/Fields/selector.php
@@ -13,7 +13,7 @@
}
}
echo sprintf(
- '',
+ '',
h($k),
empty($data) ? '' : implode(' ', $data)
);
diff --git a/templates/element/genericElements/IndexTable/headers.php b/templates/element/genericElements/IndexTable/headers.php
index c518b11..d6b33bd 100644
--- a/templates/element/genericElements/IndexTable/headers.php
+++ b/templates/element/genericElements/IndexTable/headers.php
@@ -14,7 +14,7 @@
$header_data = sprintf(
'',
empty($header['select_all_class']) ? 'select_all' : $header['select_all_class'],
- empty($header['select_all_function']) ? 'onclick="toggleAllAttributeCheckboxes();"' : 'onclick="' . $header['select_all_function'] . '"'
+ empty($header['select_all_function']) ? 'onclick="toggleAllAttributeCheckboxes(this);"' : 'onclick="' . $header['select_all_function'] . '"'
);
} else {
$header_data = h($header['name']);
@@ -38,14 +38,3 @@
$thead .= '';
echo $thead;
?>
-
diff --git a/templates/element/genericElements/IndexTable/index_table.php b/templates/element/genericElements/IndexTable/index_table.php
index d9b04ec..9a407d4 100644
--- a/templates/element/genericElements/IndexTable/index_table.php
+++ b/templates/element/genericElements/IndexTable/index_table.php
@@ -93,7 +93,8 @@
}
$tbody = ' | ' . $rows . '';
echo sprintf(
- '',
+ '',
+ $tableRandomValue,
$tableRandomValue,
$this->element(
'/genericElements/IndexTable/headers',
@@ -114,6 +115,7 @@
?>
\ No newline at end of file
diff --git a/templates/element/genericElements/ListTopBar/group_search.php b/templates/element/genericElements/ListTopBar/group_search.php
index 7822e07..814ffcc 100644
--- a/templates/element/genericElements/ListTopBar/group_search.php
+++ b/templates/element/genericElements/ListTopBar/group_search.php
@@ -12,6 +12,9 @@
* - id: element ID for the input field - defaults to quickFilterField
*/
if (!isset($data['requirement']) || $data['requirement']) {
+ if (!empty($data['quickFilter'])) {
+ $quickFilter = $data['quickFilter'];
+ }
$filterEffective = !empty($quickFilter); // No filters will be picked up, thus rendering the filtering useless
$filteringButton = '';
if (!empty($data['allowFilering'])) {
diff --git a/templates/element/genericElements/SingleViews/Fields/jsonField.php b/templates/element/genericElements/SingleViews/Fields/jsonField.php
index 9c8b16b..ef14c58 100644
--- a/templates/element/genericElements/SingleViews/Fields/jsonField.php
+++ b/templates/element/genericElements/SingleViews/Fields/jsonField.php
@@ -4,7 +4,7 @@
$string = $field['raw'];
} else {
$value = Cake\Utility\Hash::extract($data, $field['path']);
- $string = empty($value[0]) ? '' : $value[0];
+ $string = count($value) == 0 ? '' : $value[0];
}
echo sprintf(
'',
diff --git a/templates/genericTemplates/index_simple.php b/templates/element/genericElements/index_simple.php
similarity index 100%
rename from templates/genericTemplates/index_simple.php
rename to templates/element/genericElements/index_simple.php
diff --git a/templates/genericTemplates/delete.php b/templates/genericTemplates/delete.php
index 6b07d6a..f2b7449 100644
--- a/templates/genericTemplates/delete.php
+++ b/templates/genericTemplates/delete.php
@@ -1,39 +1,31 @@
-
-
-
-
-
-
= __('Are you sure you want to delete {0} #{1}?', h(Cake\Utility\Inflector::singularize($this->request->getParam('controller'))), h($id)) ?>
-
-
= h($deletionText) ?>
-
-
-
-
-
-
+element('genericElements/Form/genericForm', [
+ 'entity' => null,
+ 'ajax' => false,
+ 'raw' => true,
+ 'data' => [
+ 'fields' => [
+ [
+ 'type' => 'text',
+ 'field' => 'ids',
+ 'default' => !empty($id) ? json_encode([$id]) : ''
+ ]
+ ],
+ 'submit' => [
+ 'action' => $this->request->getParam('action')
+ ]
+ ]
+]);
+$formHTML = sprintf('%s
', $form);
+
+$bodyMessage = !empty($deletionText) ? __($deletionText) : __('Are you sure you want to delete {0} #{1}?', h(Cake\Utility\Inflector::singularize($this->request->getParam('controller'))), h($id));
+$bodyHTML = sprintf('%s%s', $formHTML, $bodyMessage);
+
+echo $this->Bootstrap->modal([
+ 'size' => 'lg',
+ 'title' => !empty($deletionTitle) ? $deletionTitle : __('Delete {0}', h(Cake\Utility\Inflector::singularize(Cake\Utility\Inflector::humanize($this->request->getParam('controller'))))),
+ 'type' => 'confirm-danger',
+ 'confirmText' => !empty($deletionConfirm) ? $deletionConfirm : __('Delete'),
+ 'bodyHtml' => $bodyHTML,
+]);
+?>
diff --git a/webroot/css/bootstrap-additional.css b/webroot/css/bootstrap-additional.css
index 981324c..95e2e80 100644
--- a/webroot/css/bootstrap-additional.css
+++ b/webroot/css/bootstrap-additional.css
@@ -1,3 +1,23 @@
+/* utils */
+.mw-75 {
+ max-width: 75% !important;
+ }
+.mw-50 {
+ max-width: 50% !important;
+}
+.mw-25 {
+ max-width: 25% !important;
+}
+.mh-75 {
+ max-width: 75% !important;
+}
+.mh-50 {
+ max-width: 50% !important;
+}
+.mh-25 {
+ max-width: 25% !important;
+}
+
/* Toast */
.toast {
min-width: 250px;
@@ -66,3 +86,25 @@
.toast-dark strong {
color: #040505;
}
+
+div.progress-timeline {
+ padding: 0.2em 0.2em 0.5em 0.2em;
+}
+div.progress-timeline ul {
+ position: relative;
+ padding: 0;
+}
+div.progress-timeline li {
+ list-style-type: none;
+ position: relative
+}
+div.progress-timeline li.progress-inactive {
+ opacity: 0.5;
+}
+div.progress-timeline .progress-line {
+ height: 2px;
+ /* background: gray; */
+}
+div.progress-timeline .progress-line.progress-inactive {
+ opacity: 0.5;
+}
\ No newline at end of file
diff --git a/webroot/js/bootstrap-helper.js b/webroot/js/bootstrap-helper.js
index 8cb39e3..2d50a49 100644
--- a/webroot/js/bootstrap-helper.js
+++ b/webroot/js/bootstrap-helper.js
@@ -31,7 +31,7 @@ class UIFactory {
* @param {ModalFactory~POSTFailCallback} POSTFailCallback - The callback that handles form submissions errors and validation errors.
* @return {Promise