mirror of https://github.com/MISP/MISP
new: [workflowPart] Started integration of workflow parts
parent
d1c617a287
commit
4e47782a04
|
@ -0,0 +1,93 @@
|
|||
<?php
|
||||
App::uses('AppController', 'Controller');
|
||||
|
||||
class WorkflowPartsController extends AppController
|
||||
{
|
||||
public $components = array(
|
||||
'RequestHandler'
|
||||
);
|
||||
|
||||
public function index()
|
||||
{
|
||||
$params = [
|
||||
'filters' => ['name', 'uuid', 'timestamp'],
|
||||
'quickFilters' => ['name', 'uuid'],
|
||||
];
|
||||
$this->CRUD->index($params);
|
||||
if ($this->IndexFilter->isRest()) {
|
||||
return $this->restResponsePayload;
|
||||
}
|
||||
$this->set('menuData', ['menuList' => 'workflowParts', 'menuItem' => 'index']);
|
||||
}
|
||||
|
||||
public function add($fromEditor = false)
|
||||
{
|
||||
$params = [];
|
||||
$this->CRUD->add($params);
|
||||
if ($this->IndexFilter->isRest()) {
|
||||
return $this->restResponsePayload;
|
||||
}
|
||||
$this->set('fromEditor', !empty($fromEditor));
|
||||
$this->set('menuData', ['menuList' => 'workflowParts', 'menuItem' => 'add']);
|
||||
}
|
||||
|
||||
public function edit($id)
|
||||
{
|
||||
$params = [];
|
||||
$this->CRUD->edit($id, $params);
|
||||
if ($this->IndexFilter->isRest()) {
|
||||
return $this->restResponsePayload;
|
||||
}
|
||||
$this->set('menuData', ['menuList' => 'workflowParts', 'menuItem' => 'edit']);
|
||||
$this->set('id', $id);
|
||||
$this->render('add');
|
||||
}
|
||||
|
||||
public function delete($id)
|
||||
{
|
||||
$params = [
|
||||
];
|
||||
$this->CRUD->delete($id, $params);
|
||||
if ($this->IndexFilter->isRest()) {
|
||||
return $this->restResponsePayload;
|
||||
}
|
||||
$this->set('menuData', ['menuList' => 'workflowParts', 'menuItem' => 'delete']);
|
||||
}
|
||||
|
||||
public function view($id)
|
||||
{
|
||||
$this->CRUD->view($id, [
|
||||
]);
|
||||
if ($this->IndexFilter->isRest()) {
|
||||
return $this->restResponsePayload;
|
||||
}
|
||||
$this->set('id', $id);
|
||||
$this->set('menuData', ['menuList' => 'workflowParts', 'menuItem' => 'view']);
|
||||
}
|
||||
|
||||
public function import()
|
||||
{
|
||||
if ($this->request->is('post') || $this->request->is('put')) {
|
||||
$workflowPartData = JsonTool::decode($this->request->data['WorkflowPart']['data']);
|
||||
if ($workflowPartData === null) {
|
||||
throw new MethodNotAllowedException(__('Error while decoding JSON'));
|
||||
}
|
||||
$this->request->data['WorkflowPart']['data'] = JsonTool::encode($workflowPartData);
|
||||
$this->add();
|
||||
}
|
||||
}
|
||||
|
||||
public function export($id)
|
||||
{
|
||||
$workflowPart = $this->WorkflowPart->find('first', [
|
||||
'conditions' => [
|
||||
'id' => $id,
|
||||
]
|
||||
]);
|
||||
$content = JsonTool::encode($workflowPart, JSON_PRETTY_PRINT);
|
||||
$this->response->body($content);
|
||||
$this->response->type('json');
|
||||
$this->response->download(sprintf('workflowpart_%s_%s.json', $workflowPart['WorkflowPart']['name'], time()));
|
||||
return $this->response;
|
||||
}
|
||||
}
|
|
@ -1704,6 +1704,18 @@ class AppModel extends Model
|
|||
INDEX `user_id` (`user_id`),
|
||||
INDEX `org_id` (`org_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;";
|
||||
$sqlArray[] = "CREATE TABLE IF NOT EXISTS `workflow_parts` (
|
||||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`uuid` varchar(40) COLLATE utf8_bin NOT NULL ,
|
||||
`name` varchar(191) NOT NULL,
|
||||
`description` varchar(191) NOT NULL,
|
||||
`timestamp` int(11) NOT NULL DEFAULT 0,
|
||||
`data` text,
|
||||
PRIMARY KEY (`id`),
|
||||
INDEX `uuid` (`uuid`),
|
||||
INDEX `name` (`name`),
|
||||
INDEX `timestamp` (`timestamp`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;";
|
||||
break;
|
||||
case 'fixNonEmptySharingGroupID':
|
||||
$sqlArray[] = 'UPDATE `events` SET `sharing_group_id` = 0 WHERE `distribution` != 4;';
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
<?php
|
||||
App::uses('AppModel', 'Model');
|
||||
|
||||
class WorkflowPart extends AppModel
|
||||
{
|
||||
public $recursive = -1;
|
||||
|
||||
public $actsAs = [
|
||||
'AuditLog',
|
||||
'Containable',
|
||||
];
|
||||
|
||||
public $belongsTo = [
|
||||
];
|
||||
|
||||
public $validate = [
|
||||
'value' => [
|
||||
'stringNotEmpty' => [
|
||||
'rule' => ['stringNotEmpty']
|
||||
]
|
||||
],
|
||||
'uuid' => [
|
||||
'uuid' => [
|
||||
'rule' => 'uuid',
|
||||
'message' => 'Please provide a valid RFC 4122 UUID'
|
||||
],
|
||||
'unique' => [
|
||||
'rule' => 'isUnique',
|
||||
'message' => 'The UUID provided is not unique',
|
||||
'required' => 'create'
|
||||
]
|
||||
],
|
||||
];
|
||||
|
||||
const CAPTURE_FIELDS = ['name', 'description', 'timestamp', 'data'];
|
||||
|
||||
public function beforeValidate($options = array())
|
||||
{
|
||||
parent::beforeValidate();
|
||||
if (empty($this->data['WorkflowPart']['uuid'])) {
|
||||
$this->data['WorkflowPart']['uuid'] = CakeText::uuid();
|
||||
} else {
|
||||
$this->data['WorkflowPart']['uuid'] = strtolower($this->data['WorkflowPart']['uuid']);
|
||||
}
|
||||
if (empty($this->data['WorkflowPart']['data'])) {
|
||||
$this->data['WorkflowPart']['data'] = [];
|
||||
}
|
||||
if (empty($this->data['WorkflowPart']['timestamp'])) {
|
||||
$this->data['WorkflowPart']['timestamp'] = time();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function afterFind($results, $primary = false)
|
||||
{
|
||||
foreach ($results as $k => $result) {
|
||||
if (empty($result['WorkflowPart']['data'])) {
|
||||
$result['WorkflowPart']['data'] = '{}';
|
||||
}
|
||||
$results[$k]['WorkflowPart']['data'] = JsonTool::decode($result['WorkflowPart']['data']);
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
public function beforeSave($options = [])
|
||||
{
|
||||
$this->data['WorkflowPart']['data'] = JsonTool::encode($this->data['WorkflowPart']['data']);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public function logExecutionError($workflow, $message)
|
||||
{
|
||||
$this->Log = ClassRegistry::init('Log');
|
||||
$this->Log->createLogEntry('SYSTEM', 'execute_workflow', 'Workflow', $workflow['Workflow']['id'], $message);
|
||||
}
|
||||
|
||||
private function __logError($id, $message)
|
||||
{
|
||||
$this->Log = ClassRegistry::init('Log');
|
||||
$this->Log->createLogEntry('SYSTEM', 'load_module', 'Workflow', $id, $message);
|
||||
return false;
|
||||
}
|
||||
|
||||
private function __saveAndReturnErrors($data, $saveOptions = [], $errors = [])
|
||||
{
|
||||
$saveSuccess = $this->save($data, $saveOptions);
|
||||
if (!$saveSuccess) {
|
||||
foreach ($this->validationErrors as $validationError) {
|
||||
$errors[] = $validationError[0];
|
||||
}
|
||||
}
|
||||
return $errors;
|
||||
}
|
||||
|
||||
}
|
|
@ -1612,6 +1612,37 @@ $divider = $this->element('/genericElements/SideMenu/side_menu_divider');
|
|||
}
|
||||
break;
|
||||
|
||||
case 'workflowParts':
|
||||
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
|
||||
'element_id' => 'index',
|
||||
'url' => '/workflowParts/index',
|
||||
'text' => __('List Workflow Parts')
|
||||
));
|
||||
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
|
||||
'url' => $baseurl . '/workflowParts/import',
|
||||
'text' => __('Import Workflow Parts')
|
||||
));
|
||||
if ($menuItem === 'view' || $menuItem === 'edit') {
|
||||
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
|
||||
'element_id' => 'view',
|
||||
'url' => '/workflowParts/view/' . h($id),
|
||||
'text' => __('View Workflow Part')
|
||||
));
|
||||
if ($isSiteAdmin) {
|
||||
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
|
||||
'element_id' => 'edit',
|
||||
'url' => '/workflows/edit/' . h($id),
|
||||
'text' => __('Edit Workflow Part')
|
||||
));
|
||||
}
|
||||
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
|
||||
'url' => '/admin/audit_logs/index/model:WorkflowParts/model_id:' . h($id),
|
||||
'text' => __('View worflow part history'),
|
||||
'requirement' => Configure::read('MISP.log_new_audit') && $canAccess('auditLogs', 'admin_index'),
|
||||
));
|
||||
}
|
||||
break;
|
||||
|
||||
case 'workflows':
|
||||
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
|
||||
'element_id' => 'index_trigger',
|
||||
|
@ -1623,6 +1654,11 @@ $divider = $this->element('/genericElements/SideMenu/side_menu_divider');
|
|||
'url' => '/workflows/moduleIndex',
|
||||
'text' => __('List Modules')
|
||||
));
|
||||
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
|
||||
'element_id' => 'index',
|
||||
'url' => '/workflowParts/index',
|
||||
'text' => __('List Workflow Parts')
|
||||
));
|
||||
if ($menuItem === 'view' || $menuItem === 'edit') {
|
||||
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
|
||||
'element_id' => 'view',
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
$edit = $this->request->params['action'] === 'edit' ? true : false;
|
||||
$fields = [
|
||||
[
|
||||
'field' => 'name',
|
||||
'class' => 'span6',
|
||||
'placeholder' => 'Name of the workflow part',
|
||||
],
|
||||
[
|
||||
'field' => 'description',
|
||||
'type' => 'textarea',
|
||||
'class' => 'input span6',
|
||||
'placeholder' => 'Concise description of the workflow part',
|
||||
],
|
||||
[
|
||||
'field' => 'data',
|
||||
'type' => 'textarea',
|
||||
'class' => 'input span6',
|
||||
'div' => (!empty($fromEditor) ? 'hidden' : '')
|
||||
]
|
||||
];
|
||||
echo $this->element('genericElements/Form/genericForm', [
|
||||
'data' => [
|
||||
'description' => false,
|
||||
'model' => 'WorkflowPart',
|
||||
'title' => $edit ? __('Edit Workflow Part') : __('Add Workflow Part'),
|
||||
'fields' => $fields,
|
||||
'submit' => [
|
||||
'action' => $this->request->params['action'],
|
||||
'ajaxSubmit' => 'submitGenericFormInPlace();'
|
||||
]
|
||||
]
|
||||
]);
|
||||
|
||||
if (!$ajax) {
|
||||
echo $this->element('/genericElements/SideMenu/side_menu', $menuData);
|
||||
}
|
|
@ -7,7 +7,7 @@ echo $this->element('genericElements/Form/genericForm', [
|
|||
'data' => [
|
||||
'model' => 'Workflow',
|
||||
'title' => __('Import Workflow'),
|
||||
'description' => __('Paste a JSON of a Workflow to import or provide a JSON file below.'),
|
||||
'description' => __('Paste a JSON of a Workflow part to import it or provide a JSON file below.'),
|
||||
'fields' => [
|
||||
[
|
||||
'field' => 'json',
|
||||
|
@ -15,7 +15,7 @@ echo $this->element('genericElements/Form/genericForm', [
|
|||
'class' => 'input span6',
|
||||
'div' => 'input clear',
|
||||
'label' => __('JSON'),
|
||||
'placeholder' => __('Workflow JSON'),
|
||||
'placeholder' => __('Workflow Part JSON'),
|
||||
'rows' => 18
|
||||
],
|
||||
[
|
||||
|
@ -30,4 +30,6 @@ echo $this->element('genericElements/Form/genericForm', [
|
|||
]
|
||||
]);
|
||||
|
||||
echo $this->element('/genericElements/SideMenu/side_menu', array('menuList' => 'workflows', 'menuItem' => 'import'));
|
||||
if (!$ajax) {
|
||||
echo $this->element('/genericElements/SideMenu/side_menu', ['menuList' => 'workflowParts', 'menuItem' => 'import']);
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
<?php
|
||||
$fields = [
|
||||
[
|
||||
'name' => __('ID'),
|
||||
'sort' => 'WorkflowPart.id',
|
||||
'data_path' => 'WorkflowPart.id',
|
||||
],
|
||||
[
|
||||
'name' => __('UUID'),
|
||||
'sort' => 'WorkflowPart.uuid',
|
||||
'data_path' => 'WorkflowPart.uuid',
|
||||
],
|
||||
[
|
||||
'name' => __('Name'),
|
||||
'sort' => 'WorkflowPart.name',
|
||||
'data_path' => 'WorkflowPart.name',
|
||||
],
|
||||
[
|
||||
'name' => __('Description'),
|
||||
'sort' => 'WorkflowPart.description',
|
||||
'data_path' => 'WorkflowPart.description',
|
||||
],
|
||||
[
|
||||
'name' => __('Timestamp'),
|
||||
'sort' => 'WorkflowPart.timestamp',
|
||||
'data_path' => 'WorkflowPart.timestamp',
|
||||
],
|
||||
];
|
||||
|
||||
|
||||
echo $this->element('genericElements/IndexTable/scaffold', [
|
||||
'scaffold_data' => [
|
||||
'data' => [
|
||||
'data' => $data,
|
||||
'top_bar' => [
|
||||
'pull' => 'right',
|
||||
'children' => [
|
||||
[
|
||||
'type' => 'simple',
|
||||
'children' => [
|
||||
'data' => [
|
||||
'type' => 'simple',
|
||||
'fa-icon' => 'plus',
|
||||
'text' => __('Import Workflow Part'),
|
||||
'class' => 'btn-primary modal-open',
|
||||
'url' => "$baseurl/workflowParts/import",
|
||||
]
|
||||
]
|
||||
],
|
||||
[
|
||||
'type' => 'search',
|
||||
'button' => __('Filter'),
|
||||
'placeholder' => __('Enter value to search'),
|
||||
'data' => '',
|
||||
'searchKey' => 'quickFilter'
|
||||
]
|
||||
]
|
||||
],
|
||||
'fields' => $fields,
|
||||
'title' => __('Workflow Parts'),
|
||||
'description' => __('You can create re-use workflow parts in your workflows'),
|
||||
'actions' => [
|
||||
[
|
||||
'url' => $baseurl . '/workflowParts/view',
|
||||
'url_params_data_paths' => ['WorkflowPart.id'],
|
||||
'icon' => 'eye',
|
||||
'dbclickAction' => true,
|
||||
],
|
||||
[
|
||||
'url' => $baseurl . '/workflowParts/edit',
|
||||
'url_params_data_paths' => ['WorkflowPart.id'],
|
||||
'icon' => 'edit',
|
||||
],
|
||||
[
|
||||
'url' => $baseurl . '/workflowParts/export',
|
||||
'url_params_data_paths' => ['WorkflowPart.id'],
|
||||
'title' => __('Export Workflow Part'),
|
||||
'icon' => 'download',
|
||||
],
|
||||
[
|
||||
|
||||
'onclick' => sprintf(
|
||||
'openGenericModal(\'%s/workflowParts/delete/[onclick_params_data_path]\');',
|
||||
$baseurl
|
||||
),
|
||||
'onclick_params_data_path' => 'WorkflowPart.id',
|
||||
'icon' => 'trash'
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]);
|
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
echo $this->element(
|
||||
'genericElements/SingleViews/single_view',
|
||||
[
|
||||
'title' => 'Workflow view',
|
||||
'data' => $data,
|
||||
'fields' => [
|
||||
[
|
||||
'key' => __('Name'),
|
||||
'path' => 'WorkflowPart.name'
|
||||
],
|
||||
[
|
||||
'key' => __('ID'),
|
||||
'path' => 'WorkflowPart.id'
|
||||
],
|
||||
[
|
||||
'key' => __('UUID'),
|
||||
'path' => 'WorkflowPart.uuid'
|
||||
],
|
||||
[
|
||||
'key' => __('Timestamp'),
|
||||
'path' => 'WorkflowPart.timestamp',
|
||||
],
|
||||
[
|
||||
'key' => __('Description'),
|
||||
'path' => 'WorkflowPart.description'
|
||||
],
|
||||
[
|
||||
'key' => __('Data'),
|
||||
'class' => 'restrict-height',
|
||||
'path' => 'WorkflowPart.data',
|
||||
'type' => 'json',
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
?>
|
||||
|
||||
<style>
|
||||
.restrict-height > div {
|
||||
height: 200px;
|
||||
overflow: auto;
|
||||
resize: both;
|
||||
}
|
||||
</style>
|
|
@ -100,7 +100,7 @@ $triggerModules = $modules['blocks_trigger'];
|
|||
</a>
|
||||
<ul class="dropdown-menu pull-right">
|
||||
<li id="control-import-blocks" class=""><a href="#"><i class="fa-fw <?= $this->FontAwesome->getClass('file-import') ?>"></i> <?= __('Import workflow parts') ?></a></li>
|
||||
<li id="control-export-blocks" class="disabled"><a href="#"><i class="fa-fw <?= $this->FontAwesome->getClass('file-export') ?>"></i> <?= __('Export workflow parts') ?></a></li>
|
||||
<li id="control-save-blocks" class="disabled"><a href="<?= $baseurl . '/workflowParts/add/1' ?>"><i class=" fa-fw <?= $this->FontAwesome->getClass('save') ?>"></i> <?= __('Save workflow parts') ?></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -179,6 +179,7 @@ echo $this->element('genericElements/assetLoader', [
|
|||
var $controlDuplicateButton = $('.control-buttons #control-duplicate')
|
||||
var $controlDeleteButton = $('.control-buttons #control-delete')
|
||||
var $controlExportBlocksLi = $('.control-buttons #control-export-blocks')
|
||||
var $controlSaveBlocksLi = $('.control-buttons #control-save-blocks')
|
||||
var $importWorkflowButton = $('#importWorkflow')
|
||||
var $exportWorkflowButton = $('#exportWorkflow')
|
||||
var $saveWorkflowButton = $('#saveWorkflow')
|
||||
|
@ -200,4 +201,10 @@ echo $this->element('genericElements/assetLoader', [
|
|||
$(document).ready(function() {
|
||||
initDrawflow()
|
||||
})
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.dropdown-menu li.disabled a {
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
|
@ -336,7 +336,7 @@ function initDrawflow() {
|
|||
editor.on('nodeSelected', function(node_id) {
|
||||
$controlDuplicateButton.removeClass('disabled')
|
||||
$controlDeleteButton.removeClass('disabled')
|
||||
$controlExportBlocksLi.removeClass('disabled')
|
||||
$controlSaveBlocksLi.removeClass('disabled')
|
||||
selection.select([getNodeHtmlByID(node_id)])
|
||||
})
|
||||
editor.on('nodeUnselected', function() {
|
||||
|
@ -346,7 +346,7 @@ function initDrawflow() {
|
|||
selection.clearSelection()
|
||||
$controlDuplicateButton.addClass('disabled')
|
||||
$controlDeleteButton.addClass('disabled')
|
||||
$controlExportBlocksLi.addClass('disabled')
|
||||
$controlSaveBlocksLi.addClass('disabled')
|
||||
})
|
||||
var selection = new SelectionArea({
|
||||
selectables: ['#drawflow .drawflow-node'],
|
||||
|
@ -398,6 +398,49 @@ function initDrawflow() {
|
|||
editor.removeNodeId(node.id)
|
||||
})
|
||||
})
|
||||
$controlSaveBlocksLi.click(function(evt) {
|
||||
var $link = $(this).find('a')
|
||||
evt.preventDefault()
|
||||
var selectedNodes = selection.getSelection()
|
||||
var editorData = getEditorData()
|
||||
openGenericModal($link.attr('href'), undefined, function() {
|
||||
var nodes = selectedNodes.map(function (nodeHtml) { return editorData[nodeHtml.id.slice(5)] })
|
||||
var $modal = $('#genericModal')
|
||||
var $graphData = $modal.find('form #WorkflowPartData')
|
||||
$graphData.val(JSON.stringify(nodes))
|
||||
$modal.find('.modal-body').append(
|
||||
$('<h3></h3>').append(
|
||||
$('<span></span').text('Workflow Part Content '),
|
||||
$('<a class="fas fa-copy" href="#"></a>')
|
||||
.attr('title', 'Copy Workflow Part to clipboard')
|
||||
.click(function() {
|
||||
var $clicked = $(this)
|
||||
navigator.clipboard.writeText(JSON.stringify(nodes)).then(function () {
|
||||
$clicked.removeClass('fa-copy').addClass('fa-check').addClass('text-success')
|
||||
setTimeout(function () {
|
||||
$clicked.removeClass('fa-check').addClass('fa-copy').removeClass('text-success')
|
||||
}, 2000);
|
||||
}, function (err) {
|
||||
console.error('Async: Could not copy text: ', err);
|
||||
});
|
||||
}),
|
||||
)
|
||||
)
|
||||
var $ul = $('<ul></ul>')
|
||||
nodes.forEach(function(node) {
|
||||
$ul.append(
|
||||
$('<li></li>').append(
|
||||
$('<strong></strong>').text(node.data.name),
|
||||
$('<ul></ul>').append(
|
||||
node.data.saved_filters.length > 0 ? $('<li></li>').text('Has filter') : null,
|
||||
node.data.params.length > 0 ? $('<li></li>').text('Has parameters') : null
|
||||
)
|
||||
)
|
||||
)
|
||||
})
|
||||
$modal.find('.modal-body').append($ul)
|
||||
})
|
||||
})
|
||||
|
||||
$(window).bind('beforeunload', function() {
|
||||
if (contentChanged) {
|
||||
|
|
Loading…
Reference in New Issue