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}
+
);
};
@@ -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) => {