From 3ed4d86a3ba51936e8a7789cffe9f4cbdb7519d0 Mon Sep 17 00:00:00 2001 From: frikky Date: Mon, 27 Feb 2023 02:31:30 +0100 Subject: [PATCH 1/4] enabled user input subflows and fixed bugs --- backend/go-app/go.mod | 2 +- backend/go-app/main.go | 158 +-------- backend/go-app/walkoff.go | 2 +- backend/tests/files.sh | 4 +- frontend/src/components/AppFramework.jsx | 13 +- frontend/src/components/ConfigureWorkflow.jsx | 5 +- frontend/src/components/ParsedAction.jsx | 231 +++++++------ frontend/src/components/ShuffleCodeEditor.jsx | 39 ++- frontend/src/views/Admin.jsx | 68 ++-- frontend/src/views/AngularWorkflow.jsx | 305 +++++++++++++----- frontend/src/views/AppCreator.jsx | 29 +- frontend/src/views/Dashboard.jsx | 43 ++- 12 files changed, 482 insertions(+), 417 deletions(-) diff --git a/backend/go-app/go.mod b/backend/go-app/go.mod index ff5c31d3..6e2c2bc3 100644 --- a/backend/go-app/go.mod +++ b/backend/go-app/go.mod @@ -2,7 +2,7 @@ module main go 1.19 -//replace github.com/shuffle/shuffle-shared => ../../../shuffle-shared +replace github.com/shuffle/shuffle-shared => ../../../shuffle-shared require ( cloud.google.com/go/datastore v1.10.0 diff --git a/backend/go-app/main.go b/backend/go-app/main.go index 53c9697e..e4bff643 100644 --- a/backend/go-app/main.go +++ b/backend/go-app/main.go @@ -1340,156 +1340,6 @@ func checkAdminLogin(resp http.ResponseWriter, request *http.Request) { resp.Write([]byte(fmt.Sprintf(`{"success": true, "reason": "redirect", "sso_url": "%s"}`, baseSSOUrl))) } -func handleLogin(resp http.ResponseWriter, request *http.Request) { - cors := shuffle.HandleCors(resp, request) - if cors { - return - } - - // Gets a struct of Username, password - data, err := shuffle.ParseLoginParameters(resp, request) - if err != nil { - resp.WriteHeader(401) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) - return - } - - log.Printf("[INFO] Handling login of %s", data.Username) - - err = checkUsername(data.Username) - if err != nil { - resp.WriteHeader(401) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) - return - } - - ctx := context.Background() - log.Printf("[INFO] Login Username: %s", data.Username) - users, err := shuffle.FindUser(ctx, strings.ToLower(strings.TrimSpace(data.Username))) - if err != nil && len(users) == 0 { - log.Printf("[WARNING] Failed getting user %s: %s", data.Username, err) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false, "reason": "Username and/or password is incorrect"}`)) - return - } - - if len(users) != 1 { - log.Printf(`Found multiple or no users with the same username: %s: %d`, data.Username, len(users)) - resp.WriteHeader(401) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Error: %d users with username %s"}`, len(users), data.Username))) - return - } - - Userdata := users[0] - - err = bcrypt.CompareHashAndPassword([]byte(Userdata.Password), []byte(data.Password)) - if err != nil { - log.Printf("Password for %s is incorrect: %s", data.Username, err) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false, "reason": "Username and/or password is incorrect"}`)) - return - } - - if !Userdata.Active { - log.Printf("%s is not active, but tried to login. Error: %v", data.Username, err) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false, "reason": "This user is deactivated"}`)) - return - } - - tutorialsFinished := []shuffle.Tutorial{} - for _, tutorial := range Userdata.PersonalInfo.Tutorials { - tutorialsFinished = append(tutorialsFinished, shuffle.Tutorial{ - Name: tutorial, - }) - } - returnValue := shuffle.HandleInfo{ - Success: true, - Tutorials: tutorialsFinished, - } - - loginData := `{"success": true}` - newData, err := json.Marshal(returnValue) - if err == nil { - loginData = string(newData) - } - - if len(Userdata.Session) != 0 { - log.Println("[INFO] User session already exists - resetting it") - expiration := time.Now().Add(3600 * time.Second) - - http.SetCookie(resp, &http.Cookie{ - Name: "session_token", - Value: Userdata.Session, - Expires: expiration, - }) - - returnValue.Cookies = append(returnValue.Cookies, shuffle.SessionCookie{ - Key: "session_token", - Value: Userdata.Session, - Expiration: expiration.Unix(), - }) - - loginData = fmt.Sprintf(`{"success": true, "cookies": [{"key": "session_token", "value": "%s", "expiration": %d}]}`, Userdata.Session, expiration.Unix()) - newData, err := json.Marshal(returnValue) - if err == nil { - loginData = string(newData) - } - //log.Printf("SESSION LENGTH MORE THAN 0 IN LOGIN: %s", Userdata.Session) - - err = shuffle.SetSession(ctx, Userdata, Userdata.Session) - if err != nil { - log.Printf("Error adding session to database: %s", err) - } - - resp.WriteHeader(200) - resp.Write([]byte(loginData)) - return - } else { - log.Printf("[INFO] User session is empty - create one!") - - sessionToken := uuid.NewV4().String() - expiration := time.Now().Add(3600 * time.Second) - http.SetCookie(resp, &http.Cookie{ - Name: "session_token", - Value: sessionToken, - Expires: expiration, - }) - - // ADD TO DATABASE - err = shuffle.SetSession(ctx, Userdata, sessionToken) - if err != nil { - log.Printf("Error adding session to database: %s", err) - } - - Userdata.Session = sessionToken - err = shuffle.SetUser(ctx, &Userdata, true) - if err != nil { - log.Printf("Failed updating user when setting session: %s", err) - resp.WriteHeader(500) - resp.Write([]byte(`{"success": false}`)) - return - } - - returnValue.Cookies = append(returnValue.Cookies, shuffle.SessionCookie{ - Key: "session_token", - Value: sessionToken, - Expiration: expiration.Unix(), - }) - - loginData = fmt.Sprintf(`{"success": true, "cookies": [{"key": "session_token", "value": "%s", "expiration": %d}]}`, sessionToken, expiration.Unix()) - newData, err := json.Marshal(returnValue) - if err == nil { - loginData = string(newData) - } - } - - log.Printf("[INFO] %s SUCCESSFULLY LOGGED IN with session %s", data.Username, Userdata.Session) - - resp.WriteHeader(200) - resp.Write([]byte(loginData)) -} - func fixOrgUser(ctx context.Context, org *shuffle.Org) *shuffle.Org { //found := false //for _, id := range user.Orgs { @@ -4323,9 +4173,9 @@ func runInitEs(ctx context.Context) { url := os.Getenv("SHUFFLE_APP_DOWNLOAD_LOCATION") if len(url) == 0 { - log.Printf("Skipping download since no URL is set") - //url = "https://github.com/frikky/shuffle-apps" - return + log.Printf("[INFO] Skipping download of apps since no URL is set. Default would be https://github.com/frikky/shuffle-apps") + //url = "" + //return } username := os.Getenv("SHUFFLE_DOWNLOAD_AUTH_USERNAME") @@ -6042,7 +5892,7 @@ func initHandlers() { // General - duplicates and old. r.HandleFunc("/api/v1/getusers", shuffle.HandleGetUsers).Methods("GET", "OPTIONS") - r.HandleFunc("/api/v1/login", handleLogin).Methods("POST", "OPTIONS") + r.HandleFunc("/api/v1/login", shuffle.HandleLogin).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/logout", shuffle.HandleLogout).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/register", handleRegister).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/checkusers", checkAdminLogin).Methods("GET", "OPTIONS") diff --git a/backend/go-app/walkoff.go b/backend/go-app/walkoff.go index cacb0f01..a16997ff 100644 --- a/backend/go-app/walkoff.go +++ b/backend/go-app/walkoff.go @@ -2737,7 +2737,7 @@ func IterateAppGithubFolders(ctx context.Context, fs billy.Filesystem, dir []os. fullPath = fmt.Sprintf("%s%s", extra, "api.yml") fileReader, err = fs.Open(fullPath) if err != nil { - log.Printf("Failed finding api.yaml/yml: %s", err) + log.Printf("[INFO] Failed finding api.yaml/yml for file %s: %s", filename, err) continue } } diff --git a/backend/tests/files.sh b/backend/tests/files.sh index c92a0ebc..5a699822 100755 --- a/backend/tests/files.sh +++ b/backend/tests/files.sh @@ -16,7 +16,7 @@ #r.HandleFunc("/api/v1/files/{fileId}", handleDeleteFile).Methods("DELETE", "OPTIONS") -#curl http://localhost:5001/api/v1/files/create -H "Authorization: Bearer 09627dcb-7e2a-4843-819b-417d268ff840" -d '{"filename": "rule2.yar", "org_id": "11f67b76-6051-4425-b0d6-be23daac6d12", "workflow_id": "global", "namespace": "yara"}' -curl http://localhost:5002/api/v1/files/file_366ee8d2-1af6-4270-8639-213af30b4a29/upload -H "Authorization: Bearer 09627dcb-7e2a-4843-819b-417d268ff840" -F 'shuffle_file=@upload.sh' +curl http://localhost:5001/api/v1/files/create -H "Authorization: Bearer 317f5066-395c-414d-aa3d-479cf27f47dd" -d '{"filename": "rule2.yar", "org_id": "292c7e25-40ad-4f05-904f-77d3c7b735e6", "workflow_id": "global", "namespace": "yara"}' +curl http://localhost:5001/api/v1/files/file_eb89e315-eb66-4d76-9df7-530fb003fc84/upload -H "Authorization: Bearer 317f5066-395c-414d-aa3d-479cf27f47dd" -F 'shuffle_file=@upload.sh' #curl http://localhost:5001/api/v1/files/namespaces/yara -H "Authorization: Bearer c5b4c827-65ec-47f4-9e8a-234cdba38959" --output rules.zip diff --git a/frontend/src/components/AppFramework.jsx b/frontend/src/components/AppFramework.jsx index aaabc2de..b59eed56 100644 --- a/frontend/src/components/AppFramework.jsx +++ b/frontend/src/components/AppFramework.jsx @@ -910,7 +910,9 @@ const AppFramework = (props) => { } useEffect(() => { - handleLoadNextSuggestion(frameworkData) + if (!window.location.pathname.includes("usecases")) { + handleLoadNextSuggestion(frameworkData) + } }, []) useEffect(() => { @@ -985,9 +987,8 @@ const AppFramework = (props) => { }, [newSelectedApp]) - const isCloud = - window.location.host === "localhost:3002" || - window.location.host === "shuffler.io"; + const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io"; + const imgSize = 50; var parsedFrameworkData = frameworkData === undefined ? {} : frameworkData @@ -1906,7 +1907,7 @@ const AppFramework = (props) => { /> - {injectedApps.map((apps) => { + {injectedApps.map((apps, appindex) => { var categoryTop = 100 var categoryLeft = 100 @@ -1934,7 +1935,7 @@ const AppFramework = (props) => { } return ( -
+
{apps.map((app, appIndex) => { return ( { "required": true, }) } else { - if ( - action.authentication_id === "" && - app.authentication.required === true - ) { + if (action.authentication_id === "" && app.authentication.required === true && action.parameters !== undefined && action.parameters !== null) { // Check if configuration is filled or not var filled = true; for (let [key,keyval] in Object.entries(action.parameters)) { diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index 971c224d..75e21cbf 100644 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -701,10 +701,7 @@ const ParsedAction = (props) => { } // bad detection mechanism probably - if ( - event.target.value[event.target.value.length - 1] === "." && - actionlist.length > 0 - ) { + if (event.target.value[event.target.value.length - 1] === "." && actionlist.length > 0) { console.log("GET THE LAST ARGUMENT FOR NODE!"); // THIS IS AN EXAMPLE OF SHOWING IT /* @@ -2098,6 +2095,8 @@ const ParsedAction = (props) => { toComplete += values[key].autocomplete; } + + // Handles the fields under OpenAPI body to be parsed. if (data.name.startsWith("${") && data.name.endsWith("}")) { console.log("INSIDE VALUE REPLACE: ", data.name, toComplete); @@ -2131,10 +2130,8 @@ const ParsedAction = (props) => { } } - selectedActionParameters[count]["value_replace"] = - paramcheck; - selectedAction.parameters[count]["value_replace"] = - paramcheck; + selectedActionParameters[count]["value_replace"] = paramcheck; + selectedAction.parameters[count]["value_replace"] = paramcheck; setSelectedAction(selectedAction); setUpdate(Math.random()); @@ -2144,13 +2141,15 @@ const ParsedAction = (props) => { } } - selectedActionParameters[count].value += toComplete; - selectedAction.parameters[count].value = - selectedActionParameters[count].value; - setSelectedAction(selectedAction); - setUpdate(Math.random()); - - setShowDropdown(false); + console.log("In nestedclick!!") + var newValue = selectedActionParameters[count].value + toComplete + changeActionParameter({target: {value: newValue}}, count, data) + //selectedActionParameters[count].value += toComplete; + //selectedAction.parameters[count].value = selectedActionParameters[count].value; + //setSelectedAction(selectedAction); + //setUpdate(Math.random()); + + setShowDropdown(false); setMenuPosition(null); }; @@ -2604,7 +2603,9 @@ const ParsedAction = (props) => { setUpdate(Math.random()); }} - onClick={() => setShowAutocomplete(true)} + onClick={() => { + setShowAutocomplete(true) + }} fullWidth open={showAutocomplete} style={{ @@ -2614,22 +2615,12 @@ const ParsedAction = (props) => { borderRadius: theme.palette.borderRadius, }} onChange={(e) => { - if ( - selectedActionParameters[count].value[ - selectedActionParameters[count].value.length - 1 - ] === "." - ) { - e.target.value.autocomplete = - e.target.value.autocomplete.slice( - 1, - e.target.value.autocomplete.length - ); + if (selectedActionParameters[count].value[selectedActionParameters[count].value.length - 1] === ".") { + e.target.value.autocomplete = e.target.value.autocomplete.slice(1,e.target.value.autocomplete.length); } - selectedActionParameters[count].value += - e.target.value.autocomplete; - selectedAction.parameters[count].value = - selectedActionParameters[count].value; + selectedActionParameters[count].value += e.target.value.autocomplete; + selectedAction.parameters[count].value = selectedActionParameters[count].value; setSelectedAction(selectedAction); setUpdate(Math.random()); @@ -2973,108 +2964,110 @@ const ParsedAction = (props) => { // Should make it a function lol if (workflow.branches !== undefined && workflow.branches !== null) { for (let [key,keyval] in Object.entries(workflow.branches)) { - for (let [subkey,subkeyval] in Object.entries(workflow.branches[key].conditions)) { - const condition = workflow.branches[key].conditions[subkey] - const sourceparam = condition.source - const destinationparam = condition.destination + if (workflow.branches[key].conditions !== undefined && workflow.branches[key].conditions !== null) { + for (let [subkey,subkeyval] in Object.entries(workflow.branches[key].conditions)) { + const condition = workflow.branches[key].conditions[subkey] + const sourceparam = condition.source + const destinationparam = condition.destination - // Should have a smarter way of discovering node names - // Finding index(es) and replacing at the location - if (sourceparam.value.includes("$")) { - try { - var cnt = -1 - var previous = 0 - while (true) { - cnt += 1 - // Need to make sure e.g. changing the first here doesn't change the 2nd - // $change_me - // $change_me_2 - - const foundindex = sourceparam.value.toLowerCase().indexOf(parsedBaseLabel, previous) - if (foundindex === previous && foundindex !== 0) { - break - } - - if (foundindex >= 0) { - previous = foundindex+newname.length - // Need to add diff of length to word - - // Check location: - // If it's a-zA-Z_ then don't replace - if (sourceparam.value.length > foundindex+parsedBaseLabel.length) { - const regex = /[a-zA-Z0-9_]/g; - const match = sourceparam.value[foundindex+parsedBaseLabel.length].match(regex); - if (match !== null) { - continue - } - } + // Should have a smarter way of discovering node names + // Finding index(es) and replacing at the location + if (sourceparam.value.includes("$")) { + try { + var cnt = -1 + var previous = 0 + while (true) { + cnt += 1 + // Need to make sure e.g. changing the first here doesn't change the 2nd + // $change_me + // $change_me_2 - console.log("Old found: ", workflow.branches[key].conditions[subkey].source.value) - const extralength = newname.length-parsedBaseLabel.length - sourceparam.value = sourceparam.value.substring(0, foundindex) + newname + sourceparam.value.substring(foundindex-extralength+newname.length, sourceparam.value.length) + const foundindex = sourceparam.value.toLowerCase().indexOf(parsedBaseLabel, previous) + if (foundindex === previous && foundindex !== 0) { + break + } + + if (foundindex >= 0) { + previous = foundindex+newname.length + // Need to add diff of length to word + + // Check location: + // If it's a-zA-Z_ then don't replace + if (sourceparam.value.length > foundindex+parsedBaseLabel.length) { + const regex = /[a-zA-Z0-9_]/g; + const match = sourceparam.value[foundindex+parsedBaseLabel.length].match(regex); + if (match !== null) { + continue + } + } + + console.log("Old found: ", workflow.branches[key].conditions[subkey].source.value) + const extralength = newname.length-parsedBaseLabel.length + sourceparam.value = sourceparam.value.substring(0, foundindex) + newname + sourceparam.value.substring(foundindex-extralength+newname.length, sourceparam.value.length) - console.log("New: ", workflow.branches[key].conditions[subkey].source.value) - } else { - break - } + console.log("New: ", workflow.branches[key].conditions[subkey].source.value) + } else { + break + } - // Break no matter what after 5 replaces. May need to increase - if (cnt >= 5) { - break - } + // Break no matter what after 5 replaces. May need to increase + if (cnt >= 5) { + break + } + } + } catch (e) { + console.log("Failed value replacement based on index: ", e) } - } catch (e) { - console.log("Failed value replacement based on index: ", e) } - } - if (destinationparam.value.includes("$")) { - try { - var cnt = -1 - var previous = 0 - while (true) { - cnt += 1 - // Need to make sure e.g. changing the first here doesn't change the 2nd - // $change_me - // $change_me_2 - - const foundindex = destinationparam.value.toLowerCase().indexOf(parsedBaseLabel, previous) - if (foundindex === previous && foundindex !== 0) { - break - } - - if (foundindex >= 0) { - previous = foundindex+newname.length - // Need to add diff of length to word - - // Check location: - // If it's a-zA-Z_ then don't replace - if (destinationparam.value.length > foundindex+parsedBaseLabel.length) { - const regex = /[a-zA-Z0-9_]/g; - const match = destinationparam.value[foundindex+parsedBaseLabel.length].match(regex); - if (match !== null) { - continue - } - } + if (destinationparam.value.includes("$")) { + try { + var cnt = -1 + var previous = 0 + while (true) { + cnt += 1 + // Need to make sure e.g. changing the first here doesn't change the 2nd + // $change_me + // $change_me_2 - console.log("Old found: ", workflow.branches[key].conditions[subkey].destination.value) - const extralength = newname.length-parsedBaseLabel.length - destinationparam.value = destinationparam.value.substring(0, foundindex) + newname + destinationparam.value.substring(foundindex-extralength+newname.length, destinationparam.value.length) + const foundindex = destinationparam.value.toLowerCase().indexOf(parsedBaseLabel, previous) + if (foundindex === previous && foundindex !== 0) { + break + } + + if (foundindex >= 0) { + previous = foundindex+newname.length + // Need to add diff of length to word + + // Check location: + // If it's a-zA-Z_ then don't replace + if (destinationparam.value.length > foundindex+parsedBaseLabel.length) { + const regex = /[a-zA-Z0-9_]/g; + const match = destinationparam.value[foundindex+parsedBaseLabel.length].match(regex); + if (match !== null) { + continue + } + } + + console.log("Old found: ", workflow.branches[key].conditions[subkey].destination.value) + const extralength = newname.length-parsedBaseLabel.length + destinationparam.value = destinationparam.value.substring(0, foundindex) + newname + destinationparam.value.substring(foundindex-extralength+newname.length, destinationparam.value.length) - console.log("New: ", workflow.branches[key].conditions[subkey].destination.value) - } else { - break - } + console.log("New: ", workflow.branches[key].conditions[subkey].destination.value) + } else { + break + } - // Break no matter what after 5 replaces. May need to increase - if (cnt >= 5) { - break - } + // Break no matter what after 5 replaces. May need to increase + if (cnt >= 5) { + break + } + } + } catch (e) { + console.log("Failed value replacement based on index: ", e) } - } catch (e) { - console.log("Failed value replacement based on index: ", e) } } } diff --git a/frontend/src/components/ShuffleCodeEditor.jsx b/frontend/src/components/ShuffleCodeEditor.jsx index 7bd3119a..af9d5b31 100644 --- a/frontend/src/components/ShuffleCodeEditor.jsx +++ b/frontend/src/components/ShuffleCodeEditor.jsx @@ -54,8 +54,14 @@ import { useNavigate, Link, useParams } from "react-router-dom"; const liquidFilters = [ {"name": "Size", "value": "size", "example": ""}, {"name": "Date", "value": `date: "%Y%m%d"`, "example": `{{ "now" | date: "%s" }}`}, + {"name": "Split", "value": `split: ","`, "example": `{{ "this,can,become,a,list" | split: "," }}`}, + {"name": "Join", "value": `join: ","`, "example": `{{ ["this","can","become","a","string"] | join: "," }}`}, {"name": "Escape String", "value": `{{ \"\"\"'string with weird'" quotes\"\"\" | escape_string }}`, "example": ``}, {"name": "Flatten", "value": `flatten`, "example": `{{ [1, [1, 2], [2, 3, 4]] | flatten }}`}, + {"name": "URL encode", "value": `url_encode`, "example": `{{ "https://www.google.com/search?q=hello world" | url_encode }}`}, + {"name": "URL decode ", "value": `url_decode`, "example": `{{ "https://www.google.com/search?q=hello%20world" | url_decode }}`}, + {"name": "base64_encode", "value": `base64_encode`, "example": `{{ "https://www.google.com/search?q=hello%20world" | base64_encode }}`}, + {"name": "base64_decode", "value": `base64_decode`, "example": `{{ "aGVsbG8K" | base64_encode }}`}, ] const mathFilters = [ @@ -426,10 +432,11 @@ const CodeEditor = (props) => { if(fixedVariable.slice(1,).toLowerCase() === actionlist[j].autocomplete.toLowerCase()){ valuefound = true - console.log("Valuefound: ", fixedVariable, actionlist[j].example) - try { - if (actionlist[j].example.trim().startsWith("{") || actionlist[j].example.trim().startsWith("[")) { + if (typeof actionlist[j].example === "object") { + input = input.replace(fixedVariable, JSON.stringify(actionlist[j].example)); + + } else if (actionlist[j].example.trim().startsWith("{") || actionlist[j].example.trim().startsWith("[")) { input = input.replace(fixedVariable, JSON.stringify(actionlist[j].example)); } else { input = input.replace(fixedVariable, actionlist[j].example) @@ -438,6 +445,7 @@ const CodeEditor = (props) => { input = input.replace(fixedVariable, actionlist[j].example) } } else { + // Couldn't find the correct example value } } @@ -565,12 +573,14 @@ const CodeEditor = (props) => { inputdata = JSON.stringify(inputdata) } + // Shuffle Tools 1.2.0 (in most cases?) const appid = "3e2bdf9d5069fe3f4746c29d68785a6a" const actiondata = {"description":"Repeats the call parameter","id":"","name":"repeat_back_to_me","label":"","node_type":"","environment":"","sharing":false,"private_id":"","public_id":"","app_id":"3e2bdf9d5069fe3f4746c29d68785a6a","tags":null,"authentication":[],"tested":false,"parameters":[{"description":"The message to repeat","id":"","name":"call","example":"REPEATING: Hello world","value":inputdata,"multiline":true,"options":null,"action_field":"","variant":"STATIC_VALUE","required":true,"configuration":false,"tags":null,"schema":{"type":"string"},"skip_multicheck":false,"value_replace":null,"unique_toggled":false,"autocompleted":false}],"execution_variable":{"description":"","id":"","name":"","value":""},"returns":{"description":"","example":"","id":"","schema":{"type":"string"}},"authentication_id":"","example":"","auth_not_required":false,"source_workflow":"","run_magic_output":false,"run_magic_input":false,"execution_delay":0,"app_name":"Shuffle Tools","app_version":"1.2.0","selectedAuthentication":{}} setExecutionResult({ "valid": false, "result": baseResult, + "errors": [], }) setExecuting(true) @@ -593,21 +603,27 @@ const CodeEditor = (props) => { }) .then((responseJson) => { //console.log("RESPONSE: ", responseJson) + var newResult = {} if (responseJson.success === true && responseJson.result !== null && responseJson.result !== undefined && responseJson.result.length > 0) { const result = responseJson.result.slice(0, 50)+"..." //alert.info("SUCCESS: "+result) const validate = validateJson(responseJson.result) - setExecutionResult(validate) + newResult = validate } else if (responseJson.success === false && responseJson.reason !== undefined && responseJson.reason !== null) { alert.error(responseJson.reason) - setExecutionResult({"valid": false, "result": responseJson.reason}) + newResult = {"valid": false, "result": responseJson.reason} } else if (responseJson.success === true) { - setExecutionResult({"valid": false, "result": "Couldn't finish execution. Please fill all the required fields, and retry the execution."}) + newResult = {"valid": false, "result": "Couldn't finish execution. Please fill all the required fields, and retry the execution."} } else { - setExecutionResult({"valid": false, "result": "Couldn't finish execution (2). Please fill all the required fields, and validate the execution."}) + newResult = {"valid": false, "result": "Couldn't finish execution (2). Please fill all the required fields, and validate the execution."} } - + + if (responseJson.errors !== undefined && responseJson.errors !== null && responseJson.errors.length > 0) { + newResult.errors = responseJson.errors + } + + setExecutionResult(newResult) setExecuting(false) }) .catch(error => { @@ -1262,7 +1278,7 @@ const CodeEditor = (props) => { { executeSingleAction(expOutput) }}> - + {executing ? : } @@ -1340,6 +1356,11 @@ const CodeEditor = (props) => { Test output: {executionResult.result} : null} + {executionResult.errors !== undefined && executionResult.errors !== null && executionResult.errors.length > 0 ? + + Errors ({executionResult.errors.length}): {executionResult.errors.join("\n")} + + : null} }
diff --git a/frontend/src/views/Admin.jsx b/frontend/src/views/Admin.jsx index bee637b8..fd9af9ba 100644 --- a/frontend/src/views/Admin.jsx +++ b/frontend/src/views/Admin.jsx @@ -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) => { { 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 ( { minWidth: 225, overflow: "hidden", }} - primary={new Date(file.created_at * 1000).toISOString()} + primary={new Date(file.updated_at * 1000).toISOString()} /> { { setOpenEditor(true) @@ -4114,11 +4145,7 @@ const Admin = (props) => { }} > @@ -4888,9 +4915,7 @@ const Admin = (props) => { >
diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index 7b80d767..3f1f3c93 100644 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -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 = ``; - 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 = ``; + 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 > - { - setTriggerOptionsWrapper("subflow"); - }} - color="primary" - value="subflow" - disabled - /> - } - label={
Subflow
} - /> { } label={
SMS
} /> + { + setTriggerOptionsWrapper("subflow"); + }} + color="primary" + value="subflow" + /> + } + label={
Subflow
} + /> {workflow.triggers[selectedTriggerIndex].parameters[2] !== undefined && - workflow.triggers[ - selectedTriggerIndex - ].parameters[2].value.includes("email") ? ( + workflow.triggers[selectedTriggerIndex].parameters[2].value.includes("email") ? ( { }} /> ) : 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") ? ( { 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) => {

{workflow.name}

+
+ {parentWorkflows.slice(0,5).map((wf, index) => { + return ( + + + {wf.image !== undefined && wf.image !== null && wf.image.length > 0 ? + {wf.name} + : null} + + Parent workflow: '{wf.name}' + + + + } placement="bottom"> + { + console.log("Click: ", wf) + }}> + + + + + ) + })} +
); }; @@ -14146,6 +14280,7 @@ const AngularWorkflow = (defaultprops) => { width: imgsize, height: imgsize, border: `2px solid ${statusColor}`, + filter: curapp === undefined ? "grayscale(100%)" : null, }} /> )} diff --git a/frontend/src/views/AppCreator.jsx b/frontend/src/views/AppCreator.jsx index 5719b39f..4a7b7da4 100644 --- a/frontend/src/views/AppCreator.jsx +++ b/frontend/src/views/AppCreator.jsx @@ -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) => {