enabled user input subflows and fixed bugs

This commit is contained in:
frikky
2023-02-27 02:31:30 +01:00
parent f604068619
commit 3ed4d86a3b
12 changed files with 482 additions and 417 deletions
+48 -20
View File
@@ -209,7 +209,7 @@ const Admin = (props) => {
const [openEditor, setOpenEditor] = React.useState(false);
const [renderTextBox, setRenderTextBox] = React.useState(false);
const [openFileId, setOpenFileId] = React.useState(false);
const allowedFileTypes = ["txt", "py", "yaml","yml","json"]
const allowedFileTypes = ["txt", "py", "yaml", "yml","json", "html", "js", "csv",]
const runUpdateText = (text) =>{
fetch(`${globalUrl}/api/v1/files/${openFileId}/edit`, {
@@ -1017,6 +1017,36 @@ const Admin = (props) => {
});
};
const rerunCloudWorkflows = (environment) => {
alert.info("Starting execution reruns. This can run in the background.")
fetch(
`${globalUrl}/api/v1/environments/${environment.id}/rerun`,
{
method: "GET",
credentials: "include",
}
)
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for apps :O!");
return;
} else {
alert.error(response.reason);
//alert.info("Aborted all dangling workflows");
}
return response.json();
})
.then((responseJson) => {
console.log("Got response for execution: ", responseJson);
//console.log("RESPONSE: ", responseJson)
//setFiles(responseJson)
})
.catch((error) => {
//alert.error(error.toString())
});
};
const abortEnvironmentWorkflows = (environment) => {
//console.log("Aborting all workflows started >10 minutes ago, not finished");
@@ -3943,7 +3973,7 @@ const Admin = (props) => {
<List>
<ListItem>
<ListItemText
primary="Created"
primary="Updated"
style={{ maxWidth: 225, minWidth: 225 }}
/>
<ListItemText
@@ -3989,7 +4019,8 @@ const Admin = (props) => {
bgColor = "#1f2023";
}
const isDisabledButton = isCloud || file.filesize < 100000 && file.status === ("active") && allowedFileTypes.includes(file.filename.split(".")[1]) === true
const filenamesplit = file.filename.split(".")
const iseditable = file.filesize < 100000 && file.status === "active" && allowedFileTypes.includes(filenamesplit[filenamesplit.length-1])
return (
<ListItem
@@ -4006,7 +4037,7 @@ const Admin = (props) => {
minWidth: 225,
overflow: "hidden",
}}
primary={new Date(file.created_at * 1000).toISOString()}
primary={new Date(file.updated_at * 1000).toISOString()}
/>
<ListItemText
style={{
@@ -4099,13 +4130,13 @@ const Admin = (props) => {
<ListItemText
primary=<span style={{ display:"inline"}}>
<Tooltip
title={"Edit File"}
title={`Edit File (${allowedFileTypes.join(", ")})`}
style={{}}
aria-label={"Edit"}
>
<span>
<IconButton
disabled={isDisabledButton ? false : true}
disabled={!iseditable}
style = {{padding: "6px"}}
onClick={() => {
setOpenEditor(true)
@@ -4114,11 +4145,7 @@ const Admin = (props) => {
}}
>
<EditIcon
style={{
color: isDisabledButton === true
? "white"
: "grey",
}}
style={{color: iseditable ? "white" : "grey",}}
/>
</IconButton>
</span>
@@ -4888,9 +4915,7 @@ const Admin = (props) => {
>
<div style={{ display: "flex" }}>
<Button
variant={
environment.archived ? "contained" : "outlined"
}
variant={environment.archived ? "contained" : "outlined"}
style={{ borderRadius: "0px" }}
onClick={() => deleteEnvironment(environment)}
color="primary"
@@ -4900,16 +4925,19 @@ const Admin = (props) => {
<Button
variant={"outlined"}
style={{ borderRadius: "0px" }}
disabled={isCloud && environment.Name.toLowerCase() !== "cloud"}
onClick={() => {
console.log(
"Should clear executions for: ",
environment
);
abortEnvironmentWorkflows(environment);
console.log("Should clear executions for: ", environment);
if (isCloud && environment.Name.toLowerCase() === "cloud") {
rerunCloudWorkflows(environment);
} else {
abortEnvironmentWorkflows(environment);
}
}}
color="primary"
>
Clear
{isCloud && environment.Name.toLowerCase() === "cloud" ? "Rerun" : "Clear"}
</Button>
</div>
</ListItemText>
+220 -85
View File
@@ -274,6 +274,7 @@ const AngularWorkflow = (defaultprops) => {
const [triggerAuthentication, setTriggerAuthentication] = React.useState({});
const [triggerFolders, setTriggerFolders] = React.useState([]);
const [workflows, setWorkflows] = React.useState([]);
const [parentWorkflows, setParentWorkflows] = React.useState([]);
const [showEnvironment, setShowEnvironment] = React.useState(false);
const [editWorkflowDetails, setEditWorkflowDetails] = React.useState(false);
@@ -487,7 +488,6 @@ const AngularWorkflow = (defaultprops) => {
})
.then((responseJson) => {
if (responseJson !== undefined) {
setWorkflows(responseJson);
// Sets up subflow trigger with the right info
if (trigger_index > -1) {
@@ -529,6 +529,37 @@ const AngularWorkflow = (defaultprops) => {
}
}
}
if (workflows.length === 0) {
//console.log("First request. Checking for parent trigger (if this is subflow")
var parentworkflows = []
for (let workflowkey in responseJson) {
const innerworkflow = responseJson[workflowkey]
for (let triggerkey in innerworkflow.triggers) {
const trigger = innerworkflow.triggers[triggerkey]
if (trigger.trigger_type === "SUBFLOW") {
for (let paramkey in trigger.parameters) {
const param = trigger.parameters[paramkey]
if (param.name === "workflow" && param.value === props.match.params.key) {
parentworkflows.push({
id: innerworkflow.id,
name: innerworkflow.name,
image: innerworkflow.image,
})
}
}
}
}
}
if (parentworkflows.length > 0) {
setParentWorkflows(parentworkflows.filter(wf => wf.id !== props.match.params.key))
}
}
setWorkflows(responseJson);
}
})
.catch((error) => {
@@ -2324,54 +2355,85 @@ const AngularWorkflow = (defaultprops) => {
((nodedata.app_name !== "Shuffle Tools" &&
nodedata.app_name !== "Testing" &&
nodedata.app_name !== "Shuffle Workflow" &&
nodedata.app_name !== "User Input" &&
nodedata.app_name !== "Webhook" &&
nodedata.app_name !== "Schedule" &&
nodedata.app_name !== "Email") ||
nodedata.app_name !== "User Input") ||
nodedata.isStartNode)
) {
const allNodes = cy.nodes().jsons();
var found = false;
for (let nodekey in allNodes) {
const currentNode = allNodes[nodekey];
if (
currentNode.data.attachedTo === nodedata.id &&
currentNode.data.isDescriptor
) {
found = true;
console.log("FOUND THE NODE!");
break;
}
}
const allNodes = cy.nodes().jsons();
var found = false;
for (let nodekey in allNodes) {
const currentNode = allNodes[nodekey];
if (
currentNode.data.attachedTo === nodedata.id &&
currentNode.data.isDescriptor
) {
found = true;
console.log("FOUND THE NODE!");
break;
}
}
// Readding the icon after moving the node
if (!found) {
const iconInfo = GetIconInfo(nodedata);
const svg_pin = `<svg width="${svgSize}" height="${svgSize}" viewBox="0 0 ${svgSize} ${svgSize}" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="${iconInfo.icon}" fill="${iconInfo.iconColor}"></path></svg>`;
const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin);
if (nodedata.app_name === "Webhook" || nodedata.app_name === "Schedule" || nodedata.app_name === "Gmail" || nodedata.app_name === "Office365") {
console.log("Found triggers. Add!")
const offset = nodedata.isStartNode ? 36 : 44;
const decoratorNode = {
position: {
x: event.target.position().x + offset,
y: event.target.position().y + offset,
},
locked: true,
data: {
isDescriptor: true,
isValid: true,
is_valid: true,
label: "",
image: svgpin_Url,
imageColor: iconInfo.iconBackgroundColor,
attachedTo: nodedata.id,
},
};
if (!found) {
console.log("Find amount of executions for the specific nodetype: ", nodedata.app_name, "Executions: ", workflowExecutions)
// Find how many executions it has
var executions = 0
const matchingExecutions = workflowExecutions.filter((execution => execution.execution_source === nodedata.app_name.toLowerCase()))
console.log("Matches: ", matchingExecutions.length)
const color = matchingExecutions.length > 0 ? "#34a853" : "#ea4436"
const decoratorNode = {
position: {
x: event.target.position().x + 44,
y: event.target.position().y + 44,
},
locked: true,
data: {
isDescriptor: true,
isValid: true,
is_valid: true,
isTrigger: true,
label: `${matchingExecutions.length}`,
attachedTo: nodedata.id,
imageColor: color,
hasExecutions: true,
},
};
cy.add(decoratorNode).unselectify();
} else {
console.log("Node already exists - don't add descriptor node");
}
cy.add(decoratorNode)
}
} else {
// Readding the icon after moving the node
if (!found) {
const iconInfo = GetIconInfo(nodedata);
const svg_pin = `<svg width="${svgSize}" height="${svgSize}" viewBox="0 0 ${svgSize} ${svgSize}" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="${iconInfo.icon}" fill="${iconInfo.iconColor}"></path></svg>`;
const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin);
const offset = nodedata.isStartNode ? 36 : 44;
const decoratorNode = {
position: {
x: event.target.position().x + offset,
y: event.target.position().y + offset,
},
locked: true,
data: {
isDescriptor: true,
isValid: true,
is_valid: true,
label: "",
image: svgpin_Url,
imageColor: iconInfo.iconBackgroundColor,
attachedTo: nodedata.id,
},
};
cy.add(decoratorNode).unselectify();
} else {
console.log("Node already exists - don't add descriptor node");
}
}
}
originalLocation = {
@@ -2676,6 +2738,11 @@ const AngularWorkflow = (defaultprops) => {
return;
} else if (data.isDescriptor) {
console.log("Can't select descriptor");
if (data.isTrigger) {
console.log("But maybe we can select trigger descriptor? Maybe open execution tab?")
setExecutionModalOpen(true)
}
event.target.unselect();
return;
}
@@ -4211,6 +4278,11 @@ const AngularWorkflow = (defaultprops) => {
});
};
const addRunCountButton = (event) => {
// Count executions?
// Maybe it shouldn't be onclick?
}
const addCopyButton = (event) => {
var parentNode = cy.$("#" + event.target.data("id"));
if (parentNode.data("isButton") || parentNode.data("buttonId")) return;
@@ -4379,7 +4451,52 @@ const AngularWorkflow = (defaultprops) => {
//if (parentNode.data("isButton") || parentNode.data("buttonId")) return;
if (nodedata.app_name !== undefined && !workflow.public === true) {
const allNodes = cy.nodes().jsons();
const allNodes = cy.nodes().jsons();
if (nodedata.app_name === "Webhook" || nodedata.app_name === "Schedule" || nodedata.app_name === "Gmail" || nodedata.app_name === "Office365") {
console.log("In this :)")
var found = false;
for (let nodekey in allNodes) {
const currentNode = allNodes[nodekey];
if (
currentNode.data.attachedTo === nodedata.id &&
currentNode.data.isDescriptor
) {
found = true;
console.log("FOUND THE NODE!");
break;
}
}
if (!found) {
console.log("Find amount of executions for the specific nodetype: ", nodedata.app_name, "Executions: ", workflowExecutions)
// Find how many executions it has
var executions = 0
const matchingExecutions = workflowExecutions.filter((execution => execution.execution_source === nodedata.app_name.toLowerCase()))
console.log("Matches: ", matchingExecutions.length)
const color = matchingExecutions.length > 0 ? "#34a853" : "#ea4436"
const decoratorNode = {
position: {
x: event.target.position().x + 44,
y: event.target.position().y + 44,
},
locked: true,
data: {
isDescriptor: true,
isValid: true,
is_valid: true,
isTrigger: true,
label: `${matchingExecutions.length}`,
attachedTo: nodedata.id,
imageColor: color,
hasExecutions: true,
},
};
cy.add(decoratorNode)
}
}
var found = false;
for (var _key in allNodes) {
@@ -4413,7 +4530,10 @@ const AngularWorkflow = (defaultprops) => {
if (nodedata.type === "TRIGGER") {
if (nodedata.trigger_type === "SUBFLOW" || nodedata.trigger_type === "USERINPUT") {
addCopyButton(event);
}
} else {
// Check how many executions from the source
addRunCountButton(event);
}
} else {
addCopyButton(event);
addStartnodeButton(event);
@@ -4467,7 +4587,7 @@ const AngularWorkflow = (defaultprops) => {
// locked: true,
// })
//}
}
}
if (event.target !== undefined && event.target !== null) {
event.target.animate(
@@ -4722,6 +4842,7 @@ const AngularWorkflow = (defaultprops) => {
return decoratorNode;
});
const triggers = workflow.triggers.map((trigger) => {
const node = {};
node.position = trigger.position;
@@ -5017,14 +5138,7 @@ const AngularWorkflow = (defaultprops) => {
}
// App length necessary cus of cy initialization
if (
// First load - gets the workflow
elements.length === 0 &&
workflow.actions !== undefined &&
!graphSetup &&
Object.getOwnPropertyNames(workflow).length > 0
) {
if (elements.length === 0 && workflow.actions !== undefined && !graphSetup && Object.getOwnPropertyNames(workflow).length > 0) {
setGraphSetup(true);
setupGraph();
console.log("In graph setup")
@@ -10073,6 +10187,10 @@ const AngularWorkflow = (defaultprops) => {
name: "custom_response_body",
value: "",
};
workflow.triggers[selectedTriggerIndex].parameters[4] = {
name: "await_response",
value: "v1",
};
setWorkflow(workflow);
} else {
// Always update
@@ -11065,26 +11183,6 @@ const AngularWorkflow = (defaultprops) => {
style={{ paddingLeft: 10, backgroundColor: inputColor }}
row
>
<FormControlLabel
control={
<Checkbox
checked={
workflow.triggers[selectedTriggerIndex].parameters[2] !==
undefined &&
workflow.triggers[
selectedTriggerIndex
].parameters[2].value.includes("subflow")
}
onChange={() => {
setTriggerOptionsWrapper("subflow");
}}
color="primary"
value="subflow"
disabled
/>
}
label={<div style={{ color: "white" }}>Subflow</div>}
/>
<FormControlLabel
control={
<Checkbox
@@ -11119,12 +11217,23 @@ const AngularWorkflow = (defaultprops) => {
}
label={<div style={{ color: "white" }}>SMS</div>}
/>
<FormControlLabel
control={
<Checkbox
checked={workflow.triggers[selectedTriggerIndex].parameters[2] !== undefined && workflow.triggers[selectedTriggerIndex].parameters[2].value.includes("subflow")}
onChange={() => {
setTriggerOptionsWrapper("subflow");
}}
color="primary"
value="subflow"
/>
}
label={<div style={{ color: "white" }}>Subflow</div>}
/>
</FormGroup>
{workflow.triggers[selectedTriggerIndex].parameters[2] !==
undefined &&
workflow.triggers[
selectedTriggerIndex
].parameters[2].value.includes("email") ? (
workflow.triggers[selectedTriggerIndex].parameters[2].value.includes("email") ? (
<TextField
style={{
backgroundColor: inputColor,
@@ -11187,11 +11296,7 @@ const AngularWorkflow = (defaultprops) => {
}}
/>
) : null}
{workflow.triggers[selectedTriggerIndex].parameters[2] !==
undefined &&
workflow.triggers[
selectedTriggerIndex
].parameters[2].value.includes("subflow") ? (
{workflow.triggers[selectedTriggerIndex].parameters[2] !== undefined && workflow.triggers[selectedTriggerIndex].parameters[2].value.includes("subflow") ? (
<TextField
style={{
backgroundColor: inputColor,
@@ -11213,8 +11318,7 @@ const AngularWorkflow = (defaultprops) => {
workflow.triggers[selectedTriggerIndex].parameters[5].value
}
onBlur={(event) => {
workflow.triggers[selectedTriggerIndex].parameters[5].value =
event.target.value;
workflow.triggers[selectedTriggerIndex].parameters[5].value = event.target.value;
setWorkflow(workflow);
setUpdate(Math.random());
}}
@@ -11565,6 +11669,36 @@ const AngularWorkflow = (defaultprops) => {
<h2 style={{ margin: 0 }}>{workflow.name}</h2>
</Breadcrumbs>
</div>
<div style={{display: "flex", marginLeft: 10, }}>
{parentWorkflows.slice(0,5).map((wf, index) => {
return (
<a href={`/workflows/${wf.id}`} target="_blank" rel="noopener noreferrer" key={index}>
<Tooltip arrow placement="left" title={
<span style={{}}>
{wf.image !== undefined && wf.image !== null && wf.image.length > 0 ?
<img
src={wf.image}
alt={wf.name}
style={{ backgroundColor: theme.palette.surfaceColor, maxHeight: 200, minHeigth: 200, borderRadius: theme.palette.borderRadius, }}
/>
: null}
<Typography>
Parent workflow: '{wf.name}'
</Typography>
</span>
} placement="bottom">
<span onClick={() => {
console.log("Click: ", wf)
}}>
<img src={theme.palette.defaultImage} style={{height: 25, width: 25, cursor: "pointer", border: 15, marginRight: 5, marginTop: 5, filter: "grayscale(90%)", }} />
</span>
</Tooltip>
</a>
)
})}
</div>
</div>
);
};
@@ -14146,6 +14280,7 @@ const AngularWorkflow = (defaultprops) => {
width: imgsize,
height: imgsize,
border: `2px solid ${statusColor}`,
filter: curapp === undefined ? "grayscale(100%)" : null,
}}
/>
)}
+17 -12
View File
@@ -222,22 +222,23 @@ const parseCurl = (s) => {
return out;
};
// Basically CRUD for each category + special
export const appCategories = [
{
"name": "Communication",
"color": "#FFC107",
"icon": "communication",
"action_labels": ["List Messages", "Send Message",],
"action_labels": ["List Messages", "Send Message", "Get Message", "Search messages"],
}, {
"name": "SIEM",
"color": "#FFC107",
"icon": "siem",
"action_labels": ["Get alerts", "Search", "Create detection",],
"action_labels": ["List Alerts", "Search", "Create detection", "Add hash to lookup_list",],
}, {
"name": "Eradication",
"color": "#FFC107",
"icon": "eradication",
"action_labels": ["List tickets", "Update ticket", "Block hash", "Isolate host"],
"action_labels": ["List Alerts", "Update Alert", "Create detection", "Block hash", "Search Hosts", "Isolate host"],
}, {
"name": "Cases",
"color": "#FFC107",
@@ -247,27 +248,27 @@ export const appCategories = [
"name": "Assets",
"color": "#FFC107",
"icon": "assets",
"action_labels": [],
"action_labels": ["List Assets", "Get Asset", "Search Assets", "Search Users", "Search endpoints", "Search vulnerabilities"],
}, {
"name": "Intel",
"color": "#FFC107",
"icon": "intel",
"action_labels": [],
"action_labels": ["Get IOC", "Search IOC", "Create IOC", "Update IOC", "Delete IOC", "Add IOC",],
}, {
"name": "IAM",
"color": "#FFC107",
"icon": "iam",
"action_labels": [],
"action_labels": ["Get Identity", "Get Asset", "Search Identity", "Reset Password", "Disable user", ],
}, {
"name": "Network",
"color": "#FFC107",
"icon": "network",
"action_labels": ["Block IP",],
"action_labels": ["Get Rules", "Allow IP", "Block IP",],
}, {
"name": "Other",
"color": "#FFC107",
"icon": "other",
"action_labels": [],
"action_labels": ["Update Info", "Get Info", "Get Status", "Get Version", "Get Health", "Get Config", "Get Configs", "Get Configs by type", "Get Configs by name", "Run script"],
},
]
@@ -3152,11 +3153,15 @@ const AppCreator = (defaultprops) => {
<Select
fullWidth
onChange={(e) => {
console.log("Should change: ", e.target.value)
console.log("Should change: ", e.target.value, " Index: ", index)
actions[index].action_label = e.target.value
setActions(actions)
setUpdate(Math.random());
const foundIndex = actions.findIndex((action) => action.name === data.name)
console.log("Found index: ", foundIndex)
if (foundIndex !== undefined && foundIndex !== null && foundIndex >= 0) {
actions[foundIndex].action_label = e.target.value
setActions(actions)
setUpdate(Math.random())
}
}}
value={data.action_label}
style={{
+39 -4
View File
@@ -99,6 +99,7 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged
const [description, setDescription] = useState("");
const [video, setVideo] = useState("");
const [blogpost, setBlogpost] = useState("");
const [workflowOutline, setWorkflowOutline] = useState("");
const [selectedWorkflows, setSelectedWorkflows] = useState([])
const [firstLoad, setFirstLoad] = useState(true)
@@ -188,6 +189,11 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged
parsedUsecase.mitre = mitreTags
}
if (workflowOutline !== inputUsecase.workflow_outline) {
inputUsecase.workflow_outline = workflowOutline
parsedUsecase.workflow_outline = workflowOutline
}
fetch(globalUrl + "/api/v1/workflows/usecases", {
method: "POST",
headers: {
@@ -303,6 +309,10 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged
if (inputUsecase.extra_buttons !== undefined && inputUsecase.extra_buttons !== null) {
subcase.extra_buttons = inputUsecase.extra_buttons
}
if (inputUsecase.workflow_outline !== undefined && inputUsecase.workflow_outline !== null) {
subcase.workflow_outline = inputUsecase.workflow_outline
}
}
}
@@ -433,6 +443,12 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged
if (subcase.mitre !== undefined && subcase.mitre !== null) {
setMitreTags(subcase.mitre)
}
if (subcase.workflow_outline !== undefined && subcase.workflow_outline !== null) {
setWorkflowOutline(subcase.workflow_outline)
} else {
setWorkflowOutline("")
}
}}
>
<EditIcon style={{ color: usecase.color }} />
@@ -502,9 +518,6 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged
<div style={{marginTop: 25, display: "flex", minHeight: 400, maxHeight: 400, marginRight: 15, }}>
{editing ?
<div style={{flex: 1, marginRight: 50, }}>
<Typography variant="h6">
Editing!
</Typography>
<TextField
style={{
marginTop: 10,
@@ -553,6 +566,26 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged
}}
id="videoEditing"
/>
<TextField
style={{
marginTop: 10,
marginRight: 10,
}}
variant="outlined"
fullWidth
color="primary"
label="Workflow Outline"
placeholder={"Workflow Outline"}
value={workflowOutline}
multiline
minRows={3}
onChange={(event) => {
setWorkflowOutline(event.target.value)
}}
id="workflowOutline"
tabIndex="-1"
/>
<div
style={{
display: 'flex',
@@ -573,6 +606,7 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged
setDescription("")
setVideo("")
setBlogpost("")
setWorkflowOutline("")
setEditing(false)
}}
>
@@ -590,10 +624,11 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged
cursor: "pointer"
}}
onClick={(event) => {
setEditing(false)
setUsecaseItem(inputUsecase)
setEditing(false)
setDescription("")
setVideo("")
setWorkflowOutline("")
setBlogpost("")
}}
>