chg: [workflow] Refactoring and allow running workflow by ID

pull/8530/head
Sami Mokaddem 2022-08-03 16:05:29 +02:00
parent 5ff4e04391
commit bd33dac909
No known key found for this signature in database
GPG Key ID: 164C473F627A06FA
4 changed files with 150 additions and 38 deletions

View File

@ -778,6 +778,9 @@ class ACLComponent extends Component
'moduleView'=> [],
'toggleModule'=> [],
'checkGraph'=> [],
'executeWorkflow'=> [],
'debugToggleField'=> [],
'massToggleField'=> [],
],
'workflowBlueprints' => [
'add' => [],

View File

@ -108,36 +108,47 @@ class WorkflowsController extends AppController
$this->set('menuData', array('menuList' => 'workflows', 'menuItem' => 'view'));
}
public function editor($trigger_id)
public function editor($id)
{
$modules = $this->Workflow->getModulesByType();
$trigger_ids = Hash::extract($modules['modules_trigger'], '{n}.id');
if (!in_array($trigger_id, $trigger_ids)) {
return $this->__getFailResponseBasedOnContext(
[__('Unkown trigger %s', $trigger_id)],
null,
'add',
$trigger_id,
['controller' => 'workflows', 'action' => 'triggers']
);
$trigger_id = false;
$workflow = false;
if (is_numeric($id)) {
$workflow_id = $id;
} else {
$trigger_id = $id;
}
$workflow = $this->Workflow->fetchWorkflowByTrigger($trigger_id, false);
if (empty($workflow)) { // Workflow do not exists yet. Create it.
$result = $this->Workflow->addWorkflow([
'name' => sprintf('Workflow for trigger %s', $trigger_id),
'data' => $this->Workflow->genGraphDataForTrigger($trigger_id),
'trigger_id' => $trigger_id,
]);
if (!empty($result['errors'])) {
$modules = $this->Workflow->getModulesByType();
if (!empty($trigger_id)) {
$trigger_ids = Hash::extract($modules['modules_trigger'], '{n}.id');
if (!in_array($trigger_id, $trigger_ids)) {
return $this->__getFailResponseBasedOnContext(
[__('Could not create workflow for trigger %s', $trigger_id), $result['errors']],
[__('Unkown trigger %s', $trigger_id)],
null,
'add',
$trigger_id,
['controller' => 'workflows', 'action' => 'editor']
['controller' => 'workflows', 'action' => 'triggers']
);
}
$workflow = $this->Workflow->fetchWorkflowByTrigger($trigger_id, false);
if (empty($workflow)) { // Workflow do not exists yet. Create it.
$result = $this->Workflow->addWorkflow([
'name' => sprintf('Workflow for trigger %s', $trigger_id),
'data' => $this->Workflow->genGraphDataForTrigger($trigger_id),
'trigger_id' => $trigger_id,
]);
if (!empty($result['errors'])) {
return $this->__getFailResponseBasedOnContext(
[__('Could not create workflow for trigger %s', $trigger_id), $result['errors']],
null,
'add',
$trigger_id,
['controller' => 'workflows', 'action' => 'editor']
);
}
$workflow = $this->Workflow->fetchWorkflowByTrigger($trigger_id, false);
}
} else {
$workflow = $this->Workflow->fetchWorkflow($workflow_id);
}
$modules = $this->Workflow->attachNotificationToModules($modules, $workflow);
$this->loadModel('WorkflowBlueprint');
@ -148,6 +159,23 @@ class WorkflowsController extends AppController
$this->set('workflowBlueprints', $workflowBlueprints);
}
public function executeWorkflow($workflow_id)
{
$this->request->allowMethod(['post', 'put']);
$blockingErrors = [];
$data = $this->request->data;
$result = $this->Workflow->executeWorkflow($workflow_id, $data, $blockingErrors);
if (!empty($logging) && empty($result['success'])) {
$logging['message'] = !empty($logging['message']) ? $logging['message'] : __('Error while executing workflow.');
$errorMessage = implode(', ', $blockingErrors);
$this->Workflow->loadLog()->createLogEntry('SYSTEM', $logging['action'], $logging['model'], $logging['id'], $logging['message'], __('Returned message: %s', $errorMessage));
}
return $this->RestResponse->viewData([
'success' => $result['success'],
'outcome' => $result['outcomeText'],
], $this->response->type());
}
public function triggers()
{
$triggers = $this->Workflow->getModulesByType('trigger');

View File

@ -74,7 +74,7 @@ class WorkflowFormatConverterTool
$convertedAttribute = JSONConverterTool::convertAttribute($attribute, true);
$convertedAttribute['Attribute']['_allTags'] = $allTags;
if ($convertedAttribute['Attribute']['object_id'] != 0) {
$objectModel = ClassRegistry::init('Object');
$objectModel = ClassRegistry::init('MispObject');
$object = $objectModel->fetchObjectSimple(self::$fakeSiteAdminUser, [
'conditions' => [
'Object.id' => $convertedAttribute['Attribute']['object_id'],

View File

@ -4,6 +4,7 @@ App::uses('WorkflowGraphTool', 'Tools');
class WorkflowDuplicatedModuleIDException extends Exception {}
class TriggerNotFoundException extends Exception {}
class ModuleNotFoundException extends Exception {}
class WorkflowNotFoundException extends Exception {}
class Workflow extends AppModel
@ -375,6 +376,36 @@ class Workflow extends AppModel
return !$hasMultipleOutputConnection;
}
/**
* executeWorkflow
*
* @param int $workflow_id
* @param array $data
* @param array $blockingErrors
* @return array
*/
public function executeWorkflow($workflow_id, array $data, array &$blockingErrors=[]): array
{
$this->loadAllWorkflowModules();
$workflow = $this->fetchWorkflow($workflow_id, true);
$graphData = !empty($workflow['Workflow']) ? $workflow['Workflow']['data'] : $workflow['data'];
$startNode = $this->workflowGraphTool->extractTriggerFromWorkflow($graphData, true);
$startNodeID = $startNode['id'];
$trigger_id = $startNode['data']['id'];
if ($startNode == -1) {
$blockingErrors[] = __('Invalid start node `%s`', $startNodeID);
return false;
}
$triggerModule = $this->getModuleClassByType('trigger', $trigger_id, true);
if (!empty($triggerModule->disabled)) {
return true;
}
$result = $this->__runWorkflow($workflow, $triggerModule, $data, $startNodeID, $blockingErrors);
return $result;
}
/**
* executeWorkflowForTrigger
*
@ -436,27 +467,38 @@ class Workflow extends AppModel
{
$this->loadAllWorkflowModules();
if (empty($this->loaded_modules['trigger'][$trigger_id])) {
throw new TriggerNotFoundException(__('Unknown trigger `%s`', $trigger_id));
}
$trigger = $this->loaded_modules['trigger'][$trigger_id];
if (!empty($trigger['disabled'])) {
$triggerModule = $this->getModuleClassByType('trigger', $trigger_id, true);
if (!empty($triggerModule->disabled)) {
return true;
}
$triggerModule = $this->loaded_classes['trigger'][$trigger_id];
$workflow = $this->fetchWorkflowByTrigger($trigger_id, true);
if (empty($workflow)) {
throw new WorkflowNotFoundException(__('Could not get workflow for trigger `%s`', $trigger_id));
}
$graphData = !empty($workflow['Workflow']) ? $workflow['Workflow']['data'] : $workflow['data'];
$startNode = $this->workflowGraphTool->getNodeIdForTrigger($graphData, $trigger_id);
if ($startNode == -1) {
$blockingErrors[] = __('Invalid start node `%s`', $startNode);
$startNodeID = $this->workflowGraphTool->getNodeIdForTrigger($graphData, $trigger_id);
if ($startNodeID == -1) {
$blockingErrors[] = __('Invalid start node `%s`', $startNodeID);
return false;
}
$result = $this->__runWorkflow($workflow, $triggerModule, $data, $startNodeID, $blockingErrors);
return $result['success'];
}
/**
* runWorkflow
*
* @param array $workflow
* @param $triggerModule
* @param array $data
* @param int $startNodeID
* @return array
*/
private function __runWorkflow(array $workflow, $triggerModule, array $data, $startNodeID, &$blockingErrors=[]): array
{
$this->Log = ClassRegistry::init('Log');
$message = __('Started executing workflow for trigger `%s` (%s)', $trigger_id, $workflow['Workflow']['id']);
$message = __('Started executing workflow for trigger `%s` (%s)', $triggerModule->id, $workflow['Workflow']['id']);
$this->Log->createLogEntry('SYSTEM', 'execute_workflow', 'Workflow', $workflow['Workflow']['id'], $message);
$this->__logToFile($workflow, $message);
$workflow = $this->__incrementWorkflowExecutionCount($workflow);
@ -465,7 +507,7 @@ class Workflow extends AppModel
$for_path = !empty($triggerModule->blocking) ? GraphWalker::PATH_TYPE_BLOCKING : GraphWalker::PATH_TYPE_NON_BLOCKING;
$this->sendRequestToDebugEndpoint($workflow, [], '/init?type=' . $for_path, $data);
$blockingPathExecutionSuccess = $this->walkGraph($workflow, $startNode, $for_path, $data, $blockingErrors, $walkResult);
$blockingPathExecutionSuccess = $this->walkGraph($workflow, $startNodeID, $for_path, $data, $blockingErrors, $walkResult);
$executionStoppedByStopModule = in_array('stop-execution', Hash::extract($walkResult, 'blocking_nodes.{n}.data.id'));
if (empty($blockingPathExecutionSuccess)) {
$message = __('Execution stopped. %s', PHP_EOL . implode(', ', $blockingErrors));
@ -477,12 +519,16 @@ class Workflow extends AppModel
} else if ($executionStoppedByStopModule) {
$outcomeText = 'blocked';
}
$message = __('Finished executing workflow for trigger `%s` (%s). Outcome: %s', $trigger_id, $workflow['Workflow']['id'], $outcomeText);
$message = __('Finished executing workflow for trigger `%s` (%s). Outcome: %s', $triggerModule->id, $workflow['Workflow']['id'], $outcomeText);
$this->Log->createLogEntry('SYSTEM', 'execute_workflow', 'Workflow', $workflow['Workflow']['id'], $message);
$this->__logToFile($workflow, $message);
$this->sendRequestToDebugEndpoint($workflow, [], '/end?outcome=' . $outcomeText, $walkResult);
return $blockingPathExecutionSuccess;
return [
'outcomeText' => $outcomeText,
'walkResult' => $walkResult,
'success' => $blockingPathExecutionSuccess,
];
}
/**
@ -635,20 +681,55 @@ class Workflow extends AppModel
return $moduleClass;
}
public function getModuleConfigByType($module_type, $id)
/**
* getModuleClassByType
*
* @param string $module_type
* @param string $id
* @param boolean $throwException
* @return
* @throws ModuleNotFoundException
*/
public function getModuleClassByType($module_type, $id, $throwException=false)
{
$this->loadAllWorkflowModules();
$moduleClass = $this->loaded_modules[$module_type][$id] ?? null;
$moduleClass = $this->loaded_classes[$module_type][$id] ?? null;
if (is_null($moduleClass) && !empty($throwException)) {
if ($module_type == 'trigger') {
throw new TriggerNotFoundException(__('Unknown module `%s` for module type `%s`', $id, $module_type));
} else {
throw new ModuleNotFoundException(__('Unknown module `%s` for module type `%s`', $id, $module_type));
}
}
return $moduleClass;
}
/**
* getModuleConfigByType
*
* @param string $module_type
* @param string $id
* @param boolean $throwException
* @return array
* @throws ModuleNotFoundException
*/
public function getModuleConfigByType($module_type, $id, $throwException=false): array
{
$this->loadAllWorkflowModules();
$moduleConfig = $this->loaded_modules[$module_type][$id] ?? null;
if (is_null($moduleConfig) && !empty($throwException)) {
throw new ModuleNotFoundException(__('Unknown module `%s` for module type `%s`', $id, $module_type));
}
return $moduleConfig;
}
public function attachNotificationToModules(array $modules, array $workflow): array
{
$trigger_is_misp_core_format = false;
$trigger_is_blocking = false;
$trigger_id = $this->workflowGraphTool->extractTriggerFromWorkflow($workflow['Workflow']['data'], false);
if (!empty($trigger_id)) {
$triggerClass = $this->loaded_classes['trigger'][$trigger_id];
$triggerClass = $this->getModuleClassByType('trigger', $trigger_id, true);
$trigger_is_misp_core_format = !empty($triggerClass->misp_core_format);
$trigger_is_blocking = !empty($triggerClass->blocking);
}