Merge branch '1.2.0' of https://github.com/dhaval055/shuffle into 1.2.0

This commit is contained in:
dhaval055
2023-03-14 15:28:37 +05:30
13 changed files with 522 additions and 454 deletions
+1 -1
View File
@@ -19,7 +19,7 @@ require (
github.com/gorilla/mux v1.8.0
github.com/h2non/filetype v1.1.3
github.com/satori/go.uuid v1.2.0
github.com/shuffle/shuffle-shared v0.3.59
github.com/shuffle/shuffle-shared v0.3.62
golang.org/x/crypto v0.3.0
google.golang.org/api v0.103.0
google.golang.org/appengine v1.6.7
+18 -165
View File
@@ -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 {
@@ -3829,26 +3679,32 @@ func remoteOrgJobController(org shuffle.Org, body []byte) error {
if !responseData.Success {
log.Printf("[WARNING] Should stop org job controller because no success?")
if strings.Contains(responseData.Reason, "Bad apikey") || strings.Contains(responseData.Reason, "Error getting the organization") || strings.Contains(responseData.Reason, "Organization isn't syncing") {
if strings.Contains(strings.ToLower(responseData.Reason), "bad apikey") || strings.Contains(responseData.Reason, "Error getting the organization") || strings.Contains(responseData.Reason, "Organization isn't syncing") {
log.Printf("[WARNING] Remote error; Bad apikey or org error. Stopping sync for org: %s", responseData.Reason)
if value, exists := scheduledOrgs[org.Id]; exists {
// Looks like this does the trick? Hurr
log.Printf("[WARNING] STOPPING ORG SCHEDULE for: %s", org.Id)
log.Printf("[INFO] STOPPING ORG SCHEDULE for: %s", org.Id)
value.Lock()
} else {
log.Printf("[INFO] Failed finding the schedule for org %s (%s)", org.Name, org.Id)
}
org, err := shuffle.GetOrg(ctx, org.Id)
if err != nil {
log.Printf("[WARNING] Failed finding org %s: %s", org.Id, err)
return err
}
org.SyncConfig.Interval = 0
org.SyncConfig.Apikey = ""
org.CloudSync = false
// Just in case
org, err = handleStopCloudSync(syncUrl, *org)
if err != nil {
log.Printf("[ERROR] Failed stopping cloud sync remotely: %s", err)
}
org.SyncConfig.Interval = 0
org.CloudSync = false
org.SyncConfig.Apikey = ""
startDate := time.Now().Unix()
org.SyncFeatures.Webhook = shuffle.SyncData{Active: false, Type: "trigger", Name: "Webhook", StartDate: startDate}
@@ -3866,10 +3722,7 @@ func remoteOrgJobController(org shuffle.Org, body []byte) error {
log.Printf("[INFO] Successfully STOPPED org cloud sync for %s (%s)", org.Name, org.Id)
}
return errors.New("Stopped schedule for org locally because of bad apikey.")
} else {
return errors.New(fmt.Sprintf("Failed finding the schedule for org %s (%s)", org.Name, org.Id))
}
return nil
}
return errors.New("[ERROR] Remote job handler issues.")
@@ -4323,9 +4176,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 +5895,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")
+3 -3
View File
@@ -462,7 +462,8 @@ func handleWorkflowQueue(resp http.ResponseWriter, request *http.Request) {
//log.Printf("Actionresult unmarshal: %s", string(body))
log.Printf("[DEBUG] Got workflow result from %s of length %d.", request.RemoteAddr, len(body))
err = shuffle.ValidateNewWorkerExecution(body)
ctx := context.Background()
err = shuffle.ValidateNewWorkerExecution(ctx, body)
if err == nil {
resp.WriteHeader(200)
resp.Write([]byte(fmt.Sprintf(`{"success": true, "reason": "success"}`)))
@@ -488,7 +489,6 @@ func handleWorkflowQueue(resp http.ResponseWriter, request *http.Request) {
// 4. Push to db
// IF FAIL: Set executionstatus: abort or cancel
ctx := context.Background()
workflowExecution, err := shuffle.GetWorkflowExecution(ctx, actionResult.ExecutionId)
if err != nil {
log.Printf("[ERROR] Failed getting execution (workflowqueue) %s: %s", actionResult.ExecutionId, err)
@@ -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
}
}
+2 -2
View File
@@ -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
+6 -5
View File
@@ -910,7 +910,9 @@ const AppFramework = (props) => {
}
useEffect(() => {
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) => {
/>
</div>
{injectedApps.map((apps) => {
{injectedApps.map((apps, appindex) => {
var categoryTop = 100
var categoryLeft = 100
@@ -1934,7 +1935,7 @@ const AppFramework = (props) => {
}
return (
<div style={{display: "flex", position: "absolute", top: categoryTop, left: categoryLeft, zIndex: 10010, }}>
<div key={appindex} style={{display: "flex", position: "absolute", top: categoryTop, left: categoryLeft, zIndex: 10010, }}>
{apps.map((app, appIndex) => {
return (
<Chip
@@ -170,10 +170,7 @@ const ConfigureWorkflow = (props) => {
"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)) {
+21 -28
View File
@@ -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,11 +2141,13 @@ const ParsedAction = (props) => {
}
}
selectedActionParameters[count].value += toComplete;
selectedAction.parameters[count].value =
selectedActionParameters[count].value;
setSelectedAction(selectedAction);
setUpdate(Math.random());
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,6 +2964,7 @@ 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)) {
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
@@ -3080,6 +3072,7 @@ const ParsedAction = (props) => {
}
}
}
}
for (let [key,keyval] in Object.entries(workflow.actions)) {
if (workflow.actions[key].id === selectedAction.id) {
+29 -8
View File
@@ -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) => {
<IconButton disabled={executing} color="primary" style={{border: `1px solid ${theme.palette.primary.main}`, marginLeft: 300, padding: 8}} variant="contained" onClick={() => {
executeSingleAction(expOutput)
}}>
<Tooltip title="Try it! This runs the Shuffle Tools 'repeat back to me' action with what you see in the expected output window." placement="top">
<Tooltip title="Try it! This runs the Shuffle Tools 'repeat back to me' action with what you see in the expected output window. Commonly used to test your Python scripts or Liquid filters, not requiring the full workflow to run again." placement="top">
{executing ? <CircularProgress style={{height: 18, width: 18, }} /> : <PlayArrowIcon style={{height: 18, width: 18, }} /> }
</Tooltip>
@@ -1340,6 +1356,11 @@ const CodeEditor = (props) => {
Test output: {executionResult.result}
</Typography>
: null}
{executionResult.errors !== undefined && executionResult.errors !== null && executionResult.errors.length > 0 ?
<Typography variant="body2" style={{maxHeight: 100, overflow: "auto", color: "#f85a3e",}}>
Errors ({executionResult.errors.length}): {executionResult.errors.join("\n")}
</Typography>
: null}
</span>
}
</div>
+47 -19
View File
@@ -209,7 +209,7 @@ const Admin = (props) => {
const [openEditor, setOpenEditor] = React.useState(false);
const [renderTextBox, setRenderTextBox] = React.useState(false);
const [openFileId, setOpenFileId] = React.useState(false);
const allowedFileTypes = ["txt", "py", "yaml","yml","json"]
const allowedFileTypes = ["txt", "py", "yaml", "yml","json", "html", "js", "csv",]
const runUpdateText = (text) =>{
fetch(`${globalUrl}/api/v1/files/${openFileId}/edit`, {
@@ -1017,6 +1017,36 @@ const Admin = (props) => {
});
};
const rerunCloudWorkflows = (environment) => {
alert.info("Starting execution reruns. This can run in the background.")
fetch(
`${globalUrl}/api/v1/environments/${environment.id}/rerun`,
{
method: "GET",
credentials: "include",
}
)
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for apps :O!");
return;
} else {
alert.error(response.reason);
//alert.info("Aborted all dangling workflows");
}
return response.json();
})
.then((responseJson) => {
console.log("Got response for execution: ", responseJson);
//console.log("RESPONSE: ", responseJson)
//setFiles(responseJson)
})
.catch((error) => {
//alert.error(error.toString())
});
};
const abortEnvironmentWorkflows = (environment) => {
//console.log("Aborting all workflows started >10 minutes ago, not finished");
@@ -3943,7 +3973,7 @@ const Admin = (props) => {
<List>
<ListItem>
<ListItemText
primary="Created"
primary="Updated"
style={{ maxWidth: 225, minWidth: 225 }}
/>
<ListItemText
@@ -3989,7 +4019,8 @@ const Admin = (props) => {
bgColor = "#1f2023";
}
const isDisabledButton = isCloud || file.filesize < 100000 && file.status === ("active") && allowedFileTypes.includes(file.filename.split(".")[1]) === true
const filenamesplit = file.filename.split(".")
const iseditable = file.filesize < 100000 && file.status === "active" && allowedFileTypes.includes(filenamesplit[filenamesplit.length-1])
return (
<ListItem
@@ -4006,7 +4037,7 @@ const Admin = (props) => {
minWidth: 225,
overflow: "hidden",
}}
primary={new Date(file.created_at * 1000).toISOString()}
primary={new Date(file.updated_at * 1000).toISOString()}
/>
<ListItemText
style={{
@@ -4099,13 +4130,13 @@ const Admin = (props) => {
<ListItemText
primary=<span style={{ display:"inline"}}>
<Tooltip
title={"Edit File"}
title={`Edit File (${allowedFileTypes.join(", ")})`}
style={{}}
aria-label={"Edit"}
>
<span>
<IconButton
disabled={isDisabledButton ? false : true}
disabled={!iseditable}
style = {{padding: "6px"}}
onClick={() => {
setOpenEditor(true)
@@ -4114,11 +4145,7 @@ const Admin = (props) => {
}}
>
<EditIcon
style={{
color: isDisabledButton === true
? "white"
: "grey",
}}
style={{color: iseditable ? "white" : "grey",}}
/>
</IconButton>
</span>
@@ -4888,9 +4915,7 @@ const Admin = (props) => {
>
<div style={{ display: "flex" }}>
<Button
variant={
environment.archived ? "contained" : "outlined"
}
variant={environment.archived ? "contained" : "outlined"}
style={{ borderRadius: "0px" }}
onClick={() => deleteEnvironment(environment)}
color="primary"
@@ -4900,16 +4925,19 @@ const Admin = (props) => {
<Button
variant={"outlined"}
style={{ borderRadius: "0px" }}
disabled={isCloud && environment.Name.toLowerCase() !== "cloud"}
onClick={() => {
console.log(
"Should clear executions for: ",
environment
);
console.log("Should clear executions for: ", environment);
if (isCloud && environment.Name.toLowerCase() === "cloud") {
rerunCloudWorkflows(environment);
} else {
abortEnvironmentWorkflows(environment);
}
}}
color="primary"
>
Clear
{isCloud && environment.Name.toLowerCase() === "cloud" ? "Rerun" : "Clear"}
</Button>
</div>
</ListItemText>
+178 -43
View File
@@ -274,6 +274,7 @@ const AngularWorkflow = (defaultprops) => {
const [triggerAuthentication, setTriggerAuthentication] = React.useState({});
const [triggerFolders, setTriggerFolders] = React.useState([]);
const [workflows, setWorkflows] = React.useState([]);
const [parentWorkflows, setParentWorkflows] = React.useState([]);
const [showEnvironment, setShowEnvironment] = React.useState(false);
const [editWorkflowDetails, setEditWorkflowDetails] = React.useState(false);
@@ -487,7 +488,6 @@ const AngularWorkflow = (defaultprops) => {
})
.then((responseJson) => {
if (responseJson !== undefined) {
setWorkflows(responseJson);
// Sets up subflow trigger with the right info
if (trigger_index > -1) {
@@ -529,6 +529,37 @@ const AngularWorkflow = (defaultprops) => {
}
}
}
if (workflows.length === 0) {
//console.log("First request. Checking for parent trigger (if this is subflow")
var parentworkflows = []
for (let workflowkey in responseJson) {
const innerworkflow = responseJson[workflowkey]
for (let triggerkey in innerworkflow.triggers) {
const trigger = innerworkflow.triggers[triggerkey]
if (trigger.trigger_type === "SUBFLOW") {
for (let paramkey in trigger.parameters) {
const param = trigger.parameters[paramkey]
if (param.name === "workflow" && param.value === props.match.params.key) {
parentworkflows.push({
id: innerworkflow.id,
name: innerworkflow.name,
image: innerworkflow.image,
})
}
}
}
}
}
if (parentworkflows.length > 0) {
setParentWorkflows(parentworkflows.filter(wf => wf.id !== props.match.params.key))
}
}
setWorkflows(responseJson);
}
})
.catch((error) => {
@@ -2324,10 +2355,7 @@ 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();
@@ -2344,6 +2372,39 @@ const AngularWorkflow = (defaultprops) => {
}
}
if (nodedata.app_name === "Webhook" || nodedata.app_name === "Schedule" || nodedata.app_name === "Gmail" || nodedata.app_name === "Office365") {
console.log("Found triggers. Add!")
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)
}
} else {
// Readding the icon after moving the node
if (!found) {
const iconInfo = GetIconInfo(nodedata);
@@ -2373,6 +2434,7 @@ const AngularWorkflow = (defaultprops) => {
console.log("Node already exists - don't add descriptor node");
}
}
}
originalLocation = {
x: 0,
@@ -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;
@@ -4381,6 +4453,51 @@ const AngularWorkflow = (defaultprops) => {
if (nodedata.app_name !== undefined && !workflow.public === true) {
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) {
const currentNode = allNodes[_key];
@@ -4413,6 +4530,9 @@ 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);
@@ -4722,6 +4842,7 @@ const AngularWorkflow = (defaultprops) => {
return decoratorNode;
});
const triggers = workflow.triggers.map((trigger) => {
const node = {};
node.position = trigger.position;
@@ -5017,14 +5138,7 @@ const AngularWorkflow = (defaultprops) => {
}
// App length necessary cus of cy initialization
if (
// First load - gets the workflow
elements.length === 0 &&
workflow.actions !== undefined &&
!graphSetup &&
Object.getOwnPropertyNames(workflow).length > 0
) {
if (elements.length === 0 && workflow.actions !== undefined && !graphSetup && Object.getOwnPropertyNames(workflow).length > 0) {
setGraphSetup(true);
setupGraph();
console.log("In graph setup")
@@ -10073,6 +10187,10 @@ const AngularWorkflow = (defaultprops) => {
name: "custom_response_body",
value: "",
};
workflow.triggers[selectedTriggerIndex].parameters[4] = {
name: "await_response",
value: "v1",
};
setWorkflow(workflow);
} else {
// Always update
@@ -11065,26 +11183,6 @@ const AngularWorkflow = (defaultprops) => {
style={{ paddingLeft: 10, backgroundColor: inputColor }}
row
>
<FormControlLabel
control={
<Checkbox
checked={
workflow.triggers[selectedTriggerIndex].parameters[2] !==
undefined &&
workflow.triggers[
selectedTriggerIndex
].parameters[2].value.includes("subflow")
}
onChange={() => {
setTriggerOptionsWrapper("subflow");
}}
color="primary"
value="subflow"
disabled
/>
}
label={<div style={{ color: "white" }}>Subflow</div>}
/>
<FormControlLabel
control={
<Checkbox
@@ -11119,12 +11217,23 @@ const AngularWorkflow = (defaultprops) => {
}
label={<div style={{ color: "white" }}>SMS</div>}
/>
<FormControlLabel
control={
<Checkbox
checked={workflow.triggers[selectedTriggerIndex].parameters[2] !== undefined && workflow.triggers[selectedTriggerIndex].parameters[2].value.includes("subflow")}
onChange={() => {
setTriggerOptionsWrapper("subflow");
}}
color="primary"
value="subflow"
/>
}
label={<div style={{ color: "white" }}>Subflow</div>}
/>
</FormGroup>
{workflow.triggers[selectedTriggerIndex].parameters[2] !==
undefined &&
workflow.triggers[
selectedTriggerIndex
].parameters[2].value.includes("email") ? (
workflow.triggers[selectedTriggerIndex].parameters[2].value.includes("email") ? (
<TextField
style={{
backgroundColor: inputColor,
@@ -11187,11 +11296,7 @@ const AngularWorkflow = (defaultprops) => {
}}
/>
) : null}
{workflow.triggers[selectedTriggerIndex].parameters[2] !==
undefined &&
workflow.triggers[
selectedTriggerIndex
].parameters[2].value.includes("subflow") ? (
{workflow.triggers[selectedTriggerIndex].parameters[2] !== undefined && workflow.triggers[selectedTriggerIndex].parameters[2].value.includes("subflow") ? (
<TextField
style={{
backgroundColor: inputColor,
@@ -11213,8 +11318,7 @@ const AngularWorkflow = (defaultprops) => {
workflow.triggers[selectedTriggerIndex].parameters[5].value
}
onBlur={(event) => {
workflow.triggers[selectedTriggerIndex].parameters[5].value =
event.target.value;
workflow.triggers[selectedTriggerIndex].parameters[5].value = event.target.value;
setWorkflow(workflow);
setUpdate(Math.random());
}}
@@ -11565,6 +11669,36 @@ const AngularWorkflow = (defaultprops) => {
<h2 style={{ margin: 0 }}>{workflow.name}</h2>
</Breadcrumbs>
</div>
<div style={{display: "flex", marginLeft: 10, }}>
{parentWorkflows.slice(0,5).map((wf, index) => {
return (
<a href={`/workflows/${wf.id}`} target="_blank" rel="noopener noreferrer" key={index}>
<Tooltip arrow placement="left" title={
<span style={{}}>
{wf.image !== undefined && wf.image !== null && wf.image.length > 0 ?
<img
src={wf.image}
alt={wf.name}
style={{ backgroundColor: theme.palette.surfaceColor, maxHeight: 200, minHeigth: 200, borderRadius: theme.palette.borderRadius, }}
/>
: null}
<Typography>
Parent workflow: '{wf.name}'
</Typography>
</span>
} placement="bottom">
<span onClick={() => {
console.log("Click: ", wf)
}}>
<img src={theme.palette.defaultImage} style={{height: 25, width: 25, cursor: "pointer", border: 15, marginRight: 5, marginTop: 5, filter: "grayscale(90%)", }} />
</span>
</Tooltip>
</a>
)
})}
</div>
</div>
);
};
@@ -14146,6 +14280,7 @@ const AngularWorkflow = (defaultprops) => {
width: imgsize,
height: imgsize,
border: `2px solid ${statusColor}`,
filter: curapp === undefined ? "grayscale(100%)" : null,
}}
/>
)}
+16 -11
View File
@@ -222,22 +222,23 @@ const parseCurl = (s) => {
return out;
};
// Basically CRUD for each category + special
export const appCategories = [
{
"name": "Communication",
"color": "#FFC107",
"icon": "communication",
"action_labels": ["List Messages", "Send Message",],
"action_labels": ["List Messages", "Send Message", "Get Message", "Search messages"],
}, {
"name": "SIEM",
"color": "#FFC107",
"icon": "siem",
"action_labels": ["Get alerts", "Search", "Create detection",],
"action_labels": ["List Alerts", "Search", "Create detection", "Add hash to lookup_list",],
}, {
"name": "Eradication",
"color": "#FFC107",
"icon": "eradication",
"action_labels": ["List tickets", "Update ticket", "Block hash", "Isolate host"],
"action_labels": ["List Alerts", "Update Alert", "Create detection", "Block hash", "Search Hosts", "Isolate host"],
}, {
"name": "Cases",
"color": "#FFC107",
@@ -247,27 +248,27 @@ export const appCategories = [
"name": "Assets",
"color": "#FFC107",
"icon": "assets",
"action_labels": [],
"action_labels": ["List Assets", "Get Asset", "Search Assets", "Search Users", "Search endpoints", "Search vulnerabilities"],
}, {
"name": "Intel",
"color": "#FFC107",
"icon": "intel",
"action_labels": [],
"action_labels": ["Get IOC", "Search IOC", "Create IOC", "Update IOC", "Delete IOC", "Add IOC",],
}, {
"name": "IAM",
"color": "#FFC107",
"icon": "iam",
"action_labels": [],
"action_labels": ["Get Identity", "Get Asset", "Search Identity", "Reset Password", "Disable user", ],
}, {
"name": "Network",
"color": "#FFC107",
"icon": "network",
"action_labels": ["Block IP",],
"action_labels": ["Get Rules", "Allow IP", "Block IP",],
}, {
"name": "Other",
"color": "#FFC107",
"icon": "other",
"action_labels": [],
"action_labels": ["Update Info", "Get Info", "Get Status", "Get Version", "Get Health", "Get Config", "Get Configs", "Get Configs by type", "Get Configs by name", "Run script"],
},
]
@@ -3152,11 +3153,15 @@ const AppCreator = (defaultprops) => {
<Select
fullWidth
onChange={(e) => {
console.log("Should change: ", e.target.value)
console.log("Should change: ", e.target.value, " Index: ", index)
actions[index].action_label = e.target.value
const foundIndex = actions.findIndex((action) => action.name === data.name)
console.log("Found index: ", foundIndex)
if (foundIndex !== undefined && foundIndex !== null && foundIndex >= 0) {
actions[foundIndex].action_label = e.target.value
setActions(actions)
setUpdate(Math.random());
setUpdate(Math.random())
}
}}
value={data.action_label}
style={{
+39 -4
View File
@@ -99,6 +99,7 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged
const [description, setDescription] = useState("");
const [video, setVideo] = useState("");
const [blogpost, setBlogpost] = useState("");
const [workflowOutline, setWorkflowOutline] = useState("");
const [selectedWorkflows, setSelectedWorkflows] = useState([])
const [firstLoad, setFirstLoad] = useState(true)
@@ -188,6 +189,11 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged
parsedUsecase.mitre = mitreTags
}
if (workflowOutline !== inputUsecase.workflow_outline) {
inputUsecase.workflow_outline = workflowOutline
parsedUsecase.workflow_outline = workflowOutline
}
fetch(globalUrl + "/api/v1/workflows/usecases", {
method: "POST",
headers: {
@@ -303,6 +309,10 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged
if (inputUsecase.extra_buttons !== undefined && inputUsecase.extra_buttons !== null) {
subcase.extra_buttons = inputUsecase.extra_buttons
}
if (inputUsecase.workflow_outline !== undefined && inputUsecase.workflow_outline !== null) {
subcase.workflow_outline = inputUsecase.workflow_outline
}
}
}
@@ -433,6 +443,12 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged
if (subcase.mitre !== undefined && subcase.mitre !== null) {
setMitreTags(subcase.mitre)
}
if (subcase.workflow_outline !== undefined && subcase.workflow_outline !== null) {
setWorkflowOutline(subcase.workflow_outline)
} else {
setWorkflowOutline("")
}
}}
>
<EditIcon style={{ color: usecase.color }} />
@@ -502,9 +518,6 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged
<div style={{marginTop: 25, display: "flex", minHeight: 400, maxHeight: 400, marginRight: 15, }}>
{editing ?
<div style={{flex: 1, marginRight: 50, }}>
<Typography variant="h6">
Editing!
</Typography>
<TextField
style={{
marginTop: 10,
@@ -553,6 +566,26 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged
}}
id="videoEditing"
/>
<TextField
style={{
marginTop: 10,
marginRight: 10,
}}
variant="outlined"
fullWidth
color="primary"
label="Workflow Outline"
placeholder={"Workflow Outline"}
value={workflowOutline}
multiline
minRows={3}
onChange={(event) => {
setWorkflowOutline(event.target.value)
}}
id="workflowOutline"
tabIndex="-1"
/>
<div
style={{
display: 'flex',
@@ -573,6 +606,7 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged
setDescription("")
setVideo("")
setBlogpost("")
setWorkflowOutline("")
setEditing(false)
}}
>
@@ -590,10 +624,11 @@ const UsecaseListComponent = ({keys, isCloud, globalUrl, frameworkData, isLogged
cursor: "pointer"
}}
onClick={(event) => {
setEditing(false)
setUsecaseItem(inputUsecase)
setEditing(false)
setDescription("")
setVideo("")
setWorkflowOutline("")
setBlogpost("")
}}
>
+1 -1
View File
@@ -11,7 +11,7 @@ require (
github.com/gorilla/mux v1.8.0
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/satori/go.uuid v1.2.0
github.com/shuffle/shuffle-shared v0.3.59
github.com/shuffle/shuffle-shared v0.3.62
)
require (