chg: [workflow] Jinja template rendering is done automatically based on param options

pull/9370/head
Sami Mokaddem 2023-10-25 10:52:59 +02:00
parent 369f7cdede
commit fb1c6bb0bc
No known key found for this signature in database
GPG Key ID: 164C473F627A06FA
30 changed files with 100 additions and 60 deletions

View File

@ -33,7 +33,7 @@ class AppController extends Controller
public $helpers = array('OrgImg', 'FontAwesome', 'UserName');
private $__queryVersion = '155';
private $__queryVersion = '156';
public $pyMispVersion = '2.4.178';
public $phpmin = '7.2';
public $phprec = '7.4';

View File

@ -15,7 +15,8 @@ class Module_blueprint_logic_module extends WorkflowBaseLogicModule
public function exec(array $node, WorkflowRoamingData $roamingData, array &$errors = []): bool
{
parent::exec($node, $roamingData, $errors);
$params = $this->getParamsWithValues($node);
$rData = $roamingData->getData();
$params = $this->getParamsWithValues($node, $rData);
$data = $roamingData->getData();
// Returning true will make the execution flow take the first output of this module. Otherwise, the second output will be used.
return true;

View File

@ -70,11 +70,11 @@ class Module_misp_module extends WorkflowBaseActionModule
$postData['filteredItems'] = !empty($filteredItems) ? $filteredItems : $rData;
}
$indexedParams = $this->getParamsWithValues($node);
$rData = $roamingData->getData();
$indexedParams = $this->getParamsWithValues($node, $rData);
$postData['params'] = Hash::combine($indexedParams, '{s}.id', '{s}.value');
$params = $this->getParamsWithValues($node);
$matchingData = [];
foreach ($params as $param) {
foreach ($indexedParams as $param) {
if (!empty($param['_isHashPath'])) {
$matchingData[$param['label']] = !empty($param['value']) ? $this->extractData($rData, $param['value']) : $rData;
}
@ -107,6 +107,9 @@ class Module_misp_module extends WorkflowBaseActionModule
} else {
$param['type'] = 'input';
}
if (isset($moduleParam['jinja_supported'])) {
$param['jinja_supported'] = !empty($moduleParam['jinja_supported']);
}
return $param;
}
}

View File

@ -52,11 +52,14 @@ class WorkflowBaseModule
return $fullIndexedParams;
}
protected function getParamsWithValues($node): array
protected function getParamsWithValues(array $node, array $rData): array
{
$indexedParams = $this->mergeNodeConfigIntoParameters($node);
foreach ($indexedParams as $id => $param) {
$indexedParams[$id]['value'] = $param['value'] ?? ($param['default'] ?? '');
if (!empty($param['jinja_supported']) && strlen($param['value']) > 0) {
$indexedParams[$id]['value'] = $this->render_jinja_template($param['value'], $rData);
}
}
return $indexedParams;
}

View File

@ -58,9 +58,8 @@ class Module_add_eventblocklist_entry extends WorkflowBaseActionModule
public function exec(array $node, WorkflowRoamingData $roamingData, array &$errors = []): bool
{
$params = $this->getParamsWithValues($node);
$rData = $roamingData->getData();
$params = $this->getParamsWithValues($node, $rData);
$eventUUIDExtractionPath = $params['uuid_hash_path']['value'];
$eventUUID = Hash::get($rData, $eventUUIDExtractionPath);

View File

@ -75,9 +75,8 @@ class Module_assign_country_from_enrichment extends Module_tag_operation
public function exec(array $node, WorkflowRoamingData $roamingData, array &$errors = []): bool
{
$params = $this->getParamsWithValues($node);
$rData = $roamingData->getData();
$params = $this->getParamsWithValues($node, $rData);
$user = $roamingData->getUser();
$countryExtractionPath = $params['hash_path']['value'];

View File

@ -54,7 +54,8 @@ class Module_attach_enrichment extends WorkflowBaseActionModule
public function exec(array $node, WorkflowRoamingData $roamingData, array &$errors = []): bool
{
parent::exec($node, $roamingData, $errors);
$params = $this->getParamsWithValues($node);
$rData = $roamingData->getData();
$params = $this->getParamsWithValues($node, $rData);
if (empty($params['modules']['value'])) {
$errors[] = __('No enrichmnent module selected');
return false;
@ -64,7 +65,6 @@ class Module_attach_enrichment extends WorkflowBaseActionModule
$selectedModules = array_filter($params['modules']['value'], function($module) {
return $module !== '';
});
$rData = $roamingData->getData();
$event_id = $rData['Event']['id'];
$options = [
'user' => $roamingData->getUser(),

View File

@ -42,12 +42,12 @@ class Module_attach_warninglist extends WorkflowBaseActionModule
public function exec(array $node, WorkflowRoamingData $roamingData, array &$errors = []): bool
{
parent::exec($node, $roamingData, $errors);
$params = $this->getParamsWithValues($node);
$rData = $roamingData->getData();
$params = $this->getParamsWithValues($node, $rData);
if (empty($params['warninglists']['value'])) {
$errors[] = __('No warninglist module selected');
return false;
}
$rData = $roamingData->getData();
$matchingItems = $this->getMatchingItemsForAttributes($node, $rData);
if ($matchingItems === false) {

View File

@ -25,6 +25,7 @@ class Module_attribute_comment_operation extends Module_attribute_edition_operat
'label' => __('Comment'),
'type' => 'textarea',
'placeholder' => 'Comment to be set',
'jinja_supported' => true,
],
];
}
@ -32,9 +33,8 @@ class Module_attribute_comment_operation extends Module_attribute_edition_operat
public function exec(array $node, WorkflowRoamingData $roamingData, array &$errors = []): bool
{
parent::exec($node, $roamingData, $errors);
$params = $this->getParamsWithValues($node);
$rData = $roamingData->getData();
$params = $this->getParamsWithValues($node, $rData);
$user = $roamingData->getUser();
$matchingItems = $this->getMatchingItemsForAttributes($node, $rData);
@ -52,7 +52,7 @@ class Module_attribute_comment_operation extends Module_attribute_edition_operat
{
$currentRData = $rData;
$currentRData['__currentAttribute'] = $attribute;
$renderedComment = $this->render_jinja_template($params['comment']['value'], $currentRData);
$renderedComment = $params['comment']['value'];
if ($attribute['comment'] !== $params['comment']['value']) {
$attribute['comment'] = $renderedComment;
}

View File

@ -36,9 +36,8 @@ class Module_attribute_ids_flag_operation extends Module_attribute_edition_opera
public function exec(array $node, WorkflowRoamingData $roamingData, array &$errors = []): bool
{
parent::exec($node, $roamingData, $errors);
$params = $this->getParamsWithValues($node);
$rData = $roamingData->getData();
$params = $this->getParamsWithValues($node, $rData);
$user = $roamingData->getUser();
$matchingItems = $this->getMatchingItemsForAttributes($node, $rData);

View File

@ -42,12 +42,12 @@ class Module_enrich_event extends WorkflowBaseActionModule
public function exec(array $node, WorkflowRoamingData $roamingData, array &$errors = []): bool
{
parent::exec($node, $roamingData, $errors);
$params = $this->getParamsWithValues($node);
$rData = $roamingData->getData();
$params = $this->getParamsWithValues($node, $rData);
if (empty($params['modules']['value'])) {
$errors[] = __('No enrichmnent module selected');
return false;
}
$rData = $roamingData->getData();
$event_id = $rData['Event']['id'];
$options = [
'user' => $roamingData->getUser(),

View File

@ -69,9 +69,8 @@ class Module_event_distribution_operation extends WorkflowBaseModule
public function exec(array $node, WorkflowRoamingData $roamingData, array &$errors = []): bool
{
parent::exec($node, $roamingData, $errors);
$params = $this->getParamsWithValues($node);
$rData = $roamingData->getData();
$params = $this->getParamsWithValues($node, $rData);
$user = $roamingData->getUser();
$matchingItems = $this->getMatchingItemsForAttributes($node, $rData);

View File

@ -30,10 +30,10 @@ class Module_push_zmq extends WorkflowBaseActionModule
public function exec(array $node, WorkflowRoamingData $roamingData, array &$errors=[]): bool
{
parent::exec($node, $roamingData, $errors);
$params = $this->getParamsWithValues($node);
$rData = $roamingData->getData();
$params = $this->getParamsWithValues($node, $rData);
$path = $params['data_extraction_path']['value'];
$data = $roamingData->getData();
$extracted = $this->extractData($data, $path);
$extracted = $this->extractData($rData, $path);
if ($extracted === false) {
$errors[] = __('Error while trying to extract data with path `%s`', $path);
return false;

View File

@ -39,12 +39,14 @@ class Module_send_mail extends WorkflowBaseActionModule
'label' => 'Mail template subject',
'type' => 'textarea',
'placeholder' => __('The **template** will be rendered using *Jinja2*!'),
'jinja_supported' => true,
],
[
'id' => 'mail_template_body',
'label' => 'Mail template body',
'type' => 'textarea',
'placeholder' => __('The **template** will be rendered using *Jinja2*!'),
'jinja_supported' => true,
],
];
}
@ -60,7 +62,8 @@ class Module_send_mail extends WorkflowBaseActionModule
public function exec(array $node, WorkflowRoamingData $roamingData, array &$errors = []): bool
{
parent::exec($node, $roamingData, $errors);
$params = $this->getParamsWithValues($node);
$rData = $roamingData->getData();
$params = $this->getParamsWithValues($node, $rData);
if (empty($params['recipients']['value'])) {
$errors[] = __('No recipient set.');
return false;
@ -69,10 +72,9 @@ class Module_send_mail extends WorkflowBaseActionModule
$errors[] = __('The mail template is empty.');
return false;
}
$rData = $roamingData->getData();
$renderedBody = $this->render_jinja_template($params['mail_template_body']['value'], $rData);
$renderedSubject = $this->render_jinja_template($params['mail_template_subject']['value'], $rData);
$renderedBody = $params['mail_template_body']['value'];
$renderedSubject = $params['mail_template_subject']['value'];
$users = [];
if (in_array('All accounts', $params['recipients']['value'])) {

View File

@ -77,7 +77,8 @@ class Module_splunk_hec_export extends Module_webhook
$errors[] = __('`Security.rest_client_enable_arbitrary_urls` is turned off');
return false;
}
$params = $this->getParamsWithValues($node);
$rData = $roamingData->getData();
$params = $this->getParamsWithValues($node, $rData);
if (empty($params['url']['value'])) {
$errors[] = __('URL not provided.');
return false;
@ -87,7 +88,6 @@ class Module_splunk_hec_export extends Module_webhook
return false;
}
$rData = $roamingData->getData();
$event_without_attributes = $rData['Event'];
unset($event_without_attributes['Attribute']);
unset($event_without_attributes['_AttributeFlattened']);

View File

@ -90,9 +90,8 @@ class Module_tag_operation extends WorkflowBaseActionModule
public function exec(array $node, WorkflowRoamingData $roamingData, array &$errors = []): bool
{
parent::exec($node, $roamingData, $errors);
$params = $this->getParamsWithValues($node);
$rData = $roamingData->getData();
$params = $this->getParamsWithValues($node, $rData);
$user = $roamingData->getUser();
if ($this->filtersEnabled($node)) {

View File

@ -67,9 +67,8 @@ class Module_tag_replacement_generic extends Module_tag_operation
public function exec(array $node, WorkflowRoamingData $roamingData, array &$errors = []): bool
{
$params = $this->getParamsWithValues($node);
$rData = $roamingData->getData();
$params = $this->getParamsWithValues($node, $rData);
$user = $roamingData->getUser();
if ($this->filtersEnabled($node)) {

View File

@ -32,18 +32,19 @@ class Module_telegram_send_alert extends Module_webhook
'label' => 'Message Body Template',
'type' => 'textarea',
'placeholder' => __('Template redendered using Jinja2'),
'jinja_supported' => true,
],
];
}
public function exec(array $node, WorkflowRoamingData $roamingData, array &$errors = []): bool
{
$params = $this->getParamsWithValues($node);
$rData = $roamingData->getData();
$rData = $roamingData->getData();
$params = $this->getParamsWithValues($node, $rData);
$bot_token = $params['bot_token']['value'];
$chat_id = $params['chat_id']['value'];
$message_body = $this->render_jinja_template($params['message_body_template']['value'], $rData);
$message_body = $params['message_body_template']['value'];
$data = [
'chat_id' => $chat_id,

View File

@ -67,6 +67,7 @@ class Module_webhook extends WorkflowBaseActionModule
'type' => 'textarea',
'default' => '',
'placeholder' => '',
'jinja_supported' => true,
],
[
'id' => 'headers',
@ -103,16 +104,16 @@ class Module_webhook extends WorkflowBaseActionModule
$errors[] = __('`Security.rest_client_enable_arbitrary_urls` is turned off');
return false;
}
$params = $this->getParamsWithValues($node);
$rData = $roamingData->getData();
$params = $this->getParamsWithValues($node, $rData);
if (empty($params['url']['value'])) {
$errors[] = __('URL not provided.');
return false;
}
$rData = $roamingData->getData();
$payload = '';
if (strlen($params['payload']['value']) > 0) {
$payload = $this->render_jinja_template($params['payload']['value'], $rData);
$payload = $params['payload']['value'];
} else {
$payload = $rData;
}

View File

@ -80,13 +80,13 @@ class Module_distribution_if extends WorkflowBaseLogicModule
public function exec(array $node, WorkflowRoamingData $roamingData, array &$errors=[]): bool
{
parent::exec($node, $roamingData, $errors);
$params = $this->getParamsWithValues($node);
$data = $roamingData->getData();
$params = $this->getParamsWithValues($node, $data);
$scope = $params['scope']['value'];
$operator = $params['condition']['value'];
$selected_distribution = $params['distribution']['value'];
$selected_sharing_groups = !empty($params['sharing_group_id']['value']) ? $params['sharing_group_id']['value'] : [];
$data = $roamingData->getData();
$final_distribution = $this->__getPropagatedDistribution($data['Event']);
if ($scope == 'attribute') {
$final_distribution = $this->__getPropagatedDistribution(

View File

@ -82,7 +82,8 @@ class Module_generic_filter_data extends WorkflowFilteringLogicModule
public function exec(array $node, WorkflowRoamingData $roamingData, array &$errors=[]): bool
{
parent::exec($node, $roamingData, $errors);
$params = $this->getParamsWithValues($node);
$rData = $roamingData->getData();
$params = $this->getParamsWithValues($node, $rData);
$selector = $params['selector']['value'];
$path = $params['hash_path']['value'];
$operator = $params['operator']['value'];
@ -90,7 +91,6 @@ class Module_generic_filter_data extends WorkflowFilteringLogicModule
$value_list = $params['value_list']['value'];
$valueToEvaluate = $operator == 'in_or' ? $value_list : $value;
$filteringLabel = $params['filtering-label']['value'];
$rData = $roamingData->getData();
$newRData = $rData;
if (empty($newRData['_unfilteredData'])) {

View File

@ -28,9 +28,9 @@ class Module_generic_filter_reset extends WorkflowFilteringLogicModule
public function exec(array $node, WorkflowRoamingData $roamingData, array &$errors=[]): bool
{
parent::exec($node, $roamingData, $errors);
$params = $this->getParamsWithValues($node);
$filteringLabel = $params['filtering-label']['value'];
$rData = $roamingData->getData();
$params = $this->getParamsWithValues($node, $rData);
$filteringLabel = $params['filtering-label']['value'];
$newRData = $rData['_unfilteredData'];
if (in_array($filteringLabel, array_keys($this->_genFilteringLabels()))) {

View File

@ -64,13 +64,13 @@ class Module_generic_if extends WorkflowBaseLogicModule
public function exec(array $node, WorkflowRoamingData $roamingData, array &$errors=[]): bool
{
parent::exec($node, $roamingData, $errors);
$params = $this->getParamsWithValues($node);
$data = $roamingData->getData();
$params = $this->getParamsWithValues($node, $data);
$path = $params['hash_path']['value'];
$operator = $params['operator']['value'];
$value = $params['value']['value'];
$value_list = $params['value_list']['value'];
$valueToEvaluate = $operator == 'in_or' ? $value_list : $value;
$data = $roamingData->getData();
$extracted = [];
if ($operator == 'equals' || $operator == 'not_equals') {
$extracted = Hash::get($data, $path, []);

View File

@ -60,13 +60,13 @@ class Module_organisation_if extends WorkflowBaseLogicModule
public function exec(array $node, WorkflowRoamingData $roamingData, array &$errors=[]): bool
{
parent::exec($node, $roamingData, $errors);
$params = $this->getParamsWithValues($node);
$data = $roamingData->getData();
$params = $this->getParamsWithValues($node, $data);
$org_type = $params['org_type']['value'];
$operator = $params['condition']['value'];
$selectedOrgs = !empty($params['org_id']['value']) ? $params['org_id']['value'] : [];
$selectedOrgs = is_array($selectedOrgs) ? $selectedOrgs : [$selectedOrgs]; // Backward compatibility for non-multiple `org_id`
$data = $roamingData->getData();
$path = 'Event.org_id';
if ($org_type == 'orgc') {
$path = 'Event.orgc_id';

View File

@ -35,10 +35,10 @@ class Module_published_if extends WorkflowBaseLogicModule
public function exec(array $node, WorkflowRoamingData $roamingData, array &$errors=[]): bool
{
parent::exec($node, $roamingData, $errors);
$params = $this->getParamsWithValues($node);
$data = $roamingData->getData();
$params = $this->getParamsWithValues($node, $data);
$operator = $params['condition']['value'];
$data = $roamingData->getData();
$path = 'Event.published';
$is_published = !empty(Hash::get($data, $path));
$eval = $this->evaluateCondition($is_published, $operator, true);

View File

@ -91,7 +91,8 @@ class Module_tag_if extends WorkflowBaseLogicModule
public function exec(array $node, WorkflowRoamingData $roamingData, array &$errors=[]): bool
{
parent::exec($node, $roamingData, $errors);
$params = $this->getParamsWithValues($node);
$data = $roamingData->getData();
$params = $this->getParamsWithValues($node, $data);
$selectedTags = !empty($params['tags']['value']) ? $params['tags']['value'] : [];
$selectedClusters = !empty($params['clusters']['value']) ? $params['clusters']['value'] : [];
@ -101,7 +102,6 @@ class Module_tag_if extends WorkflowBaseLogicModule
$allSelectedTags = array_merge($selectedTags, $selectedClusters);
$operator = $params['condition']['value'];
$scope = $params['scope']['value'];
$data = $roamingData->getData();
$extracted = $this->__getTagFromScope($scope, $data);
$eval = $this->evaluateCondition($extracted, $operator, $allSelectedTags);
return !empty($eval);

View File

@ -50,12 +50,12 @@ class Module_threat_level_if extends WorkflowBaseLogicModule
public function exec(array $node, WorkflowRoamingData $roamingData, array &$errors=[]): bool
{
parent::exec($node, $roamingData, $errors);
$params = $this->getParamsWithValues($node);
$data = $roamingData->getData();
$params = $this->getParamsWithValues($node, $data);
$operator = $params['condition']['value'];
$operator = $params['condition']['value'];
$selected_threatlevel = $params['threatlevel']['value'];
$data = $roamingData->getData();
$threatlevel_id = $data['Event']['threat_level_id'];
if ($operator == 'equals') {

View File

@ -649,7 +649,10 @@ $data_passed_to_if_module = [
</div>
<div class="tab-pane" id="modal-jinja2">
<h3><?= __('Jinja2 Syntax') ?></h3>
<h3>
<img src="/img/jinja.png" alt="Jinja icon" width="60" height="26">
<?= __('Jinja2 Syntax') ?>
</h3>
<p><i class="fa-fw <?= $this->FontAwesome->getClass('exclamation-triangle') ?>"></i> <?= __('For these examples, we consider the module received data under the MISP core format.') ?></p>
<p><i class="fa-fw <?= $this->FontAwesome->getClass('link') ?>"></i> <?= __('More documenation available on Jinja2 template designer documentation\'s') ?> <a href="https://jinja.palletsprojects.com/en/3.1.x/templates/"><?= __('website') ?></a></p>
<h4><?= __('You can use the dot <code>`.` </code> notation or the subscript syntax <code>`[]`</code> to access attributes of a variable') ?></h4>

View File

@ -320,6 +320,21 @@
border: 1px solid #ccc;
}
.drawflow_content_node textarea.jinja,
.drawflow_content_node input.jinja {
border: 1px solid #ae742daa;
}
.modal-body input.jinja {
border: 1px solid #ae742daa;
box-shadow: 0 1px 3px 0px #ae742d;
}
.start-codemirror.jinja + .CodeMirror.cm-s-default {
border: 1px solid #ae742daa;
box-shadow: 0 1px 3px 0px #ae742d;
border-top-color: #ccc;
}
.dropdown-menu li.disabled a {
pointer-events: none;
}

View File

@ -1734,6 +1734,19 @@ function genParameterWarning(options) {
return ''
}
function genJinjaIconIfSupported(options) {
if (!options.jinja_supported) {
return ''
}
return $('<img/>').attr({
src: "/img/jinja.png",
alt: "Jinja icon",
title: "This input supports Jinja2 templating",
width: "36",
height: "12",
})
}
function genSelect(options, forNode = true) {
var $container = $('<div>')
.addClass('node-param-container')
@ -1843,6 +1856,7 @@ function genInput(options, isTextArea, forNode = true) {
marginBbottom: 0,
})
.append(
genJinjaIconIfSupported(options),
$('<span>').text(options.label),
genParameterWarning(options)
)
@ -1856,6 +1870,9 @@ function genInput(options, isTextArea, forNode = true) {
} else {
$input = $('<input>').attr('type', 'text').css({height: '30px'})
}
if (options['jinja_supported']) {
$input.addClass('jinja')
}
$input.css({
width: '100%',
'box-sizing': 'border-box',