diff --git a/backend/go-app/go.mod b/backend/go-app/go.mod index 5475e9b0..90508fdf 100644 --- a/backend/go-app/go.mod +++ b/backend/go-app/go.mod @@ -2,7 +2,7 @@ module main go 1.16 -replace github.com/shuffle/shuffle-shared => ../../../shuffle-shared +//replace github.com/shuffle/shuffle-shared => ../../../shuffle-shared //replace github.com/frikky/kin-openapi => ../../../../git/kin-openapi //replace github.com/frikky/go-elasticsearch => ../../../../git/go-elasticsearch @@ -24,7 +24,7 @@ require ( github.com/h2non/filetype v1.1.3 github.com/nirasan/go-oauth-pkce-code-verifier v0.0.0-20170819232839-0fbfe93532da // indirect github.com/satori/go.uuid v1.2.0 - github.com/shuffle/shuffle-shared v0.2.7 + github.com/shuffle/shuffle-shared v0.2.9 go4.org v0.0.0-20201209231011-d4a079459e60 // indirect golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce google.golang.org/api v0.65.0 diff --git a/docker-compose.yml b/docker-compose.yml index 02e07e9a..f36d9a75 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,7 @@ version: '3' services: frontend: - #build: ./frontend + build: ./frontend image: ghcr.io/frikky/shuffle-frontend:nightly container_name: shuffle-frontend hostname: shuffle-frontend @@ -16,7 +16,7 @@ services: depends_on: - backend backend: - #build: ./backend + build: ./backend image: ghcr.io/frikky/shuffle-backend:nightly container_name: shuffle-backend hostname: ${BACKEND_HOSTNAME} @@ -68,7 +68,7 @@ services: container_name: shuffle-opensearch environment: - bootstrap.memory_lock=true - - "OPENSEARCH_JAVA_OPTS=-Xms2048m -Xmx2048m" # minimum and maximum Java heap size, recommend setting both to 50% of system RAM + - "OPENSEARCH_JAVA_OPTS=-Xms1024m -Xmx1024m" # minimum and maximum Java heap size, recommend setting both to 50% of system RAM - cluster.routing.allocation.disk.threshold_enabled=false - cluster.name=shuffle-cluster - node.name=shuffle-opensearch diff --git a/frontend/src/components/DetectionFramework.jsx b/frontend/src/components/DetectionFramework.jsx index 384989d4..bb74e843 100644 --- a/frontend/src/components/DetectionFramework.jsx +++ b/frontend/src/components/DetectionFramework.jsx @@ -4,7 +4,7 @@ import { securityFramework } from "./LandingpageUsecases.jsx"; import CytoscapeComponent from 'react-cytoscapejs'; import frameworkStyle from '../frameworkStyle.jsx'; import WorkflowSearch from './Workflowsearch.jsx'; -import uuid from "uuid"; +import { v4 as uuidv4 } from "uuid"; import theme from '../theme'; import { useAlert } from "react-alert"; @@ -518,7 +518,7 @@ export const usecases = { } const Framework = (props) => { - const {globalUrl, isLoaded, showOptions, selectedOption, rolling, frameworkData, size } = props; + const {globalUrl, isLoaded, showOptions, selectedOption, rolling, frameworkData, size, inputUsecase, isLoggedIn, } = props; const [cy, setCy] = React.useState() const [edgesStarted, setEdgesStarted] = React.useState(false) const [graphDone, setGraphDone] = React.useState(false) @@ -532,6 +532,50 @@ const Framework = (props) => { const alert = useAlert() + const setUsecaseItem = (inputUsecase) => { + var parsedUsecase = inputUsecase + const edges = cy.edges().jsons() + + parsedUsecase.process = [] + for (var key in edges) { + const edge = edges[key] + console.log("Edge: ", edge) + parsedUsecase.process.push(edge.data) + } + + fetch(globalUrl + "/api/v1/workflows/usecases", { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify(parsedUsecase), + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for framework!"); + } + + return response.json(); + }) + .then((responseJson) => { + if (responseJson.success === false) { + if (responseJson.reason !== undefined) { + alert.error("Failed updating: " + responseJson.reason) + } else { + alert.error("Failed to update framework for your org.") + } + } else { + alert.info("Updated usecase.") + } + }) + .catch((error) => { + alert.error(error.toString()); + //setFrameworkLoaded(true) + }) + } + const setFrameworkItem = (data) => { fetch(globalUrl + "/api/v1/apps/frameworkConfiguration", { method: "POST", @@ -586,17 +630,18 @@ const Framework = (props) => { const foundelement = cy.getElementById(discoveryData.id) if (foundelement !== undefined && foundelement !== null) { foundelement.data("large_image", newSelectedApp.image_url) - foundelement.data("text_margin_y", "60px") foundelement.data("margin_x", "0px") - foundelement.data("margin_y", "50px") - foundelement.data("width", "85px") - foundelement.data("height", "85px") + foundelement.data("margin_y", "0px") + foundelement.data("text_margin_y", `${60*scale}px`) + foundelement.data("width", `${85*scale}px`) + foundelement.data("height", `${85*scale}px`) } setFrameworkItem(submitValue) }, [newSelectedApp]) + const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"; @@ -649,7 +694,6 @@ const Framework = (props) => { const [usecaseType, setUsecaseType] = React.useState(0) const [selectedUsecase, setSelectedUsecase] = React.useState(selectedOption !== undefined ? selectedOption : "Phishing") - const elements = [] const surfaceColor = "#27292D" @@ -680,12 +724,15 @@ const Framework = (props) => { setSelectedUsecase(value) - const allEdges = cy.edges().jsons() - for (var key in allEdges) { - const newedge = allEdges[key] - const foundelement = cy.getElementById(newedge.data.id) - if (foundelement !== undefined && foundelement !== null) { - foundelement.remove() + const edges = cy.edges() + if (edges !== undefined) { + const allEdges = edges.jsons() + for (var key in allEdges) { + const newedge = allEdges[key] + const foundelement = cy.getElementById(newedge.data.id) + if (foundelement !== undefined && foundelement !== null) { + foundelement.remove() + } } } @@ -719,7 +766,6 @@ const Framework = (props) => { if (cy !== undefined && cyDone && !animationStarted) { const foundelement = cy.getElementById("CASES") - console.log("FOUND: ", foundelement) if (foundelement !== undefined && foundelement !== null) { setAnimationStarted(true) @@ -819,6 +865,7 @@ const Framework = (props) => { ) } + const onNodeSelect = (event) => { const data = event.target.data(); console.log("Node: ", data) @@ -841,7 +888,7 @@ const Framework = (props) => { const onEdgeSelect = (event) => { console.log(event) - if (event.target.data("human") === undefined) { + if (event.target.data("human") === undefined || event.target.data("human") === false) { event.target.data("human", true) } else if (event.target.data("human") === true) { event.target.remove() @@ -854,9 +901,6 @@ const Framework = (props) => { } if (graphDone && cyDone === false) { - //const [cyDone, setCyDone] = React.useState(false) - console.log("IN CYDONe") - cy.fit(null, 200); cy.on("select", "edge", (e) => onEdgeSelect(e)); cy.on("add", "edge", (e) => onEdgeAdded(e)); @@ -878,9 +922,9 @@ const Framework = (props) => { const baselocationX = 285*scale const baselocationY = 50*scale const shiftmodifier = 3*scale - const svgSize = `${40*scale}px` + console.log("Framework: ", parsedFrameworkData) const fontSize = `${12*scale}px` const defaultSize = `${85*scale}px` @@ -1217,6 +1261,27 @@ const Framework = (props) => { } changeUsecase(selectedUsecase, usecaseType) + + if (inputUsecase !== undefined && inputUsecase !== null) { + console.log("Got usecase: ", inputUsecase) + + for (var key in inputUsecase.process) { + if (inputUsecase.process[key].source === "" || inputUsecase.process[key].target === "") { + continue + } + + console.log("Edge: ", inputUsecase.process[key]) + inputUsecase.process[key].label = parseInt(key)+1 + inputUsecase.process[key].id = uuidv4(); + + cy.add({ + group: "edges", + data: inputUsecase.process[key], + }) + } + //changeUsecase(inputUsecase, 0) + //setSelectedUsecase(value) + } } if (selectedOption !== undefined && selectedUsecase !== selectedOption) { @@ -1571,6 +1636,13 @@ const Framework = (props) => { setCy(incy) }} /> + {isCloud && inputUsecase !== undefined && inputUsecase !== null && isLoggedIn === true && Object.keys(inputUsecase).length > 0 ? + + : null} ) } diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index a0d1002a..ae7eda2a 100644 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -165,6 +165,8 @@ const ParsedAction = (props) => { actionDelayChange, getParents, isCloud, + lastSaved, + setLastSaved, } = props; //const theme = useTheme(); @@ -1487,6 +1489,9 @@ const ParsedAction = (props) => { } onBlur={(event) => { baseHelperText = calculateHelpertext(event.target.value) + if (setLastSaved !== undefined) { + setLastSaved(false) + } }} /> ); @@ -2201,35 +2206,6 @@ const ParsedAction = (props) => { return null; }; - const expansionModal = ( - { - setExpansionModalOpen(false); - }} - PaperProps={{ - style: { - backgroundColor: theme.palette.surfaceColor, - color: "white", - minWidth: 600, - padding: 50, - pointerEvents: "auto", - maxHeight: 550, - overflowY: "auto", - overflowX: "hidden", - zIndex: 10012, - } - }} - > - - Workflow Variable - - Hello - - ); - //const CustomPopper = function (props) { // const classes = useStyles() // return @@ -2239,7 +2215,6 @@ const ParsedAction = (props) => { return (
- {expansionModal} {hideExtraTypes === true ? null : (
diff --git a/frontend/src/components/ShuffleCodeEditor.jsx b/frontend/src/components/ShuffleCodeEditor.jsx index a99e57b8..6cd04326 100644 --- a/frontend/src/components/ShuffleCodeEditor.jsx +++ b/frontend/src/components/ShuffleCodeEditor.jsx @@ -12,6 +12,7 @@ import { import Checkbox from '@mui/material/Checkbox'; import { orange } from '@mui/material/colors'; +import { isMobile } from "react-device-detect" import { FullscreenExit as FullscreenExitIcon, @@ -86,7 +87,7 @@ const CodeEditor = (props) => { var popup = false for(var ch=0; ch { for(var occ = 0; occ { // console.log() // for (var j = 0; j < actionlist.length; j++) { - // if(found[i].slice(1,).toLowerCase() == actionlist[j].autocomplete.toLowerCase()){ + // if(found[i].slice(1,).toLowerCase() === actionlist[j].autocomplete.toLowerCase()){ // input = input.replace(found[i], JSON.stringify(actionlist[j].example)); // console.log(input) // console.log(actionlist[j].example) @@ -182,7 +183,7 @@ const CodeEditor = (props) => { var dollar_occurence = [] for(var ch=0; ch { for (var occ = 0; occ action.autocomplete.toLowerCase() === variable_occurence[occ].slice(1,).toLowerCase()) - if(correctVariable == undefined) { - value.markText({line:i, ch:dollar_occurence[occ]}, {line:i, ch:dollar_occurence_len[occ]+dollar_occurence[occ]}, {"css": "background-color: red; border-radius: 4px; color: white"}) + if(correctVariable === undefined) { + value.markText({line:i, ch:dollar_occurence[occ]}, {line:i, ch:dollar_occurence_len[occ]+dollar_occurence[occ]}, {"css": "background-color: #f86a3e; padding-top: 2px; padding-bottom: 2px; color: white"}) } else{ - value.markText({line:i, ch:dollar_occurence[occ]}, {line:i, ch:dollar_occurence_len[occ]+dollar_occurence[occ]}, {"css": "background-color: #8b8e26; border-radius: 4px; color: white"}) + value.markText({line:i, ch:dollar_occurence[occ]}, {line:i, ch:dollar_occurence_len[occ]+dollar_occurence[occ]}, {"css": "background-color: #8b8e26; padding-top: 2px; padding-bottom: 2px; color: white"}) } // console.log(correctVariables) } @@ -237,7 +238,13 @@ const CodeEditor = (props) => { // console.log(currentLocation) var code_lines = localcodedata.split('\n') - code_lines[currentLine] = code_lines[currentLine].slice(0,currentLocation[1]) + "$" + swapVariable + code_lines[currentLine].slice(currentLocation[1]+currentVariable.length,) + var parsedVariable = currentVariable + if (currentVariable === undefined || currentVariable === null) { + console.log("Location: ", currentLocation) + parsedVariable= "$" + } + + code_lines[currentLine] = code_lines[currentLine].slice(0,currentLocation[1]) + "$" + swapVariable + code_lines[currentLine].slice(currentLocation[1]+parsedVariable.length,) // console.log(code_lines) var updatedCode = code_lines.join('\n') // console.log(updatedCode) @@ -255,7 +262,7 @@ const CodeEditor = (props) => { // console.log(found[i]); for (var j = 0; j < actionlist.length; j++) { - if(found[i].slice(1,).toLowerCase() == actionlist[j].autocomplete.toLowerCase()){ + if(found[i].slice(1,).toLowerCase() === actionlist[j].autocomplete.toLowerCase()){ input = input.replace(found[i], JSON.stringify(actionlist[j].example)); // console.log(input) // console.log(actionlist[j].example) @@ -293,8 +300,8 @@ const CodeEditor = (props) => { style: { backgroundColor: theme.palette.surfaceColor, color: "white", - minWidth: 600, - padding: 25, + minWidth: isMobile ? "100%" : 600, + padding: isMobile ? "25px 10px 25px 10px" : 25, border: theme.palette.defaultBorder, zIndex: 10012, }, @@ -318,7 +325,7 @@ const CodeEditor = (props) => { { style={{ margin: 10, padding: 10, - width: 250, + width: isMobile ? "100%" : 250, // textOverflow: 'ellipsis' }} > @@ -478,10 +485,10 @@ const CodeEditor = (props) => { Dark Theme { - if (codeTheme == "gruvbox-dark") { + if (codeTheme === "gruvbox-dark") { setcodeTheme("duotone-light") } - if (codeTheme == "duotone-light"){ + if (codeTheme === "duotone-light"){ setcodeTheme("gruvbox-dark") } }} @@ -500,68 +507,70 @@ const CodeEditor = (props) => {
- - - Output - - - {validation === true ? - { - //handleReactJsonClipboard(copy); - }} - displayDataTypes={false} - onSelect={(select) => { - //HandleJsonCopy(validate.result, select, "exec"); - }} - name={"JSON autocompletion"} - /> - : -

- {expOutput} -

+ + Output + + + } + {isMobile ? null : + validation === true ? + { + //handleReactJsonClipboard(copy); + }} + displayDataTypes={false} + onSelect={(select) => { + //HandleJsonCopy(validate.result, select, "exec"); + }} + name={"JSON autocompletion"} + /> + : +

+ {expOutput} +

}

JSON Validation: {validation ? "Correct" : "Incorrect"} @@ -573,48 +582,44 @@ const CodeEditor = (props) => { display: 'flex', }} > -

- -
-
- -
+ +
) } diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index 0abe16bf..4e2102bd 100644 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -87,6 +87,7 @@ import { ExpandMore as ExpandMoreIcon, VpnKey as VpnKeyIcon, AddComment as AddCommentIcon, + Edit as EditIcon, } from "@material-ui/icons"; import { @@ -251,6 +252,7 @@ const AngularWorkflow = (defaultprops) => { const [triggerFolders, setTriggerFolders] = React.useState([]); const [workflows, setWorkflows] = React.useState([]); const [showEnvironment, setShowEnvironment] = React.useState(false); + const [editWorkflowDetails, setEditWorkflowDetails] = React.useState(false); const [workflow, setWorkflow] = React.useState({}); const [userSettings, setUserSettings] = React.useState({}); @@ -1257,98 +1259,100 @@ const AngularWorkflow = (defaultprops) => { }; const executeWorkflow = (executionArgument, startNode, hasSaved) => { - if (hasSaved === false) { - setExecutionRequestStarted(true); - saveWorkflow(workflow, executionArgument, startNode); - console.log("FIXME: Might have forgotten to save before executing."); - return; - } + ReactDOM.unstable_batchedUpdates(() => { + if (hasSaved === false) { + setExecutionRequestStarted(true); + saveWorkflow(workflow, executionArgument, startNode); + console.log("FIXME: Might have forgotten to save before executing."); + return; + } - if (workflow.public) { - alert.info("Save it to get a new version"); - } + if (workflow.public) { + alert.info("Save it to get a new version"); + } - var returncheck = monitorUpdates(); - if (!returncheck) { - alert.error("No startnode set."); - return; - } + var returncheck = monitorUpdates(); + if (!returncheck) { + alert.error("No startnode set."); + return; + } - setVisited([]); - setExecutionRequest({}); - stop(); + setVisited([]); + setExecutionRequest({}); + stop(); - var curelements = cy.elements(); - for (var i = 0; i < curelements.length; i++) { - curelements[i].addClass("not-executing-highlight"); - } + var curelements = cy.elements(); + for (var i = 0; i < curelements.length; i++) { + curelements[i].addClass("not-executing-highlight"); + } - const data = { execution_argument: executionArgument, start: startNode }; - fetch( - globalUrl + "/api/v1/workflows/" + props.match.params.key + "/execute", - { - method: "POST", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - credentials: "include", - body: JSON.stringify(data), - } - ) - .then((response) => { - if (response.status !== 200) { - console.log("Status not 200 for WORKFLOW EXECUTION :O!"); - } + const data = { execution_argument: executionArgument, start: startNode }; + fetch( + globalUrl + "/api/v1/workflows/" + props.match.params.key + "/execute", + { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + body: JSON.stringify(data), + } + ) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for WORKFLOW EXECUTION :O!"); + } - return response.json(); - }) - .then((responseJson) => { - if (!responseJson.success) { - alert.error("Failed to start: " + responseJson.reason); - setExecutionRunning(false); - setExecutionRequestStarted(false); - stop(); + return response.json(); + }) + .then((responseJson) => { + if (!responseJson.success) { + alert.error("Failed to start: " + responseJson.reason); + setExecutionRunning(false); + setExecutionRequestStarted(false); + stop(); - for (var i = 0; i < curelements.length; i++) { - curelements[i].removeClass("not-executing-highlight"); - } - return; - } else { - setExecutionRunning(true); - setExecutionRequestStarted(false); - } + for (var i = 0; i < curelements.length; i++) { + curelements[i].removeClass("not-executing-highlight"); + } + return; + } else { + setExecutionRunning(true); + setExecutionRequestStarted(false); + } - if ( - responseJson.execution_id === "" || - responseJson.execution_id === undefined || - responseJson.authorization === "" || - responseJson.authorization === undefined - ) { - alert.error("Something went wrong during execution startup"); - console.log("BAD RESPONSE FOR EXECUTION: ", responseJson); - setExecutionRunning(false); - setExecutionRequestStarted(false); - stop(); + if ( + responseJson.execution_id === "" || + responseJson.execution_id === undefined || + responseJson.authorization === "" || + responseJson.authorization === undefined + ) { + alert.error("Something went wrong during execution startup"); + console.log("BAD RESPONSE FOR EXECUTION: ", responseJson); + setExecutionRunning(false); + setExecutionRequestStarted(false); + stop(); - for (var i = 0; i < curelements.length; i++) { - curelements[i].removeClass("not-executing-highlight"); - } - return; - } + for (var i = 0; i < curelements.length; i++) { + curelements[i].removeClass("not-executing-highlight"); + } + return; + } - setExecutionRequest({ - execution_id: responseJson.execution_id, - authorization: responseJson.authorization, - }); - setExecutionData({}); - setExecutionModalOpen(true); - setExecutionModalView(1); - start(); - }) - .catch((error) => { - alert.error(error.toString()); - }); + setExecutionRequest({ + execution_id: responseJson.execution_id, + authorization: responseJson.authorization, + }); + setExecutionData({}); + setExecutionModalOpen(true); + setExecutionModalView(1); + start(); + }) + .catch((error) => { + alert.error(error.toString()); + }); + }) }; // This can be used to only show prioritzed ones later @@ -1542,6 +1546,35 @@ const AngularWorkflow = (defaultprops) => { }); }; + // Searhc by username, userId, workflow, appId should all work + const getUserProfile = (username) => { + fetch(`${globalUrl}/api/v1/users/creators/${username}`, { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for WORKFLOW EXECUTION :O!"); + } + + + return response.json(); + }) + .then((responseJson) => { + if (responseJson.success !== false) { + console.log("Found creator: ", responseJson) + setCreatorProfile(responseJson) + } + }) + .catch((error) => { + console.log(error); + }) + } + const getWorkflow = (workflow_id, sourcenode) => { console.log( //`Getting workflow ${workflow_id} with append value ${sourcenode}` @@ -1582,9 +1615,7 @@ const AngularWorkflow = (defaultprops) => { } if (responseJson.public) { - alert.info( - "This workflow is public. Save the workflow to " - ); + alert.info("This workflow is public. Save the workflow to start using it."); console.log("RESP: ", responseJson) if (Object.getOwnPropertyNames(creatorProfile).length === 0) { @@ -8686,13 +8717,13 @@ const AngularWorkflow = (defaultprops) => { workflow.triggers[selectedTriggerIndex].parameters[2] = {"name": "auth_headers", "value": ""} setWorkflow(workflow) } else { - // Always update - const newUrl = referenceUrl+"webhook_"+selectedTrigger.id - //console.log("Validating webhook url: ", newUrl) - if (newUrl !== workflow.triggers[selectedTriggerIndex].parameters[0].value) { - console.log("Url is wrong - updating") - workflow.triggers[selectedTriggerIndex].parameters[0].value = newUrl - setWorkflow(workflow) + if (selectedTrigger.environment !== "cloud") { + const newUrl = referenceUrl+"webhook_"+selectedTrigger.id + if (newUrl !== workflow.triggers[selectedTriggerIndex].parameters[0].value) { + console.log("Url is wrong - should update. This functionality is temporarily disabled.") + //workflow.triggers[selectedTriggerIndex].parameters[0].value = newUrl + //setWorkflow(workflow) + } } } @@ -8898,13 +8929,13 @@ const AngularWorkflow = (defaultprops) => { // Always update const newUrl = referenceUrl + "webhook_" + selectedTrigger.id; //console.log("Validating webhook url: ", newUrl); - if ( - newUrl !== workflow.triggers[selectedTriggerIndex].parameters[0].value - ) { - console.log("Url is wrong - updating"); - workflow.triggers[selectedTriggerIndex].parameters[0].value = newUrl; - setWorkflow(workflow); - } + if (selectedTrigger.environment !== "cloud") { + if (newUrl !== workflow.triggers[selectedTriggerIndex].parameters[0].value) { + console.log("Url is wrong. NOT updating because of hybrid."); + //workflow.triggers[selectedTriggerIndex].parameters[0].value = newUrl; + //setWorkflow(workflow); + } + } } const trigger_header_auth = @@ -8976,28 +9007,21 @@ const AngularWorkflow = (defaultprops) => { fullWidth onChange={(e) => { selectedTrigger.environment = e.target.value; - setSelectedTrigger(selectedTrigger); if (e.target.value === "cloud") { - const tmpvalue = - workflow.triggers[ - selectedTriggerIndex - ].parameters[0].value.split("/"); + const tmpvalue = workflow.triggers[selectedTriggerIndex].parameters[0].value.split("/"); const urlpath = tmpvalue.slice(3, tmpvalue.length); const newurl = "https://shuffler.io/" + urlpath.join("/"); - workflow.triggers[selectedTriggerIndex].parameters[0].value = - newurl; + workflow.triggers[selectedTriggerIndex].parameters[0].value = newurl; } else { - const tmpvalue = - workflow.triggers[ - selectedTriggerIndex - ].parameters[0].value.split("/"); + const tmpvalue = workflow.triggers[selectedTriggerIndex].parameters[0].value.split("/"); const urlpath = tmpvalue.slice(3, tmpvalue.length); - const newurl = - window.location.origin + "/" + urlpath.join("/"); - workflow.triggers[selectedTriggerIndex].parameters[0].value = - newurl; + const newurl = window.location.origin + "/" + urlpath.join("/"); + workflow.triggers[selectedTriggerIndex].parameters[0].value = newurl; } + console.log("New value: ", workflow.triggers[selectedTriggerIndex].parameters[0]) + selectedTrigger.parameters[0] = workflow.triggers[selectedTriggerIndex].parameters[0] + setSelectedTrigger(selectedTrigger); setWorkflow(workflow); setUpdate(Math.random()); }} @@ -9086,10 +9110,8 @@ const AngularWorkflow = (defaultprops) => { } }} helperText={ - workflow.triggers[selectedTriggerIndex].parameters[0] - .value !== undefined && - workflow.triggers[selectedTriggerIndex].parameters[0] - .value !== null && + workflow.triggers[selectedTriggerIndex].parameters[0].value !== undefined && + workflow.triggers[selectedTriggerIndex].parameters[0].value !== null && (workflow.triggers[ selectedTriggerIndex ].parameters[0].value.includes("localhost") || @@ -9115,7 +9137,7 @@ const AngularWorkflow = (defaultprops) => { }} fullWidth disabled - defaultValue={ + value={ workflow.triggers[selectedTriggerIndex].parameters[0].value } color="primary" @@ -10515,31 +10537,33 @@ const AngularWorkflow = (defaultprops) => { /> )} - - - - - + {userdata.avatar === creatorProfile.github_avatar ? null : + + + + + + } {workflow.public ? ( { globalUrl={globalUrl} setSelectedActionEnvironment={setSelectedActionEnvironment} requiresAuthentication={requiresAuthentication} + setLastSaved={setLastSaved} + lastSaved={lastSaved} /> } else if (Object.getOwnPropertyNames(selectedComment).length > 0) { @@ -10849,34 +10875,6 @@ const AngularWorkflow = (defaultprops) => { //}}>Execute websocket // - // Searhc by username, userId, workflow, appId should all work - const getUserProfile = (username) => { - fetch(`${globalUrl}/api/v1/users/creators/${username}`, { - method: "GET", - headers: { - "Content-Type": "application/json", - Accept: "application/json", - }, - credentials: "include", - }) - .then((response) => { - if (response.status !== 200) { - console.log("Status not 200 for WORKFLOW EXECUTION :O!"); - } - - - return response.json(); - }) - .then((responseJson) => { - console.log("Found creator: ", responseJson) - if (responseJson.success !== false) { - setCreatorProfile(responseJson) - } - }) - .catch((error) => { - console.log(error); - }) - } const leftView = workflow.public === true ?
@@ -10929,14 +10927,6 @@ const AngularWorkflow = (defaultprops) => {
: null } -
- - Mitre Att&ck: - - - TBD - -
{appGroup.length > 0 ?
@@ -10967,6 +10957,15 @@ const AngularWorkflow = (defaultprops) => {
: null} + +
+ + Mitre Att&ck: + + + TBD + +
Related Workflows: @@ -10985,6 +10984,19 @@ const AngularWorkflow = (defaultprops) => {
: null} + {userdata.avatar === creatorProfile.github_avatar ? + + : null} : isMobile && leftViewOpen ?
{ return (
- { - if (!showVariable) { - setOpen(!open) - } - }} - > + {data.value.length > 60 ? + { + if (!showVariable) { + setOpen(!open) + } + }} + > + + {data.name} {showVariable ? data.value : null} + + + : {data.name}: {showVariable ? data.value : null} - + } {open ? {
); + const editWorkflowModal = + { + setEditWorkflowDetails(false) + }} + PaperProps={{ + style: { + pointerEvents: "auto", + backgroundColor: surfaceColor, + color: "white", + border: theme.palette.defaultBorder, + maxWidth: "100%", + padding: 50, + }, + }} + > + Edit workflow! + + const ExecutionVariableModal = (props) => { const { variableInfo } = props @@ -13604,6 +13654,7 @@ const parsedExecutionArgument = () => { {authenticationModal} {codePopoutModal} {configureWorkflowModal} + {editWorkflowModal} { +const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLoggedIn}) => { const [expandedIndex, setExpandedIndex] = useState(-1); const [expandedItem, setExpandedItem] = useState(-1); + const [inputUsecase, setInputUsecase] = useState({}); + + const [editing, setEditing] = useState(false); + const [description, setDescription] = useState(""); + const [video, setVideo] = useState(""); + const [blogpost, setBlogpost] = useState(""); + + const [mitreTags, setMitreTags] = useState([]); + let navigate = useNavigate(); if (keys === undefined || keys === null || keys.length === 0) { return null } + const getUsecase = (name, index, subindex) => { + fetch(globalUrl + "/api/v1/workflows/usecases/"+name.replaceAll(" ", "_"), { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for framework!"); + } + + return response.json(); + }) + .then((responseJson) => { + if (responseJson.success === false) { + setInputUsecase({ + "name": name, + }) + } else { + setInputUsecase(responseJson) + } + + setExpandedIndex(index) + setExpandedItem(subindex) + }) + .catch((error) => { + //alert.error(error.toString()); + setInputUsecase({}) + setExpandedIndex(index) + setExpandedItem(subindex) + }) + } + + const setUsecaseItem = (inputUsecase) => { + var parsedUsecase = inputUsecase + + if (blogpost !== inputUsecase.blogpost) { + inputUsecase.blogpost = blogpost + parsedUsecase.blogpost = blogpost + } + + if (video !== inputUsecase.video) { + inputUsecase.video = video + parsedUsecase.video = video + } + + if (description !== inputUsecase.description) { + inputUsecase.description = description + parsedUsecase.description = description + } + + if (mitreTags !== inputUsecase.mitre) { + inputUsecase.mitre = mitreTags + parsedUsecase.mitre = mitreTags + } + + fetch(globalUrl + "/api/v1/workflows/usecases", { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify(parsedUsecase), + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for framework!"); + } + + return response.json(); + }) + .then((responseJson) => { + if (responseJson.success === false) { + if (responseJson.reason !== undefined) { + //alert.error("Failed updating: " + responseJson.reason) + } else { + //alert.error("Failed to update framework for your org.") + } + } else { + //alert.info("Updated usecase.") + } + }) + .catch((error) => { + //alert.error(error.toString()); + //setFrameworkLoaded(true) + }) + } + return (
@@ -81,6 +186,23 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl}) => { } const selectedItem = subindex === expandedItem && index === expandedIndex + if (selectedItem && subcase.name !== undefined && inputUsecase.name !== undefined) { + if (subcase.name.toLowerCase().replaceAll(" ", "_") === inputUsecase.name.toLowerCase().replaceAll(" ", "_")) { + console.log("Input: ", inputUsecase) + if (inputUsecase.description !== undefined && inputUsecase.description !== null) { + subcase.description = inputUsecase.description + } + + if (inputUsecase.blogpost !== undefined && inputUsecase.blogpost !== null) { + subcase.blogpost = inputUsecase.blogpost + } + + if (inputUsecase.video !== undefined && inputUsecase.video !== null) { + subcase.video = inputUsecase.video + } + } + } + const finished = subcase.matches.length > 0 //const backgroundColor = selectedItem ? "inherit" : finished ? "inherit" : usecase.color const backgroundColor = "inherit" @@ -90,14 +212,12 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl}) => { { if (selectedItem) { } else { - if (subcase.description !== undefined && subcase.description !== null && subcase.description.length > 0) { - setExpandedIndex(index) - setExpandedItem(subindex) - } + //if (subcase.description !== undefined && subcase.description !== null && subcase.description.length > 0) { + getUsecase(subcase.name, index, subindex) + //} } }}> { - console.log("Clicked: ", subcase) }}> {!selectedItem ?
@@ -131,7 +251,7 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl}) => { placement="top" > { }} > @@ -168,10 +288,37 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl}) => { {subcase.name} - - Description: {subcase.description} -
+ {isLoggedIn === true ? + + { + setEditing(true) + if (subcase.description !== undefined && subcase.description !== null) { + setDescription(subcase.description) + } + + if (subcase.blogpost !== undefined && subcase.blogpost !== null) { + setBlogpost(subcase.blogpost) + } + + if (subcase.video !== undefined && subcase.video !== null) { + setVideo(subcase.video) + } + + if (subcase.mitre !== undefined && subcase.mitre !== null) { + setMitreTags(subcase.mitre) + } + }} + > + + + + : null} {subcase.blogpost !== null && subcase.blogpost !== undefined && subcase.blogpost.length > 0 ? { onClick={(e) => { setExpandedItem(-1) setExpandedIndex(-1) + setEditing(false) + setInputUsecase({}) }} > @@ -231,65 +380,168 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl}) => {
- {/* - {subcase.name} + + Editing! + + { + setDescription(event.target.value) + }} + id="descriptionEditng" + /> + { + setBlogpost(event.target.value) + }} + id="blogpostEditing" + /> + { + setVideo(event.target.value) + }} + id="videoEditing" + /> +
+ + +
+
+ : +
+ + {subcase.description} + + + Your workflow{subcase.matches.length === 1 ? "" : "s"} ({subcase.matches.length}) + + {subcase.matches.length > 0 ? + + {subcase.matches.map((workflow, workflowindex) => { + return ( + + + + ) + })} + + : +
+ + No workflow selected yet. + +
+ } + {isCloud !== false ? +
+ { + navigate("/search?tab=workflows&q="+subcase.name) + }}> + Public workflows + + {/* +
+ + No workflows yet. + +
+ */} +
+ : null} +
+ } +
- */} - -
- + }}> + +
- -
- - Your workflow{subcase.matches.length === 1 ? "" : "s"} ({subcase.matches.length}) - - {subcase.matches.length > 0 ? - - {subcase.matches.map((workflow, workflowindex) => { - return ( - - - - ) - })} - - : -
- - No workflow selected yet. - -
- } - {isCloud !== false ? - - Public workflows - - : null} -
-
} @@ -449,11 +701,44 @@ const Dashboard = (props) => { const [selectedUsecases, setSelectedUsecases] = useState([]); const [usecases, setUsecases] = useState([]); const [workflows, setWorkflows] = useState([]); + const [frameworkData, setFrameworkData] = useState(undefined); const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"; + const getFramework = () => { + fetch(globalUrl + "/api/v1/apps/frameworkConfiguration", { + method: "GET", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for framework!"); + } + + return response.json(); + }) + .then((responseJson) => { + if (responseJson.success === false) { + if (responseJson.reason !== undefined) { + //alert.error("Failed loading: " + responseJson.reason) + } else { + //alert.error("Failed to load framework for your org.") + } + } else { + setFrameworkData(responseJson) + } + }) + .catch((error) => { + alert.error(error.toString()); + }) + } + const getAvailableWorkflows = () => { fetch(globalUrl + "/api/v1/workflows", { method: "GET", @@ -605,6 +890,7 @@ const Dashboard = (props) => { useEffect(() => { getAvailableWorkflows() + getFramework() //fetchUsecases() }, []); @@ -865,7 +1151,7 @@ const Dashboard = (props) => {
{usecases !== null && usecases !== undefined && usecases.length > 0 ? -
+
{usecases.map((usecase, index) => { return ( {
: null} - + {treeKeys.length > 0 ? diff --git a/frontend/src/views/FrameworkWrapper.jsx b/frontend/src/views/FrameworkWrapper.jsx index 23ec05b0..6ba0fe94 100644 --- a/frontend/src/views/FrameworkWrapper.jsx +++ b/frontend/src/views/FrameworkWrapper.jsx @@ -1,4 +1,5 @@ import React, { useEffect, useState } from 'react'; +import ReactDOM from "react-dom" import DetectionFramework from "../components/DetectionFramework.jsx"; import { useAlert } from "react-alert"; @@ -39,14 +40,18 @@ const Framework = (props) => { } else { alert.error("Failed to load framework for your org.") } - } else { + setFrameworkLoaded(true) - setFrameworkData(responseJson) + } else { + ReactDOM.unstable_batchedUpdates(() => { + setFrameworkData(responseJson) + setFrameworkLoaded(true) + }) } }) .catch((error) => { - alert.error(error.toString()); setFrameworkLoaded(true) + alert.error(error.toString()); }) } @@ -63,7 +68,7 @@ const Framework = (props) => {
- {frameworkLoaded === true ? + {frameworkLoaded === true && isLoaded ? { const [newWorkflowTags, setNewWorkflowTags] = React.useState([]); const [defaultReturnValue, setDefaultReturnValue] = React.useState(""); + const [blogpost, setBlogpost] = React.useState(""); + const [status, setStatus] = React.useState("test"); const [deleteModalOpen, setDeleteModalOpen] = React.useState(false); const [publishModalOpen, setPublishModalOpen] = React.useState(false); @@ -816,7 +818,10 @@ const Workflows = (props) => { data.tags, data.default_return_value, {}, - false + false, + [], + "", + data.status, ) .then((response) => { if (response !== undefined) { @@ -830,7 +835,10 @@ const Workflows = (props) => { data.tags, data.default_return_value, data, - false + false, + [], + "", + data.status ).then((response) => { if (response !== undefined) { alert.success(`Successfully imported ${data.name}`); @@ -883,12 +891,18 @@ const Workflows = (props) => { if (responseJson !== undefined) { setWorkflows(responseJson); fetchUsecases(responseJson) + + var setProdFilter = false if (responseJson !== undefined) { - var actionnamelist = []; var parsedactionlist = []; for (var key in responseJson) { + const workflow = responseJson[key] + if (workflow.status === "production") { + setProdFilter = true + } + for (var actionkey in responseJson[key].actions) { const action = responseJson[key].actions[actionkey]; //console.log("Action: ", action) @@ -905,10 +919,22 @@ const Workflows = (props) => { setActionImageList(parsedactionlist); } - setFilteredWorkflows(responseJson); - setWorkflowDone(true); - + + if (setProdFilter === true) { + setFilters(["status:production"]); + const newWorkflows = responseJson.filter(workflow => workflow.status === "production") + console.log(newWorkflows) + if (newWorkflows !== undefined && newWorkflows !== null) { + setFilteredWorkflows(newWorkflows); + } else { + setFilteredWorkflows(responseJson); + } + } else { + setFilteredWorkflows(responseJson); + } + // Ensures the zooming happens only once per load + setWorkflowDone(true); setTimeout(() => { setFirstLoad(false) }, 100) @@ -946,7 +972,7 @@ const Workflows = (props) => { if (workflow.usecase_ids !== undefined && workflow.usecase_ids !== null) { for (var usecasekey in workflow.usecase_ids) { if (workflow.usecase_ids[usecasekey].toLowerCase() === subcategory.name.toLowerCase()) { - console.log("Got match: ", workflow.usecase_ids[usecasekey]) + //console.log("Got match: ", workflow.usecase_ids[usecasekey]) category.matches.push({ "workflow": workflow.id, @@ -1247,7 +1273,8 @@ const Workflows = (props) => { // Add correct ID's for triggers // Add mag - + + data.status = "test" let dataStr = JSON.stringify(data); let dataUri = "data:application/json;charset=utf-8," + encodeURIComponent(dataStr); @@ -1638,6 +1665,7 @@ const Workflows = (props) => { } } + console.log("data: ", data) return (
@@ -1667,7 +1695,16 @@ const Workflows = (props) => { {image}
- + + {data.image !== undefined && data.image !== null && data.image.length > 0 ? + {data.name} + : null} + + Edit {data.name} + + + } placement="bottom"> { } { editingWorkflow, redirect, currentUsecases, + inputblogpost, + inputstatus, ) => { var method = "POST"; var extraData = ""; @@ -1905,6 +1944,8 @@ const Workflows = (props) => { if (tags !== undefined) { workflowdata["tags"] = tags; } + workflowdata["blogpost"] = inputblogpost + workflowdata["status"] = inputstatus if (defaultReturnValue !== undefined) { workflowdata["default_return_value"] = defaultReturnValue; @@ -2005,7 +2046,10 @@ const Workflows = (props) => { data.tags, data.default_return_value, {}, - false + false, + [], + "", + data.status, ) .then((response) => { if (response !== undefined) { @@ -2022,7 +2066,10 @@ const Workflows = (props) => { data.tags, data.default_return_value, data, - false + false, + [], + "", + data.status, ).then((response) => { if (response !== undefined) { alert.success("Successfully imported " + data.name); @@ -2212,7 +2259,7 @@ const Workflows = (props) => { } { {showMoreClicked ? - + + { + if (event.target.value.toLowerCase() === "test") { + setStatus("test") + } else if (event.target.value.toLowerCase() === "production") { + setStatus("production") + } + }} + InputProps={{ + style: { + color: "white", + }, + }} + color="primary" + defaultValue={status} + placeholder="The status of the workflow. Can be test or production." + rows="1" + margin="dense" + fullWidth + /> + setBlogpost(event.target.value)} + InputProps={{ + style: { + color: "white", + }, + }} + color="primary" + defaultValue={blogpost} + placeholder="A blogpost or other reference for how this work workflow was built, and what it's for." + rows="1" + margin="dense" + fullWidth + /> setDefaultReturnValue(event.target.value)} InputProps={{ @@ -2596,6 +2677,8 @@ const Workflows = (props) => { editingWorkflow, false, selectedUsecases, + blogpost, + status, ); setNewWorkflowName(""); @@ -2612,6 +2695,8 @@ const Workflows = (props) => { {}, true, selectedUsecases, + blogpost, + status, ); } @@ -2999,7 +3084,6 @@ const Workflows = (props) => { backgroundColor: filters.includes(usecase.name.toLowerCase()) ? usecase.color : theme.palette.surfaceColor, borderRadius: theme.palette.borderRadius, marginRight: index === usecases.length-1 ? 0 : 10, - height: 60, cursor: "pointer", border: `2px solid ${usecase.color}`, overflow: "hidden",