chg: [inbox:localTool] Usage of localTools in the inbox to process connection requests - WiP

pull/59/head
mokaddem 2021-06-17 14:13:10 +02:00
parent 1f8010a6ec
commit e1ebbc125a
10 changed files with 239 additions and 106 deletions

View File

@ -60,6 +60,13 @@ class LocalToolRequestProcessor extends GenericRequestProcessor
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) {
@ -128,13 +135,34 @@ class LocalToolRequestProcessor extends GenericRequestProcessor
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,
],
'remote_org' => $remote_org,
'remote_tool_data' => $requestData,
'remote_cerebrate' => $remoteCerebrate,
'connection' => $connection,
];
}
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');
->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'
@ -191,18 +219,23 @@ class IncomingConnectionRequestProcessor extends LocalToolRequestProcessor imple
$this->discard($id, $inboxRequest);
}
} else {
$connectorResult = $this->acceptConnection($connector, $remoteCerebrate, $inboxRequest['data']);
$connectionSuccessfull = false;
$connectionData = [];
$resultTitle = __('Could not inter-connect `{0}`\'s {1}', $inboxRequest['origin'], $inboxRequest['local_tool_name']);
$errors = [];
$connectorResult = [];
try {
$connectorResult = $this->acceptConnection($connector, $remoteCerebrate, $inboxRequest['data']);
$connectionSuccessfull = true;
} catch (\Throwable $th) {
$connectionSuccessfull = false;
$errors = $th->getMessage();
}
$resultTitle = __('Could not inter-connect `{0}`\'s {1}', $inboxRequest['origin'], $inboxRequest['local_tool_name']);
if ($connectionSuccessfull) {
$resultTitle = __('Interconnection for `{0}`\'s {1} created', $inboxRequest['origin'], $inboxRequest['local_tool_name']);
$this->discard($id, $inboxRequest);
}
}
return $this->genActionResult(
$connectionData,
$connectorResult,
$connectionSuccessfull,
$resultTitle,
$errors
@ -216,34 +249,36 @@ class IncomingConnectionRequestProcessor extends LocalToolRequestProcessor imple
protected function acceptConnection($connector, $remoteCerebrate, $requestData)
{
$connectorResult = $connector->acceptConnection($requestData['data']);
$connectorResult['connectorName'] = $requestData->local_tool_name;
$response = $this->sendAcceptedRequestToRemote($remoteCerebrate, $connectorResult);
// change state if sending fails
// add the entry to the outbox if sending fails.
$connection = $this->getConnection($requestData);
$params = $this->genBroodParam($remoteCerebrate, $connection, $connector, $requestData);
$connectorResult = $connector->acceptConnection($params);
$response = $this->sendAcceptedRequestToRemote($params, $connectorResult);
return $response;
}
protected function declineConnection($connector, $remoteCerebrate, $requestData)
{
$connectorResult = $connector->declineConnection($requestData['data']);
$connectorResult['connectorName'] = $requestData->local_tool_name;
$response = $this->sendDeclinedRequestToRemote($remoteCerebrate, $connectorResult);
$connection = $this->getConnection($requestData);
$params = $this->genBroodParam($remoteCerebrate, $connection, $connector, $requestData);
$connectorResult = $connector->declineConnection($params);
$response = $this->sendDeclinedRequestToRemote($params, $connectorResult);
return $response;
}
protected function sendAcceptedRequestToRemote($remoteCerebrate, $connectorResult)
protected function sendAcceptedRequestToRemote($params, $connectorResult)
{
$urlPath = '/inbox/createInboxEntry/LocalTool/AcceptedRequest';
$response = $this->Inbox->sendRequest($remoteCerebrate, $urlPath, true, $connectorResult);
return $response;
$response = $this->Broods->sendLocalToolAcceptedRequest($params, $connectorResult);
// change state if sending fails
// add the entry to the outbox if sending fails.
return $response->getJson();
}
protected function sendDeclinedRequestToRemote($remoteCerebrate, $connectorResult)
{
$urlPath = '/inbox/createInboxEntry/LocalTool/DeclinedRequest';
$response = $this->Inbox->sendRequest($remoteCerebrate, $urlPath, true, $connectorResult);
return $response;
$response = $this->Broods->sendLocalToolDeclinedRequest($params, $connectorResult);
// change state if sending fails
// add the entry to the outbox if sending fails.
return $response->getJson();
}
}
@ -254,7 +289,6 @@ class AcceptedRequestProcessor extends LocalToolRequestProcessor implements Gene
public function __construct() {
parent::__construct();
$this->description = __('Handle Phase II of inter-connection when initial request has been accepted by the remote cerebrate.');
// $this->Broods = TableRegistry::getTableLocator()->get('Broods');
}
protected function addValidatorRules($validator)
@ -281,15 +315,22 @@ class AcceptedRequestProcessor extends LocalToolRequestProcessor implements Gene
public function process($id, $requestData, $inboxRequest)
{
$connector = $this->getConnector($request);
$remoteCerebrate = $this->getIssuerBrood($request);
$connectorResult = $this->finalizeConnection($connector, $remoteCerebrate, $requestData['data']);
$connectionSuccessfull = false;
$connectionData = [];
$resultTitle = __('Could not finalize inter-connection for `{0}`\'s {1}', $requestData['origin'], $requestData['local_tool_name']);
$connector = $this->getConnector($inboxRequest);
$remoteCerebrate = $this->getIssuerBrood($inboxRequest);
$errors = [];
$connectorResult = [];
try {
$connectorResult = $this->finalizeConnection($connector, $remoteCerebrate, $inboxRequest['data']);
$connectionSuccessfull = true;
} catch (\Throwable $th) {
$connectionSuccessfull = false;
$errors = $th->getMessage();
}
$connectionData = [];
$resultTitle = __('Could not finalize inter-connection for `{0}`\'s {1}', $inboxRequest['origin'], $inboxRequest['local_tool_name']);
if ($connectionSuccessfull) {
$resultTitle = __('Interconnection for `{0}`\'s {1} finalized', $requestData['origin'], $requestData['local_tool_name']);
$resultTitle = __('Interconnection for `{0}`\'s {1} finalized', $inboxRequest['origin'], $inboxRequest['local_tool_name']);
$this->discard($id, $requestData);
}
return $this->genActionResult(
@ -307,7 +348,9 @@ class AcceptedRequestProcessor extends LocalToolRequestProcessor implements Gene
protected function finalizeConnection($connector, $remoteCerebrate, $requestData)
{
$connectorResult = $connector->finaliseConnection($requestData['data']);
$connection = $this->getConnection($requestData);
$params = $this->genBroodParam($remoteCerebrate, $connection, $connector, $requestData);
$connectorResult = $connector->finaliseConnection($params);
return $connectorResult;
}
}

View File

@ -112,6 +112,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());

View File

@ -128,14 +128,7 @@ class InboxController extends AppController
'origin' => $this->request->clientIp(),
'user_id' => $this->ACL->getUser()['id'],
];
$entryData['data'] = $this->request->data ?? [];
// $entryData['data'] = [
// 'connectorName' => 'MispConnector',
// 'cerebrateURL' => 'http://localhost:8000',
// 'url' => 'https://localhost:8443',
// 'email' => 'admin@admin.test',
// 'authkey' => 'DkM9fEfwrG8Bg3U0ncKamocIutKt5YaUFuxzsB6b',
// ];
$entryData['data'] = $this->request->getData() ?? [];
$this->requestProcessor = TableRegistry::getTableLocator()->get('RequestProcessor');
if ($scope == 'LocalTool') {
$this->validateLocalToolRequestEntry($entryData);

View File

@ -229,7 +229,27 @@ class LocalToolsController extends AppController
}
$params['local_tool_id'] = $postParams['local_tool_id'];
$encodingResult = $this->LocalTools->encodeConnection($params);
$this->redirect();
$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']);
$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']);
$this->redirect($this->referer());
}
}
return $response;
} else {
$remoteTool = $this->LocalTools->getRemoteToolById($params);
$local_tools = $this->LocalTools->encodeConnectionChoice($params);

View File

@ -7,6 +7,7 @@ class CommonConnectorTools
{
public $description = '';
public $name = '';
public $connectorName = '';
public $exposedFunctions = [
'diagnostics'
];

View File

@ -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 = [
@ -100,16 +101,19 @@ class MispConnector extends CommonConnectorTools
}
}
public function getHTTPClient(Object $connection): Object
private function getHeaders(array $connectionSettings): array
{
return [
'AUTHORIZATION' => $connectionSettings['authkey'],
'Accept' => 'application/json',
'Content-type' => 'application/json'
];
}
private function getHTTPClient(Object $connection): Object
{
$settings = json_decode($connection->settings, true);
$options = [
'headers' => [
'AUTHORIZATION' => $settings['authkey'],
'Accept' => 'Application/json',
'Content-type' => 'Application/json'
],
];
$options = [];
if (!empty($settings['skip_ssl'])) {
$options['ssl_verify_peer'] = false;
$options['ssl_verify_host'] = false;
@ -126,7 +130,12 @@ class MispConnector extends CommonConnectorTools
$http = $this->getHTTPClient($connection);
try {
$response = $http->post($settings['url'] . '/users/view/me.json', '{}');
$response = $http->post($settings['url'] . '/users/view/me.json', '{}', ['headers' => [
'AUTHORIZATION' => $settings['authkey'],
'Accept' => 'application/json',
],
'type' => 'json',
]);
} catch (\Exception $e) {
return [
'status' => 0,
@ -194,14 +203,14 @@ class MispConnector extends CommonConnectorTools
$response = $http->post($settings['url'] . $url, json_encode($params['body']), [
'headers' => [
'AUTHORIZATION' => $settings['authkey'],
'Accept' => 'application/json'
'Accept' => 'application/json',
],
'type' => 'json'
'type' => 'json',
]);
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());
}
}
@ -701,12 +710,14 @@ class MispConnector extends CommonConnectorTools
$params['connection_settings'] = json_decode($params['connection']['settings'], true);
$params['misp_organisation'] = $this->getSetOrg($params);
$params['sync_user'] = $this->createSyncUser($params);
$params['sync_connection'] = $this->addServer([
'authkey' => $params['remote_tool']['authkey'],
'url' => $params['remote_tool']['url'],
'name' => $params['remote_tool']['name'],
$serverParams = $params;
$serverParams['body'] = [
'authkey' => $params['remote_tool_data']['authkey'],
'url' => $params['remote_tool_data']['url'],
'name' => !empty($params['remote_tool_data']['name']) ? $params['remote_tool_data']['name'] : 'Empty name fix me',
'remote_org_id' => $params['misp_organisation']['id']
]);
];
$params['sync_connection'] = $this->addServer($serverParams);
return [
'email' => $params['sync_user']['email'],
'authkey' => $params['sync_user']['authkey'],
@ -716,12 +727,15 @@ class MispConnector extends CommonConnectorTools
public function finaliseConnection(array $params): bool
{
$params['sync_connection'] = $this->addServer([
'authkey' => $params['remote_tool']['authkey'],
'url' => $params['remote_tool']['url'],
'name' => $params['remote_tool']['name'],
$params['misp_organisation'] = $this->getSetOrg($params);
$serverParams = $params;
$serverParams['body'] = [
'authkey' => $params['remote_tool_data']['authkey'],
'url' => $params['remote_tool_data']['url'],
'name' => !empty($params['remote_tool_data']['name']) ? $params['remote_tool_data']['name'] : 'Empty name fix me',
'remote_org_id' => $params['misp_organisation']['id']
]);
];
$params['sync_connection'] = $this->addServer($serverParams);
return true;
}
@ -733,6 +747,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.'));
@ -771,10 +786,10 @@ class MispConnector extends CommonConnectorTools
private function addServer(array $params): array
{
if (
empty($params['authkey']) ||
empty($params['url']) ||
empty($params['remote_org_id']) ||
empty($params['name'])
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].'));
}

View File

@ -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;
@ -192,4 +195,57 @@ class BroodsTable extends AppTable
return false;
}
}
public function sendRequest($brood, $urlPath, $methodPost = true, $data = []): Response
{
$http = new Client();
$config = [
'headers' => [
'AUTHORIZATION' => $brood->authkey,
'Accept' => 'application/json'
],
'type' => 'json'
];
$url = $brood->url . $urlPath;
if ($methodPost) {
$response = $http->post($url, json_encode($data), $config);
} else {
$response = $http->get($brood->url, $data, $config);
}
if ($response->isOk()) {
return $response;
} else {
throw new NotFoundException(__('Could not send to the requested resource.'));
}
}
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'];
return $data;
}
public function sendLocalToolConnectionRequest($params, $data): Response
{
$url = '/inbox/createInboxEntry/LocalTool/IncomingConnectionRequest';
$data = $this->injectRequiredData($params, $data);
return $this->sendRequest($params['remote_cerebrate'], $url, true, $data);
}
public function sendLocalToolAcceptedRequest($params, $data): Response
{
$url = '/inbox/createInboxEntry/LocalTool/AcceptedRequest';
$data = $this->injectRequiredData($params, $data);
return $this->sendRequest($params['remote_cerebrate'], $url, true, $data);
}
public function sendLocalToolDeclinedRequest($params, $data): Response
{
$url = '/inbox/createInboxEntry/LocalTool/DeclinedRequest';
$data = $this->injectRequiredData($params, $data);
return $this->sendRequest($params['remote_cerebrate'], $url, true, $data);
}
}

View File

@ -64,29 +64,6 @@ class InboxTable extends AppTable
return $rules;
}
public function sendRequest($brood, $urlPath, $methodPost = true, $data = []): boolean
{
$http = new Client();
$config = [
'headers' => [
'AUTHORIZATION' => $brood->authkey,
'Accept' => 'application/json'
],
'type' => 'json'
];
$url = $brood->url . $urlPath;
if ($methodPost) {
$response = $http->post($url, json_encode(data), $config);
} else {
$response = $http->get($brood->url, json_encode(data), $config);
}
if ($response->isOk()) {
return $response;
} else {
throw new NotFoundException(__('Could not post to the requested resource.'));
}
}
public function checkUserBelongsToBroodOwnerOrg($user, $entryData) {
$this->Broods = \Cake\ORM\TableRegistry::getTableLocator()->get('Broods');
$this->Individuals = \Cake\ORM\TableRegistry::getTableLocator()->get('Individuals');
@ -105,7 +82,7 @@ class InboxTable extends AppTable
}
}
if (!$found) {
$errors[] = __('User is not part of the brood organisation');
$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;
}

View File

@ -205,9 +205,12 @@ class LocalToolsTable extends AppTable
public function encodeConnection(array $params): array
{
$params = $this->buildConnectionParams($params);
$result = $params['connector'][$params['remote_tool']['connector']]->initiateConnectionWrapper($params);
$this->sendEncodedConnection($params['remote_cerebrate'], $params['remote_tool']['connector'], $result);
return $result;
$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
@ -244,12 +247,29 @@ class LocalToolsTable extends AppTable
return $local_tools;
}
public function sendEncodedConnection($remoteCerebrate, $connectorName, $encodedConnection)
public function sendEncodedConnection($params, $encodedConnection)
{
$encodedConnection['connectorName'] = $connectorName;
$encodedConnection['cerebrateURL'] = Configure::read('App.fullBaseUrl');
$urlPath = '/inbox/createInboxEntry/LocalTool/IncomingConnectionRequest';
$response = $this->Inbox->sendRequest($remoteCerebrate, $urlPath, true, $encodedConnection);
// If sending failed: Modify state + add entry in outbox
$this->Broods = \Cake\ORM\TableRegistry::getTableLocator()->get('Broods');
try {
$response = $this->Broods->sendLocalToolConnectionRequest($params, $encodedConnection);
$jsonReply = $response->getJson();
if (empty($jsonReply['success'])) {
$this->handleMessageNotCreated($response);
}
} catch (NotFoundException $e) {
return $this->handleSendingFailed($response);
}
return $jsonReply;
}
public function handleSendingFailed($response)
{
// debug('sending failed. Modify state and add entry in outbox');
throw new NotFoundException(__('sending failed. Modify state and add entry in outbox'));
}
public function handleMessageNotCreated($response)
{
// debug('Saving message failed. Modify state and add entry in outbox');
}
}

View File

@ -997,6 +997,7 @@ class FormValidationHelper {
} else {
$messageNode.addClass('invalid-feedback')
}
if (typeof errors === 'object') {
const hasMultipleErrors = Object.keys(errors).length > 1
for (const [ruleName, error] of Object.entries(errors)) {
if (hasMultipleErrors) {
@ -1005,6 +1006,9 @@ class FormValidationHelper {
$messageNode.text(error)
}
}
} else {
$messageNode.text(errors)
}
return $messageNode
}