mirror of https://github.com/MISP/MISP
378 lines
12 KiB
PHP
378 lines
12 KiB
PHP
<?php
|
|
class WorkflowBaseModule
|
|
{
|
|
public $is_misp_module = false;
|
|
public $blocking = false;
|
|
public $is_custom = false;
|
|
public $expect_misp_core_format = false;
|
|
public $id = 'to-override';
|
|
public $name = 'to-override';
|
|
public $version = '0.1';
|
|
public $description = 'to-override';
|
|
public $icon = '';
|
|
public $icon_class = '';
|
|
public $inputs = 0;
|
|
public $outputs = 0;
|
|
public $multiple_output_connection = false;
|
|
public $support_filters = false;
|
|
public $saved_filters = [
|
|
['text' => 'selector', 'value' => ''],
|
|
['text' => 'value', 'value' => ''],
|
|
['text' => 'operator', 'value' => ''],
|
|
['text' => 'path', 'value' => ''],
|
|
];
|
|
public $params = [];
|
|
|
|
private $Workflow;
|
|
|
|
/** @var PubSubTool */
|
|
private static $loadedPubSubTool;
|
|
|
|
public function __construct()
|
|
{
|
|
}
|
|
|
|
public function debug(array $node, WorkflowRoamingData $roamingData, array $data=[]): void
|
|
{
|
|
if (!isset($this->Workflow)) {
|
|
$this->Workflow = ClassRegistry::init('Workflow');
|
|
}
|
|
$workflow = $roamingData->getWorkflow();
|
|
$path = sprintf('/debug/%s', $node['data']['id'] ?? '');
|
|
$this->Workflow->sendRequestToDebugEndpoint($workflow, $node, $path, $data);
|
|
}
|
|
|
|
protected function mergeNodeConfigIntoParameters($node): array
|
|
{
|
|
$fullIndexedParams = [];
|
|
foreach ($this->params as $param) {
|
|
$param['value'] = $node['data']['indexed_params'][$param['id']] ?? null;
|
|
$fullIndexedParams[$param['id']] = $param;
|
|
}
|
|
return $fullIndexedParams;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
protected function filtersEnabled($node): bool
|
|
{
|
|
$indexedFilters = $this->getFilters($node);
|
|
foreach ($indexedFilters as $k => $v) {
|
|
if ($v != '') {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
protected function getFilters($node): array
|
|
{
|
|
$indexedFilters = [];
|
|
$nodeParam = [];
|
|
foreach ($node['data']['saved_filters'] as $name => $value) {
|
|
$nodeParam[$name] = $value;
|
|
}
|
|
foreach ($this->saved_filters as $filter) {
|
|
$filter['value'] = $nodeParam[$filter['text']] ?? $filter['value'];
|
|
$indexedFilters[$filter['text']] = $filter['value'];
|
|
}
|
|
return $indexedFilters;
|
|
}
|
|
|
|
public function getConfig(): array
|
|
{
|
|
$reflection = new ReflectionObject($this);
|
|
$properties = [];
|
|
foreach ($reflection->getProperties() as $property) {
|
|
if ($property->isPublic()) {
|
|
$properties[$property->getName()] = $property->getValue($this);
|
|
}
|
|
}
|
|
return $properties;
|
|
}
|
|
|
|
public function exec(array $node, WorkflowRoamingData $roamingData, array &$errors=[]): bool
|
|
{
|
|
return true;
|
|
}
|
|
|
|
protected function push_zmq($message)
|
|
{
|
|
if (!self::$loadedPubSubTool) {
|
|
App::uses('PubSubTool', 'Tools');
|
|
$pubSubTool = new PubSubTool();
|
|
$pubSubTool->initTool();
|
|
self::$loadedPubSubTool = $pubSubTool;
|
|
}
|
|
$pubSubTool = self::$loadedPubSubTool;
|
|
$pubSubTool->workflow_push($message);
|
|
}
|
|
|
|
protected function render_jinja_template($template, array $data): string
|
|
{
|
|
$mispModule = ClassRegistry::init('Module');
|
|
$postData = [
|
|
'module' => 'jinja_template_rendering',
|
|
'text' => JsonTool::encode([
|
|
'template' => $template,
|
|
'data' => $data,
|
|
])
|
|
];
|
|
$result = $mispModule->queryModuleServer($postData, false, 'Enrichment', false, [], true);
|
|
if (!empty($result['error'])) {
|
|
return '';
|
|
}
|
|
$rendered = $result['results'][0]['values'][0];
|
|
return $rendered;
|
|
}
|
|
|
|
protected function logError($message)
|
|
{
|
|
$this->Log = ClassRegistry::init('Log');
|
|
$this->Log->createLogEntry('SYSTEM', 'exec_module', 'Workflow', $this->id, $message);
|
|
}
|
|
|
|
public function checkLoading()
|
|
{
|
|
return 'The Factory Must Grow';
|
|
}
|
|
|
|
public function extractData($data, $path)
|
|
{
|
|
$extracted = $data;
|
|
if (!empty($path)) {
|
|
try {
|
|
$extracted = Hash::extract($data, $path);
|
|
} catch (Exception $e) {
|
|
return false;
|
|
}
|
|
}
|
|
return $extracted;
|
|
}
|
|
|
|
protected function getMatchingItemsForAttributes(array $node, array $rData): array
|
|
{
|
|
if ($this->filtersEnabled($node)) {
|
|
$filters = $this->getFilters($node);
|
|
$extracted = $this->extractData($rData, $filters['selector']);
|
|
if ($extracted === false) {
|
|
return false;
|
|
}
|
|
$matchingItems = $this->getItemsMatchingCondition($extracted, $filters['value'], $filters['operator'], $filters['path']);
|
|
} else {
|
|
$matchingItems = Hash::extract($rData, 'Event._AttributeFlattened.{n}');
|
|
}
|
|
return $matchingItems;
|
|
}
|
|
|
|
protected function extractDataForFilters(array $node, WorkflowRoamingData $roamingData)
|
|
{
|
|
$rData = $roamingData->getData();
|
|
if (empty($this->support_filters)) {
|
|
return $rData;
|
|
}
|
|
$filters = $this->getFilters($node);
|
|
if (in_array(null, array_values($filters))) {
|
|
return $rData;
|
|
}
|
|
$extracted = $this->extractData($rData, $filters['selector']);
|
|
if ($extracted === false) {
|
|
return $rData;
|
|
}
|
|
$matchingItems = $this->getItemsMatchingCondition($extracted, $filters['value'], $filters['operator'], $filters['path']);
|
|
return $matchingItems;
|
|
}
|
|
|
|
protected function evaluateCondition($data, $operator, $value): bool
|
|
{
|
|
if ($operator == 'in') {
|
|
return is_array($data) && in_array($value, $data);
|
|
} elseif ($operator == 'not_in') {
|
|
return is_array($data) && !in_array($value, $data);
|
|
} elseif ($operator == 'equals') {
|
|
return !is_array($data) && $data == $value;
|
|
} elseif ($operator == 'not_equals') {
|
|
return !is_array($data) && $data != $value;
|
|
} elseif ($operator == 'in_or' || $operator == 'in_and' || $operator == 'not_in_or' || $operator == 'not_in_and') {
|
|
if (!is_array($data) || !is_array($value)) {
|
|
return false;
|
|
}
|
|
$matching = array_filter($data, function($item) use ($value) {
|
|
return in_array($item, $value);
|
|
});
|
|
if ($operator == 'in_or') {
|
|
return !empty($matching);
|
|
} elseif ($operator == 'in_and') {
|
|
sort($matching);
|
|
sort($value);
|
|
return array_values($matching) == array_values($value);
|
|
} elseif ($operator == 'not_in_or') {
|
|
return empty($matching);
|
|
} elseif ($operator == 'not_in_and') {
|
|
return array_values($matching) != array_values($value);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public function getItemsMatchingCondition($items, $value, $operator, $path)
|
|
{
|
|
foreach ($items as $i => $item) {
|
|
$subItem = $this->extractData($item, $path);
|
|
if (in_array($operator, ['equals', 'not_equals'])) {
|
|
$subItem = !empty($subItem) ? $subItem[0] : $subItem;
|
|
}
|
|
if ($operator == 'any_value' && !empty($subItem)) {
|
|
continue;
|
|
} else if (!$this->evaluateCondition($subItem, $operator, $value)) {
|
|
unset($items[$i]);
|
|
}
|
|
}
|
|
return $items;
|
|
}
|
|
|
|
protected function addNotification(array $errors, string $severity, string $text, string $description='', array $details=[], bool $showInSidebar=false, bool $showInNode=false): array
|
|
{
|
|
$errors[$severity][] = [
|
|
'text' => $text,
|
|
'description' => $description,
|
|
'details' => $details,
|
|
'__show_in_sidebar' => $showInSidebar,
|
|
'__show_in_node' => $showInNode,
|
|
];
|
|
return $errors;
|
|
}
|
|
|
|
public function diagnostic(): array
|
|
{
|
|
return [];
|
|
}
|
|
}
|
|
|
|
class WorkflowBaseTriggerModule extends WorkflowBaseModule
|
|
{
|
|
const OVERHEAD_LOW = 1;
|
|
const OVERHEAD_MEDIUM = 2;
|
|
const OVERHEAD_HIGH = 3;
|
|
|
|
public $scope = 'others';
|
|
public $blocking = false;
|
|
public $misp_core_format = false;
|
|
public $trigger_overhead = self::OVERHEAD_LOW;
|
|
public $trigger_overhead_message = '';
|
|
public $inputs = 0;
|
|
public $outputs = 1;
|
|
|
|
/**
|
|
* normalizeData Massage the data before entering the workflow
|
|
*
|
|
* @param array $data
|
|
* @return array|false
|
|
*/
|
|
public function normalizeData(array $data)
|
|
{
|
|
if (!empty($this->misp_core_format)) {
|
|
$converted = $this->convertData($data);
|
|
if (empty($converted)) {
|
|
return false;
|
|
}
|
|
return $converted;
|
|
}
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* convertData function
|
|
*
|
|
* @param array $data
|
|
* @return array
|
|
*/
|
|
protected function convertData(array $data): array
|
|
{
|
|
App::uses('WorkflowFormatConverterTool', 'Tools');
|
|
return WorkflowFormatConverterTool::convert($data);
|
|
}
|
|
}
|
|
|
|
class WorkflowBaseLogicModule extends WorkflowBaseModule
|
|
{
|
|
public $blocking = false;
|
|
public $inputs = 1;
|
|
public $outputs = 2;
|
|
}
|
|
|
|
class WorkflowBaseActionModule extends WorkflowBaseModule
|
|
{
|
|
protected $fastLookupArrayMispFormat = [];
|
|
protected $fastLookupArrayFlattened = [];
|
|
|
|
public function exec(array $node, WorkflowRoamingData $roamingData, array &$errors = []): bool
|
|
{
|
|
$rData = $roamingData->getData();
|
|
if ($this->expect_misp_core_format) {
|
|
$this->_buildFastLookupForRoamingData($rData);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
protected function _buildFastLookupForRoamingData($rData): void
|
|
{
|
|
if (!empty($rData['Event']['Attribute'])) {
|
|
foreach ($rData['Event']['Attribute'] as $i => $attribute) {
|
|
$this->fastLookupArrayMispFormat[$attribute['id']] = $i;
|
|
}
|
|
}
|
|
if (!empty($rData['Event']['Object'])) {
|
|
foreach ($rData['Event']['Object'] as $j => $object) {
|
|
foreach ($object['Attribute'] as $i => $attribute) {
|
|
$this->fastLookupArrayMispFormat[$attribute['id']] = [$j, $i];
|
|
}
|
|
}
|
|
}
|
|
foreach ($rData['Event']['_AttributeFlattened'] as $i => $attribute) {
|
|
$this->fastLookupArrayFlattened[$attribute['id']] = $i;
|
|
}
|
|
}
|
|
|
|
protected function _overrideAttribute(array $oldAttribute, array $newAttribute, array $rData): array
|
|
{
|
|
$attributeID = $oldAttribute['id'];
|
|
$rData['Event']['_AttributeFlattened'][$this->fastLookupArrayFlattened[$attributeID]] = $newAttribute;
|
|
if (is_array($this->fastLookupArrayMispFormat[$attributeID])) {
|
|
$objectID = $this->fastLookupArrayMispFormat[$attributeID][0];
|
|
$attributeID = $this->fastLookupArrayMispFormat[$attributeID][1];
|
|
$rData['Event']['Object'][$objectID]['Attribute'][$attributeID] = $newAttribute;
|
|
} else {
|
|
$attributeID = $this->fastLookupArrayMispFormat[$attributeID];
|
|
$rData['Event']['Attribute'][$attributeID] = $newAttribute;
|
|
}
|
|
return $rData;
|
|
}
|
|
}
|
|
|
|
class WorkflowFilteringLogicModule extends WorkflowBaseLogicModule
|
|
{
|
|
public $blocking = false;
|
|
public $inputs = 1;
|
|
public $outputs = 2;
|
|
|
|
protected function _genFilteringLabels(): array
|
|
{
|
|
$names = ['A', 'B', 'C', 'D', 'E', 'F'];
|
|
$labels = [];
|
|
foreach ($names as $name) {
|
|
$labels[$name] = __('Label %s', $name);
|
|
}
|
|
return $labels;
|
|
}
|
|
} |