chg: [workflow] Added an `id` in all module instead of relying on the label

pull/8530/head
Sami Mokaddem 2022-07-27 15:41:21 +02:00
parent 5b6f051749
commit e265057d24
No known key found for this signature in database
GPG Key ID: 164C473F627A06FA
12 changed files with 116 additions and 91 deletions

View File

@ -70,7 +70,8 @@ class Module_misp_module extends WorkflowBaseActionModule
$postData['filteredItems'] = !empty($filteredItems) ? $filteredItems : $rData;
}
$postData['params'] = $this->getIndexedParamsWithValues($node);
$indexedParams = $this->getParamsWithValues($node);
$postData['params'] = Hash::combine($indexedParams, '{s}.id', '{s}.value');
$params = $this->getParamsWithValues($node);
$matchingData = [];
foreach ($params as $param) {
@ -93,9 +94,11 @@ class Module_misp_module extends WorkflowBaseActionModule
// FIXME: We might want to align the module config with what's currently supported
protected function translateParams($paramName, $moduleParam): array
{
$param = [];
$param['label'] = $paramName;
$param['placeholder'] = $moduleParam['value'] ?? '';
$param = [
'id' => Inflector::underscore($paramName),
'label' => Inflector::humanize($paramName),
'placeholder' => $moduleParam['value'] ?? '',
];
if ($moduleParam['type'] == 'hash_path') {
$param['type'] = 'input';
$param['_isHashPath'] = true;

View File

@ -32,35 +32,29 @@ class WorkflowBaseModule
{
}
protected function getParams($node): array
protected function mergeNodeConfigIntoParameters($node): array
{
$indexedParam = [];
$nodeParam = [];
$nodeParamByID = [];
foreach ($node['data']['params'] as $param) {
$nodeParam[$param['label']] = $param;
$nodeParamByID[$param['id']] = $param;
}
foreach ($this->params as $param) {
$param['value'] = $nodeParam[$param['label']]['value'] ?? null;
$indexedParam[$param['label']] = $param;
$param['value'] = $nodeParamByID[$param['id']]['value'] ?? null;
$indexedParam[$param['id']] = $param;
}
return $indexedParam;
}
protected function getParamsWithValues($node): array
{
$indexedParams = $this->getParams($node);
foreach ($indexedParams as $label => $param) {
$indexedParams[$label]['value'] = $param['value'] ?? ($param['default'] ?? '');
$indexedParams = $this->mergeNodeConfigIntoParameters($node);
foreach ($indexedParams as $id => $param) {
$indexedParams[$id]['value'] = $param['value'] ?? ($param['default'] ?? '');
}
return $indexedParams;
}
protected function getIndexedParamsWithValues($node): array
{
$indexedParams = $this->getParamsWithValues($node);
return Hash::combine($indexedParams, '{s}.label', '{s}.value');
}
protected function getFilters($node): array
{
$indexedFilters = [];

View File

@ -29,8 +29,9 @@ class Module_enrich_event extends WorkflowBaseActionModule
}
$this->params = [
[
'type' => 'select',
'id' => 'modules',
'label' => 'Modules',
'type' => 'select',
'options' => $moduleOptions,
],
];
@ -40,7 +41,7 @@ class Module_enrich_event extends WorkflowBaseActionModule
{
parent::exec($node, $roamingData, $errors);
$params = $this->getParamsWithValues($node);
if (empty($params['Modules']['value'])) {
if (empty($params['modules']['value'])) {
$errors[] = __('No enrichmnent module selected');
return false;
}
@ -49,7 +50,7 @@ class Module_enrich_event extends WorkflowBaseActionModule
$options = [
'user' => $roamingData->getUser(),
'event_id' => $event_id,
'modules' => [$params['Modules']['value']]
'modules' => [$params['modules']['value']]
];
$filters = $this->getFilters($node);
$extracted = $this->extractData($rData, $filters['selector']);

View File

@ -17,20 +17,9 @@ class Module_push_zmq extends WorkflowBaseActionModule
parent::__construct();
$this->params = [
[
'type' => 'input',
'label' => 'Namespace',
'default' => '',
'placeholder' => __('A namespace in the ZMQ topic')
],
[
'type' => 'input',
'label' => 'Content',
'default' => '',
'placeholder' => __('Whatever text to be published')
],
[
'type' => 'input',
'id' => 'match_condition',
'label' => 'Match Condition',
'type' => 'input',
'default' => '',
'placeholder' => 'Attribute.{n}.AttributeTag.{n}.Tag.name',
],
@ -41,7 +30,7 @@ class Module_push_zmq extends WorkflowBaseActionModule
{
parent::exec($node, $roamingData, $errors);
$params = $this->getParamsWithValues($node);
$path = $params['Match Condition']['value'];
$path = $params['match_condition']['value'];
$data = $roamingData->getData();
$extracted = $this->extractData($data, $path);
if ($extracted === false) {

View File

@ -23,22 +23,25 @@ class Module_webhook extends WorkflowBaseActionModule
parent::__construct();
$this->params = [
[
'type' => 'input',
'id' => 'url',
'label' => 'Payload URL',
'type' => 'input',
'placeholder' => 'https://example.com/test',
],
[
'type' => 'select',
'id' => 'content_type',
'label' => 'Content type',
'default' => 'form',
'type' => 'select',
'default' => 'json',
'options' => [
'json' => 'application/json',
'form' => 'application/x-www-form-urlencoded',
],
],
[
'type' => 'input',
'id' => 'data_extraction_path',
'label' => 'Data extraction path',
'type' => 'input',
'default' => '',
'placeholder' => 'Attribute.{n}.AttributeTag.{n}.Tag.name',
],
@ -49,16 +52,16 @@ class Module_webhook extends WorkflowBaseActionModule
{
parent::exec($node, $roamingData, $errors);
$params = $this->getParamsWithValues($node);
if (empty($params['Payload URL']['value'])) {
if (empty($params['url']['value'])) {
$errors[] = __('URL not provided.');
return false;
}
$rData = $roamingData->getData();
$path = $params['Data extraction path']['value'];
$extracted = !empty($params['Data extraction path']['value']) ? $this->extractData($rData, $path) : $rData;
$path = $params['data_extraction_path']['value'];
$extracted = !empty($params['data_extraction_path']['value']) ? $this->extractData($rData, $path) : $rData;
try {
$response = $this->doRequest($params['Payload URL']['value'], $params['Content type']['value'], $extracted);
$response = $this->doRequest($params['url']['value'], $params['content_type']['value'], $extracted);
if ($response->isOk()) {
return true;
}

View File

@ -38,7 +38,7 @@ class Module_concurrent_task extends WorkflowBaseLogicModule
Job::WORKER_PRIO,
'workflowParallelTask',
sprintf('Workflow ID: %s', $roamingData->getWorkflow()['Workflow']['id']),
'Running workflow parallel tasks.'
__('Running workflow parallel tasks.')
);
$this->Job->getBackgroundJobsTool()->enqueue(
BackgroundJobsTool::PRIO_QUEUE,
@ -48,7 +48,7 @@ class Module_concurrent_task extends WorkflowBaseLogicModule
$roamingData->getWorkflow()['Workflow']['id'],
$node_id_to_exec,
JsonTool::encode($roamingData->getData()),
Workflow::NON_BLOCKING_PATH,
$this->Workflow::NON_BLOCKING_PATH,
$jobId
],
true,

View File

@ -34,6 +34,7 @@ class Module_distribution_if extends WorkflowBaseLogicModule
}
$this->params = [
[
'id' => 'scope',
'label' => 'Scope',
'type' => 'select',
'options' => [
@ -43,12 +44,14 @@ class Module_distribution_if extends WorkflowBaseLogicModule
'default' => 'attribute',
],
[
'id' => 'condition',
'label' => 'Condition',
'type' => 'select',
'default' => 'equals',
'options' => $this->operators,
],
[
'id' => 'distribution',
'label' => 'Distribution',
'type' => 'select',
'default' => '0',
@ -63,9 +66,9 @@ class Module_distribution_if extends WorkflowBaseLogicModule
parent::exec($node, $roamingData, $errors);
$params = $this->getParamsWithValues($node);
$scope = $params['Scope']['value'];
$operator = $params['Condition']['value'];
$value = $params['Distribution']['value'];
$scope = $params['scope']['value'];
$operator = $params['condition']['value'];
$value = $params['distribution']['value'];
$data = $roamingData->getData();
$final_distribution = $this->__getPropagatedDistribution($data['Event']);
if ($scope == 'attribute') {

View File

@ -24,19 +24,22 @@ class Module_generic_if extends WorkflowBaseLogicModule
parent::__construct();
$this->params = [
[
'type' => 'input',
'id' => 'value',
'label' => 'Value',
'type' => 'input',
'placeholder' => 'tlp:red',
],
[
'type' => 'select',
'id' => 'operator',
'label' => 'Operator',
'type' => 'select',
'default' => 'in',
'options' => $this->operators,
],
[
'type' => 'input',
'id' => 'hash_path',
'label' => 'Hash path',
'type' => 'input',
'placeholder' => 'Attribute.{n}.Tag',
],
];
@ -46,9 +49,9 @@ class Module_generic_if extends WorkflowBaseLogicModule
{
parent::exec($node, $roamingData, $errors);
$params = $this->getParamsWithValues($node);
$path = $params['Hash path']['value'];
$operator = $params['Operator']['value'];
$value = $params['Value']['value'];
$path = $params['hash_path']['value'];
$operator = $params['operator']['value'];
$value = $params['value']['value'];
$data = $roamingData->getData();
$extracted = [];
if ($operator == 'equals' || $operator == 'not_equals') {

View File

@ -29,24 +29,26 @@ class Module_organisation_if extends WorkflowBaseLogicModule
]);
$this->params = [
[
'id' => 'org_type',
'label' => 'Organisation Type',
'type' => 'select',
'options' => [
'org' => __('Owner Organisation'),
'orgc' => __('Creator Organisation'),
],
'default' => 'orgc',
'label' => 'Organisation Type',
],
[
'type' => 'select',
'id' => 'condition',
'label' => 'Condition',
'type' => 'select',
'default' => 'equals',
'options' => $this->operators,
],
[
'id' => 'org_id',
'type' => 'picker',
'multiple' => false,
'label' => 'Organisation',
'options' => $orgs,
'default' => 1,
'placeholder' => __('Pick an organisation'),
@ -59,9 +61,9 @@ class Module_organisation_if extends WorkflowBaseLogicModule
parent::exec($node, $roamingData, $errors);
$params = $this->getParamsWithValues($node);
$org_type = $params['Organisation Type']['value'];
$operator = $params['Condition']['value'];
$org_id = $params['Organisation']['value'];
$org_type = $params['org_type']['value'];
$operator = $params['condition']['value'];
$org_id = $params['org_id']['value'];
$data = $roamingData->getData();
$path = 'Event.org_id';
if ($org_type == 'orgc') {

View File

@ -23,8 +23,9 @@ class Module_published_if extends WorkflowBaseLogicModule
parent::__construct();
$this->params = [
[
'type' => 'select',
'id' => 'condition',
'label' => 'Condition',
'type' => 'select',
'default' => 'equals',
'options' => $this->operators,
],
@ -36,7 +37,7 @@ class Module_published_if extends WorkflowBaseLogicModule
parent::exec($node, $roamingData, $errors);
$params = $this->getParamsWithValues($node);
$operator = $params['Condition']['value'];
$operator = $params['condition']['value'];
$data = $roamingData->getData();
$path = 'Event.published';
$is_published = !empty(Hash::get($data, $path));

View File

@ -37,6 +37,8 @@ class Module_tag_if extends WorkflowBaseLogicModule
$tags = array_column(array_column($tags, 'Tag'), 'name', 'id');
$this->params = [
[
'id' => 'scope',
'label' => 'Scope',
'type' => 'select',
'options' => [
'event' => __('Event'),
@ -44,18 +46,19 @@ class Module_tag_if extends WorkflowBaseLogicModule
'event_attribute' => __('Inherited Attribute'),
],
'default' => 'event',
'label' => 'Scope',
],
[
'type' => 'select',
'id' => 'condition',
'label' => 'Condition',
'type' => 'select',
'default' => 'in_or',
'options' => $this->operators,
],
[
'id' => 'tags',
'label' => 'Tags',
'type' => 'picker',
'multiple' => true,
'label' => 'Tags',
'options' => $tags,
'placeholder' => __('Pick a tag'),
],
@ -67,9 +70,9 @@ class Module_tag_if extends WorkflowBaseLogicModule
parent::exec($node, $roamingData, $errors);
$params = $this->getParamsWithValues($node);
$value = $params['Tags']['value'];
$operator = $params['Condition']['value'];
$scope = $params['Scope']['value'];
$value = $params['tags']['value'];
$operator = $params['condition']['value'];
$scope = $params['scope']['value'];
$data = $roamingData->getData();
$extracted = $this->__getTagFromScope($scope, $data);
$eval = $this->evaluateCondition($extracted, $operator, $value);

View File

@ -690,7 +690,7 @@ function loadWorkflow(workflow) {
console.error('Tried to add node for unknown module ' + node.data.id + ' (' + node.id + ')')
var userFriendlyParams = {}
node.data.params.forEach(function (param) {
userFriendlyParams[param.label] = (param.value ?? param.default)
userFriendlyParams[param.id] = (param.value ?? param.default)
})
var html = window['dotBlock_error']({
error: 'Invalid module id`' + node.data.id + '` (' + node.id + ')',
@ -840,7 +840,7 @@ function addNodesFromWorkflowBlueprint(workflowBlueprint, cursorPosition) {
if (all_modules_by_id[node.data.id] === undefined) {
var userFriendlyParams = {}
node.data.params.forEach(function (param) {
userFriendlyParams[param.label] = (param.value ?? param.default)
userFriendlyParams[param.id] = (param.value ?? param.default)
})
var errorMessage = 'Invalid ' + node.data.module_type + ' module id `' + node.data.id + '` (' + node.id + ')'
var html = window['dotBlock_error']({
@ -911,29 +911,35 @@ function getCanvasCentroid() {
}
function mergeNodeAndModuleParams(node, moduleParams) {
var moduleParamsByFormattedName = {}
var nodeParamsByFormattedName = {}
moduleParams.forEach(function (param) {
moduleParamsByFormattedName[param.label.toLowerCase().replace(' ', '-')] = param
var moduleParamsById = {}
var nodeParamsById = {}
moduleParams.forEach(function (param, i) {
if (param.id === undefined) { // Param id is not set in the module definition.
param.id = 'param-' + i
param.no_id = true
}
moduleParamsById[param.id] = param
})
node.data.params.forEach(function (param) {
nodeParamsByFormattedName[param.label.toLowerCase().replace(' ', '-')] = param
node.data.params.forEach(function (param, i) {
if (param.id === undefined) { // Param id is not set in the module definition.
param.id = 'param-' + i
}
nodeParamsById[param.id] = param
})
var finalParams = {}
var nodeAndModuleParams = node.data.params.concat(moduleParams)
nodeAndModuleParams.forEach(function (param) {
var formattedName = param.label.toLowerCase().replace(' ', '-')
if (finalParams[formattedName]) { // param has already been processed
if (finalParams[param.id]) { // param has already been processed
return;
}
if (moduleParamsByFormattedName[formattedName] === undefined) { // Param do not exist in the module (anymore or never did)
if (moduleParamsById[param.id] === undefined) { // Param do not exist in the module (anymore or never did)
param.is_invalid = true
}
if (!param['param_id']) {
param['param_id'] = getIDForNodeParameter(node, param)
}
finalParam = Object.assign({}, nodeParamsByFormattedName[formattedName], moduleParamsByFormattedName[formattedName])
finalParams[formattedName] = finalParam
finalParam = Object.assign({}, nodeParamsById[param.id], moduleParamsById[param.id])
finalParams[param.id] = finalParam
})
return Object.values(finalParams)
}
@ -1212,14 +1218,31 @@ function afterModalShowCallback() {
}
function genParameterWarning(options) {
return options.is_invalid ?
$('<span>').addClass('text-error').css('margin-left', '5px')
// return options.is_invalid ?
// $('<span>').addClass('text-error').css('margin-left', '5px')
// .append(
// $('<i>').addClass('fas fa-exclamation-triangle'),
// $('<span>').text('Invalid parameter')
// )
// .attr('title', 'This parameter does not exist in the associated module and thus will be removed upon saving. Make sure you have the latest version of this module.') :
// ''
var text = '', text_short = ''
if (options.is_invalid) {
text = 'This parameter does not exist in the associated module and thus will be removed upon saving. Make sure you have the latest version of this module.'
text_short = 'Invalid parameter'
} else if (options.no_id) {
text = 'This parameter does not have an ID in the associated module and thus will be ignored. Make sure you have the latest version of this module.'
text_short = 'parameter has no ID'
}
if (text || text_short) {
return $('<span>').addClass('text-error').css('margin-left', '5px')
.append(
$('<i>').addClass('fas fa-exclamation-triangle'),
$('<span>').text('Invalid parameter')
$('<span>').text(text_short)
)
.attr('title', 'This parameter does not exist in the associated module and thus will be removed upon saving. Make sure you have the latest version of this module.') :
''
.attr('title', text)
}
return ''
}
function genSelect(options, forNode = true) {
@ -1464,7 +1487,7 @@ function getIDForNodeParameter(node, param) {
if (param.id !== undefined) {
return param.id + '-' + node.node_uid
}
return param.label.toLowerCase().replace(' ', '-') + '-' + node.node_uid
return param.id + '-' + node.node_uid
}
function getNodeFromNodeInput($input) {
@ -1621,10 +1644,10 @@ function genGenericBlockFilter(block) {
]
var filters = getFiltersFromNode(block)
var $div = $('<div></div>').append($('<form></form>').append(
genGenericInput({ id: 'filtering-selector', label: 'Element selector', type: 'text', placeholder: 'Attribute.{n}', required: false, value: filters.selector}),
genGenericInput({ id: 'filtering-value', label: 'Value', type: 'text', placeholder: 'tlp:white', required: false, value: filters.value}),
genGenericSelect({ id: 'filtering-operator', label: 'Operator', options: operatorOptions, value: filters.operator}),
genGenericInput({ id: 'filtering-path', label: 'Hash Path', type: 'text', placeholder: 'AttributeTag.{n}.Tag.name', required: false, value: filters.path}),
genGenericInput({ id: 'filtering-selector', id: 'element_selector', label: 'Element selector', type: 'text', placeholder: 'Attribute.{n}', required: false, value: filters.selector}),
genGenericInput({ id: 'filtering-value', id: 'value', label: 'Value', type: 'text', placeholder: 'tlp:white', required: false, value: filters.value}),
genGenericSelect({ id: 'filtering-operator', id: 'operator', label: 'Operator', options: operatorOptions, value: filters.operator}),
genGenericInput({ id: 'filtering-path', id: 'hash_path', label: 'Hash Path', type: 'text', placeholder: 'AttributeTag.{n}.Tag.name', required: false, value: filters.path}),
))
return $div[0].outerHTML
}