diff --git a/backend/go-app/main.go b/backend/go-app/main.go index 1b6be031..737e8b0e 100644 --- a/backend/go-app/main.go +++ b/backend/go-app/main.go @@ -2447,7 +2447,7 @@ func handleLogin(resp http.ResponseWriter, request *http.Request) { resp.Write([]byte(loginData)) return } else { - log.Printf("User session is empty - create one!") + log.Printf("[INFO] User session is empty - create one!") sessionToken := uuid.NewV4().String() expiration := time.Now().Add(3600 * time.Second) @@ -5797,7 +5797,7 @@ func handleCloudExecutionOnprem(workflowId, startNode, executionSource, executio var execution ExecutionRequest err = json.Unmarshal([]byte(parsedArgument), &execution) if err == nil { - log.Printf("FOUND EXEC %#v", execution) + //log.Printf("[INFO] FOUND EXEC %#v", execution) if len(execution.ExecutionArgument) > 0 { parsedArgument := strings.Replace(string(execution.ExecutionArgument), "\\\"", "\"", -1) log.Printf("New exec argument: %s", execution.ExecutionArgument) @@ -5822,7 +5822,7 @@ func handleCloudExecutionOnprem(workflowId, startNode, executionSource, executio return err } - log.Println(string(b)) + //log.Println(string(b)) newRequest := &http.Request{ URL: &url.URL{}, Method: "POST", @@ -5864,8 +5864,8 @@ func handleCloudJob(job CloudSyncJob) error { } emails, err := getOutlookEmail(outlookClient, maildata) - log.Printf("EMAILS: %d", len(emails)) - log.Printf("INSIDE GET OUTLOOK EMAIL!: %#v, %s", emails, err) + //log.Printf("EMAILS: %d", len(emails)) + //log.Printf("INSIDE GET OUTLOOK EMAIL!: %#v, %s", emails, err) //type FullEmail struct { email := FullEmail{} @@ -5879,7 +5879,7 @@ func handleCloudJob(job CloudSyncJob) error { return err } - log.Printf("Should handle webhook for workflow %s with start node %s and data %s", job.PrimaryItemId, job.SecondaryItem, job.ThirdItem) + log.Printf("[INFO] Should handle outlook webhook for workflow %s with start node %s and data of length %d", job.PrimaryItemId, job.SecondaryItem, len(job.ThirdItem)) err = handleCloudExecutionOnprem(job.PrimaryItemId, job.SecondaryItem, "outlook", string(emailBytes)) if err != nil { log.Printf("Failed executing workflow from cloud outlook hook: %s", err) @@ -5889,7 +5889,7 @@ func handleCloudJob(job CloudSyncJob) error { } } else if job.Type == "webhook" { if job.Action == "execute" { - log.Printf("Should handle webhook for workflow %s with start node %s and data %s", job.PrimaryItemId, job.SecondaryItem, job.ThirdItem) + log.Printf("Should handle normal webhook for workflow %s with start node %s and data %s", job.PrimaryItemId, job.SecondaryItem, job.ThirdItem) err := handleCloudExecutionOnprem(job.PrimaryItemId, job.SecondaryItem, "webhook", job.ThirdItem) if err != nil { log.Printf("Failed executing workflow from cloud hook: %s", err) @@ -6053,7 +6053,7 @@ func remoteOrgJobController(org Org, body []byte) error { } if len(responseData.Jobs) > 0 { - log.Printf("[INFO] Remote JOB ret: %s", string(body)) + //log.Printf("[INFO] Remote JOB ret: %s", string(body)) log.Printf("Got job with reason %s and %d job(s)", responseData.Reason, len(responseData.Jobs)) } diff --git a/backend/go-app/oauth2.go b/backend/go-app/oauth2.go index 04bb845c..8c4a355f 100644 --- a/backend/go-app/oauth2.go +++ b/backend/go-app/oauth2.go @@ -126,7 +126,7 @@ func getOutlookEmail(client *http.Client, maildata MailData) ([]FullEmail, error //messageId := email.Resourcedata.ID //requestUrl := fmt.Sprintf("https://graph.microsoft.com/v1.0/me/%s", messageId) requestUrl := fmt.Sprintf("https://graph.microsoft.com/v1.0/%s", email.Resource) - log.Printf("URL: %#v", requestUrl) + //log.Printf("Outlook email URL: %#v", requestUrl) ret, err := client.Get(requestUrl) if err != nil { @@ -141,8 +141,8 @@ func getOutlookEmail(client *http.Client, maildata MailData) ([]FullEmail, error } //type FullEmail struct { - log.Printf("[INFO] EMAIL Body: %s", string(body)) - log.Printf("[INFO] Status email: %d", ret.StatusCode) + //log.Printf("[INFO] EMAIL Body: %s", string(body)) + //log.Printf("[INFO] Status email: %d", ret.StatusCode) if ret.StatusCode != 200 { return []FullEmail{}, err } @@ -724,7 +724,7 @@ func createOutlookSub(resp http.ResponseWriter, request *http.Request) { log.Printf("Failed finding org %s: %s", org.Id, err) return } - log.Printf("[INFO] Starting cloud configuration TO STOP trigger %s in org %s", trigger.Id, org.Id) + log.Printf("[INFO] Starting cloud configuration TO START trigger %s in org %s for workflow %s", trigger.Id, org.Id, trigger.WorkflowId) action := CloudSyncJob{ Type: "outlook", @@ -732,7 +732,7 @@ func createOutlookSub(resp http.ResponseWriter, request *http.Request) { OrgId: org.Id, PrimaryItemId: trigger.Id, SecondaryItem: trigger.Start, - ThirdItem: trigger.WorkflowId, + ThirdItem: workflowId, } err = executeCloudAction(action, org.SyncConfig.Apikey) @@ -982,8 +982,8 @@ func handleOutlookCallback(resp http.ResponseWriter, request *http.Request) { } emails, err := getOutlookEmail(outlookClient, maildata) - log.Printf("EMAILS: %d", len(emails)) - log.Printf("INSIDE GET OUTLOOK EMAIL!: %#v, %s", emails, err) + log.Printf("[INFO] EMAILS: %d. If this is more than 1, please contact frikky@shuffler.io", len(emails)) + //log.Printf("INSIDE GET OUTLOOK EMAIL!: %#v, %s", emails, err) //type FullEmail struct { email := FullEmail{} @@ -1087,14 +1087,14 @@ func handleOutlookSubRemoval(ctx context.Context, user User, workflowId, trigger } if runningEnvironment != "cloud" { - log.Printf("[INFO] SHOULD STOP OUTLOOK SUB ONPREM SYNC WITH CLOUD") + log.Printf("[INFO] SHOULD STOP OUTLOOK SUB ONPREM SYNC WITH CLOUD for workflow ID %s", workflowId) org, err := getOrg(ctx, user.ActiveOrg.Id) if err != nil { log.Printf("[INFO] Failed finding org %s during outlook removal: %s", org.Id, err) return err } - log.Printf("[INFO] Stopping cloud configuration for trigger %s in org %s", trigger.Id, org.Id) + log.Printf("[INFO] Stopping cloud configuration for trigger %s in org %s for workflow %s", trigger.Id, org.Id, trigger.WorkflowId) action := CloudSyncJob{ Type: "outlook", Action: "stop", @@ -1128,7 +1128,7 @@ func handleOutlookSubRemoval(ctx context.Context, user User, workflowId, trigger if err == nil { for _, sub := range curSubscriptions.Value { if sub.NotificationURL == notificationURL { - log.Printf("[INFO] Removing subscription %s from o365", sub.Id) + log.Printf("[INFO] Removing subscription %s from o365 for workflow %s", sub.Id, workflowId) removeOutlookSubscription(outlookClient, sub.Id) } } diff --git a/backend/go-app/walkoff.go b/backend/go-app/walkoff.go index b89d079d..16e1a653 100644 --- a/backend/go-app/walkoff.go +++ b/backend/go-app/walkoff.go @@ -424,7 +424,7 @@ type Workflow struct { Value string `json:"value" datastore:"value,noindex"` } `json:"execution_variables,omitempty" datastore:"execution_variables"` ExecutionEnvironment string `json:"execution_environment" datastore:"execution_environment"` - PreviouslySaved bool `json:"first_save" datastore:"first_save"` + PreviouslySaved bool `json:"previously_saved" datastore:"first_save"` Categories Categories `json:"categories" datastore:"categories"` ExampleArgument string `json:"example_argument" datastore:"example_argument,noindex"` } @@ -1810,7 +1810,7 @@ func setNewWorkflow(resp http.ResponseWriter, request *http.Request) { action.IsValid = true } - action.LargeImage = "" + //action.LargeImage = "" newActions = append(newActions, action) } @@ -1962,7 +1962,7 @@ func setNewWorkflow(resp http.ResponseWriter, request *http.Request) { return } - log.Printf("Saved new workflow %s with name %s", workflow.ID, workflow.Name) + log.Printf("[INFO] Saved new workflow %s with name %s", workflow.ID, workflow.Name) //memcacheName := fmt.Sprintf("%s_workflows", user.Username) //memcache.Delete(ctx, memcacheName) @@ -2047,7 +2047,7 @@ func deleteWorkflow(resp http.ResponseWriter, request *http.Request) { } // FIXME - maybe delete workflow executions - log.Printf("Should delete workflow %s", fileId) + log.Printf("[INFO] Should have deleted workflow %s", fileId) err = DeleteKey(ctx, "workflow", fileId) if err != nil { log.Printf("Failed deleting key %s", fileId) @@ -2244,6 +2244,8 @@ func saveWorkflow(resp http.ResponseWriter, request *http.Request) { return } + //log.Printf("SAVED: %#v", workflow.PreviouslySaved) + // FIXME - auth and check if they should have access if fileId != workflow.ID { log.Printf("Path and request ID are not matching: %s:%s.", fileId, workflow.ID) @@ -2258,7 +2260,7 @@ func saveWorkflow(resp http.ResponseWriter, request *http.Request) { } if len(workflow.ExecutingOrg.Id) == 0 { - log.Printf("Setting executing org for workflow") + log.Printf("[INFO] Setting executing org for workflow") user.ActiveOrg.Users = []User{} workflow.ExecutingOrg = user.ActiveOrg } @@ -2281,18 +2283,23 @@ func saveWorkflow(resp http.ResponseWriter, request *http.Request) { } if action.Environment == "" { - resp.WriteHeader(401) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "An environment for %s is required"}`, action.Label))) - return + if workflow.PreviouslySaved { + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "An environment for %s is required"}`, action.Label))) + return + } action.IsValid = true } // FIXME: Have a good way of tracking errors. ID's or similar. if !action.IsValid && len(action.Errors) > 0 { log.Printf("Node %s is invalid and needs to be remade. Errors: %s", action.Label, strings.Join(action.Errors, "\n")) - resp.WriteHeader(401) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Node %s is invalid and needs to be remade."}`, action.Label))) - return + + if workflow.PreviouslySaved { + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Node %s is invalid and needs to be remade."}`, action.Label))) + return + } action.IsValid = true action.Errors = []string{} } @@ -2301,7 +2308,418 @@ func saveWorkflow(resp http.ResponseWriter, request *http.Request) { newActions = append(newActions, action) } - log.Printf("PRE SAVECHECK") + newTriggers := []Trigger{} + for _, trigger := range workflow.Triggers { + log.Printf("[INFO] Trigger %s: %s", trigger.TriggerType, trigger.Status) + + // Check if it's actually running + // FIXME: Do this for other triggers too + if trigger.TriggerType == "SCHEDULE" && trigger.Status != "uninitialized" { + schedule, err := getSchedule(ctx, trigger.ID) + if err != nil { + trigger.Status = "stopped" + } else if schedule.Id == "" { + trigger.Status = "stopped" + } + } else if trigger.TriggerType == "SUBFLOW" { + for index, param := range trigger.Parameters { + if len(param.Value) == 0 && param.Name != "argument" { + //log.Printf("Param: %#v", param) + if param.Name == "user_apikey" { + apikey := "" + if len(user.ApiKey) > 0 { + apikey = user.ApiKey + } else { + user, err = generateApikey(ctx, user) + if err != nil { + workflow.IsValid = false + workflow.Errors = []string{"Trigger is missing a parameter: %s", param.Name} + + log.Printf("No type specified for user input node") + + if workflow.PreviouslySaved { + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Trigger %s is missing the parameter %s"}`, trigger.Label, param.Name))) + return + } + } + + apikey = user.ApiKey + } + + log.Printf("[INFO] Set apikey in subflow trigger for user during save") + trigger.Parameters[index].Value = apikey + } else { + + workflow.IsValid = false + workflow.Errors = []string{"Trigger is missing a parameter: %s", param.Name} + + log.Printf("No type specified for user input node") + if workflow.PreviouslySaved { + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Trigger %s is missing the parameter %s"}`, trigger.Label, param.Name))) + return + } + } + } + } + } else if trigger.TriggerType == "WEBHOOK" && trigger.Status != "uninitialized" { + hook, err := getHook(ctx, trigger.ID) + if err != nil { + log.Printf("Failed getting webhook") + trigger.Status = "stopped" + } else if hook.Id == "" { + trigger.Status = "stopped" + } + } else if trigger.TriggerType == "USERINPUT" { + // E.g. check email + sms := "" + email := "" + triggerType := "" + triggerInformation := "" + for _, item := range trigger.Parameters { + if item.Name == "alertinfo" { + triggerInformation = item.Value + } else if item.Name == "type" { + triggerType = item.Value + } else if item.Name == "email" { + email = item.Value + } else if item.Name == "sms" { + sms = item.Value + } + } + + if len(triggerType) == 0 { + log.Printf("No type specified for user input node") + if workflow.PreviouslySaved { + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "No contact option specified in user input"}`))) + return + } + } + + // FIXME: This is not the right time to send them, BUT it's well served for testing. Save -> send email / sms + _ = triggerInformation + if strings.Contains(triggerType, "email") { + if email == "test@test.com" { + log.Printf("Email isn't specified during save.") + if workflow.PreviouslySaved { + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Email field in user input can't be empty"}`))) + return + } + } + + log.Printf("Should send email to %s during execution.", email) + } + if strings.Contains(triggerType, "sms") { + if sms == "0000000" { + log.Printf("Email isn't specified during save.") + if workflow.PreviouslySaved { + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "SMS field in user input can't be empty"}`))) + return + } + } + + log.Printf("Should send SMS to %s during execution.", sms) + } + } + + //log.Println("TRIGGERS") + allNodes = append(allNodes, trigger.ID) + newTriggers = append(newTriggers, trigger) + } + + workflow.Triggers = newTriggers + + if len(workflow.Actions) == 0 { + workflow.Actions = []Action{} + } + if len(workflow.Branches) == 0 { + workflow.Branches = []Branch{} + } + if len(workflow.Triggers) == 0 { + workflow.Triggers = []Trigger{} + } + if len(workflow.Errors) == 0 { + workflow.Errors = []string{} + } + + //log.Printf("PRE VARIABLES") + for _, variable := range workflow.WorkflowVariables { + if len(variable.Value) == 0 { + log.Printf("[WARNING] Variable %s is empty!", variable.Name) + workflow.Errors = append(workflow.Errors, fmt.Sprintf("Variable %s is empty!", variable.Name)) + //resp.WriteHeader(401) + //resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Variable %s can't be empty"}`, variable.Name))) + //return + } + } + + if len(workflow.ExecutionVariables) > 0 { + log.Printf("[INFO] Found %d execution variable(s)", len(workflow.ExecutionVariables)) + } + + if len(workflow.WorkflowVariables) > 0 { + log.Printf("[INFO] Found %d workflow variable(s)", len(workflow.WorkflowVariables)) + } + + // FIXME - do actual checks ROFL + // FIXME - minor issues with e.g. hello world and self.console_logger + // Nodechecks + foundNodes := []string{} + for _, node := range allNodes { + for _, branch := range workflow.Branches { + //log.Println("branch") + //log.Println(node) + //log.Println(branch.DestinationID) + if node == branch.DestinationID || node == branch.SourceID { + foundNodes = append(foundNodes, node) + break + } + } + } + + // FIXME - append all nodes (actions, triggers etc) to one single array here + //log.Printf("PRE VARIABLES") + if len(foundNodes) != len(allNodes) || len(workflow.Actions) <= 0 { + // This shit takes a few seconds lol + if !workflow.IsValid { + oldworkflow, err := getWorkflow(ctx, fileId) + if err != nil { + log.Printf("Workflow %s doesn't exist - oldworkflow.", fileId) + if workflow.PreviouslySaved { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false, "reason": "Item already exists."}`)) + return + } + } + + oldworkflow.IsValid = false + err = setWorkflow(ctx, *oldworkflow, fileId) + if err != nil { + log.Printf("Failed saving workflow to database: %s", err) + if workflow.PreviouslySaved { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + } + } + + // FIXME - more checks here - force reload of data or something + //if len(allNodes) == 0 { + // resp.WriteHeader(401) + // resp.Write([]byte(`{"success": false, "reason": "Please insert a node"}`)) + // return + //} + + // Allowed with only a start node + //if len(allNodes) != 1 { + // resp.WriteHeader(401) + // resp.Write([]byte(`{"success": false, "reason": "There are nodes with no branches"}`)) + // return + //} + } + + // FIXME - might be a sploit to run someone elses app if getAllWorkflowApps + // doesn't check sharing=true + // Have to do it like this to add the user's apps + //log.Println("Apps set starting") + //log.Printf("EXIT ON ERROR: %#v", workflow.Configuration.ExitOnError) + //workflowapps, apperr := getAllWorkflowApps(ctx, 500) + + // Started getting the single apps, but if it's weird, this is faster + // 1. Check workflow.Start + // 2. Check if any node has "isStartnode" + if len(workflow.Actions) > 0 { + index := -1 + for indexFound, action := range workflow.Actions { + //log.Println("Apps set done") + if workflow.Start == action.ID { + index = indexFound + } + } + + if index >= 0 { + workflow.Actions[0].IsStartNode = true + } else { + if workflow.PreviouslySaved { + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "You need to set a startnode."}`))) + return + } + } + } + + allAuths, err := getAllWorkflowAppAuth(ctx, user.ActiveOrg.Id) + if userErr != nil { + log.Printf("Api authentication failed in get all apps: %s", userErr) + if workflow.PreviouslySaved { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + } + + // Check every app action and param to see whether they exist + //log.Printf("PRE ACTIONS 2") + newActions = []Action{} + for _, action := range workflow.Actions { + reservedApps := []string{ + "0ca8887e-b4af-4e3e-887c-87e9d3bc3d3e", + } + + //log.Printf("%s Action execution var: %s", action.Label, action.ExecutionVariable.Name) + + builtin := false + for _, id := range reservedApps { + if id == action.AppID { + builtin = true + break + } + } + + // Check auth + // 1. Find the auth in question + // 2. Update the node and workflow info in the auth + // 3. Get the values in the auth and add them to the action values + if len(action.AuthenticationId) > 0 { + authFound := false + for _, auth := range allAuths { + if auth.Id == action.AuthenticationId { + authFound = true + + // Updates the auth item itself IF necessary + go updateAppAuth(auth, workflow.ID, action.ID, true) + break + } + } + + if !authFound { + log.Printf("App auth %s doesn't exist. Setting error", action.AuthenticationId) + workflow.Errors = append(workflow.Errors, fmt.Sprintf("App authentication for %s doesn't exist!", action.AppName)) + workflow.IsValid = false + + action.Errors = append(action.Errors, "App authentication doesn't exist") + action.IsValid = false + action.AuthenticationId = "" + //resp.WriteHeader(401) + //resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "App auth %s doesn't exist"}`, action.AuthenticationId))) + //return + } + } + + if builtin { + newActions = append(newActions, action) + } else { + curapp := WorkflowApp{} + // FIXME - can this work with ONLY AppID? + for _, app := range workflowapps { + if app.ID == action.AppID { + curapp = app + break + } + + // Has to NOT be generated + if app.Name == action.AppName && app.AppVersion == action.AppVersion { + curapp = app + break + } + } + + // Check to see if the whole app is valid + if curapp.Name != action.AppName { + workflow.Errors = append(workflow.Errors, fmt.Sprintf("App %s doesn't exist", action.AppName)) + action.Errors = append(action.Errors, "This app doesn't exist.") + action.IsValid = false + workflow.IsValid = false + + // Append with errors + newActions = append(newActions, action) + log.Printf("App %s doesn't exist. Adding as error.", action.AppName) + //resp.WriteHeader(401) + //resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "App %s doesn't exist"}`, action.AppName))) + //return + } else { + // Check tosee if the appaction is valid + curappaction := WorkflowAppAction{} + for _, curAction := range curapp.Actions { + if action.Name == curAction.Name { + curappaction = curAction + break + } + } + + // Check to see if the action is valid + if curappaction.Name != action.Name { + log.Printf("[ERROR] Action %s in app %s doesn't exist.", action.Name, curapp.Name) + if workflow.PreviouslySaved { + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Action %s in app %s doesn't exist"}`, action.Name, curapp.Name))) + return + } + } + + // FIXME - check all parameters to see if they're valid + // Includes checking required fields + + newParams := []WorkflowAppActionParameter{} + for _, param := range curappaction.Parameters { + found := false + + // Handles check for parameter exists + value not empty in used fields + for _, actionParam := range action.Parameters { + if actionParam.Name == param.Name { + found = true + + if actionParam.Value == "" && actionParam.Variant == "STATIC_VALUE" && actionParam.Required == true { + log.Printf("[WARNING] Appaction %s with required param '%s' is empty. Can't save.", action.Name, param.Name) + //if workflow.PreviouslySaved { + // resp.WriteHeader(401) + // resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Appaction %s in app %s with required param '%s' is empty.", "node_id": "%s"}`, action.Name, action.AppName, param.Name, action.ID))) + // return + //} else { + + thisError := fmt.Sprintf("Missing parameter %s", param.Name) + action.Errors = append(action.Errors, thisError) + workflow.Errors = append(workflow.Errors, thisError) + action.IsValid = false + } + + if actionParam.Variant == "" { + actionParam.Variant = "STATIC_VALUE" + } + + newParams = append(newParams, actionParam) + break + } + } + + // Handles check for required params + if !found && param.Required { + log.Printf("Appaction %s with required param %s doesn't exist.", action.Name, param.Name) + thisError := fmt.Sprintf("Parameter %s is required", param.Name) + action.Errors = append(action.Errors, thisError) + + workflow.Errors = append(workflow.Errors, thisError) + action.IsValid = false + //newActions = append(newActions, action) + //resp.WriteHeader(401) + //resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Appaction %s with required param '%s' is empty."}`, action.Name, param.Name))) + //return + } + + } + + action.Parameters = newParams + newActions = append(newActions, action) + } + } + } + + //log.Printf("PRE SAVECHECK") if !workflow.PreviouslySaved { log.Printf("[WORKFLOW INIT] NOT PREVIOUSLY SAVED - SET ACTION AUTH!") //AuthenticationId string `json:"authentication_id,omitempty" datastore:"authentication_id"` @@ -2429,7 +2847,7 @@ func saveWorkflow(resp http.ResponseWriter, request *http.Request) { newActions = actionFixing } else { - log.Printf("Err: %s - %s", err, apperr) + log.Printf("FirstSave error: %s - %s", err, apperr) //workflowapps, apperr := getAllWorkflowApps(ctx, 100) //allAuths, err := getAllWorkflowAppAuth(ctx, user.ActiveOrg.Id) } @@ -2437,386 +2855,8 @@ func saveWorkflow(resp http.ResponseWriter, request *http.Request) { workflow.PreviouslySaved = true } - log.Printf("PRE TRIGGERS") - workflow.Actions = newActions - newTriggers := []Trigger{} - for _, trigger := range workflow.Triggers { - log.Printf("[INFO] Trigger %s: %s", trigger.TriggerType, trigger.Status) - - // Check if it's actually running - // FIXME: Do this for other triggers too - if trigger.TriggerType == "SCHEDULE" && trigger.Status != "uninitialized" { - schedule, err := getSchedule(ctx, trigger.ID) - if err != nil { - trigger.Status = "stopped" - } else if schedule.Id == "" { - trigger.Status = "stopped" - } - } else if trigger.TriggerType == "SUBFLOW" { - for index, param := range trigger.Parameters { - if len(param.Value) == 0 && param.Name != "argument" { - log.Printf("Param: %#v", param) - if param.Name == "user_apikey" { - apikey := "" - if len(user.ApiKey) > 0 { - apikey = user.ApiKey - } else { - user, err = generateApikey(ctx, user) - if err != nil { - workflow.IsValid = false - workflow.Errors = []string{"Trigger is missing a parameter: %s", param.Name} - - log.Printf("No type specified for user input node") - resp.WriteHeader(401) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Trigger %s is missing the parameter %s"}`, trigger.Label, param.Name))) - return - } - - apikey = user.ApiKey - } - - log.Printf("[INFO] Set apikey in subflow trigger for user during save") - trigger.Parameters[index].Value = apikey - } else { - - workflow.IsValid = false - workflow.Errors = []string{"Trigger is missing a parameter: %s", param.Name} - - log.Printf("No type specified for user input node") - resp.WriteHeader(401) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Trigger %s is missing the parameter %s"}`, trigger.Label, param.Name))) - return - } - } - } - } else if trigger.TriggerType == "WEBHOOK" && trigger.Status != "uninitialized" { - hook, err := getHook(ctx, trigger.ID) - if err != nil { - log.Printf("Failed getting webhook") - trigger.Status = "stopped" - } else if hook.Id == "" { - trigger.Status = "stopped" - } - } else if trigger.TriggerType == "USERINPUT" { - // E.g. check email - sms := "" - email := "" - triggerType := "" - triggerInformation := "" - for _, item := range trigger.Parameters { - if item.Name == "alertinfo" { - triggerInformation = item.Value - } else if item.Name == "type" { - triggerType = item.Value - } else if item.Name == "email" { - email = item.Value - } else if item.Name == "sms" { - sms = item.Value - } - } - - if len(triggerType) == 0 { - log.Printf("No type specified for user input node") - resp.WriteHeader(401) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "No contact option specified in user input"}`))) - return - } - - // FIXME: This is not the right time to send them, BUT it's well served for testing. Save -> send email / sms - _ = triggerInformation - if strings.Contains(triggerType, "email") { - if email == "test@test.com" { - log.Printf("Email isn't specified during save.") - resp.WriteHeader(401) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Email field in user input can't be empty"}`))) - return - } - - log.Printf("Should send email to %s during execution.", email) - } - if strings.Contains(triggerType, "sms") { - if sms == "0000000" { - log.Printf("Email isn't specified during save.") - resp.WriteHeader(401) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "SMS field in user input can't be empty"}`))) - return - } - - log.Printf("Should send SMS to %s during execution.", sms) - } - } - - //log.Println("TRIGGERS") - allNodes = append(allNodes, trigger.ID) - newTriggers = append(newTriggers, trigger) - } - - workflow.Triggers = newTriggers - - //log.Printf("PRE VARIABLES") - for _, variable := range workflow.WorkflowVariables { - if len(variable.Value) == 0 { - log.Printf("Can't have an empty variable: %s", variable.Name) - resp.WriteHeader(401) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Variable %s can't be empty"}`, variable.Name))) - return - } - } - - if len(workflow.Actions) == 0 { - workflow.Actions = []Action{} - } - if len(workflow.Branches) == 0 { - workflow.Branches = []Branch{} - } - if len(workflow.Triggers) == 0 { - workflow.Triggers = []Trigger{} - } - if len(workflow.Errors) == 0 { - workflow.Errors = []string{} - } - - if len(workflow.ExecutionVariables) > 0 { - log.Printf("[INFO] Found %d execution variable(s)", len(workflow.ExecutionVariables)) - } - - if len(workflow.WorkflowVariables) > 0 { - log.Printf("[INFO] Found %d workflow variable(s)", len(workflow.WorkflowVariables)) - } - - // FIXME - do actual checks ROFL - // FIXME - minor issues with e.g. hello world and self.console_logger - // Nodechecks - foundNodes := []string{} - for _, node := range allNodes { - for _, branch := range workflow.Branches { - //log.Println("branch") - //log.Println(node) - //log.Println(branch.DestinationID) - if node == branch.DestinationID || node == branch.SourceID { - foundNodes = append(foundNodes, node) - break - } - } - } - - // FIXME - append all nodes (actions, triggers etc) to one single array here - //log.Printf("PRE VARIABLES") - if len(foundNodes) != len(allNodes) || len(workflow.Actions) <= 0 { - // This shit takes a few seconds lol - if !workflow.IsValid { - oldworkflow, err := getWorkflow(ctx, fileId) - if err != nil { - log.Printf("Workflow %s doesn't exist - oldworkflow.", fileId) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false, "reason": "Item already exists."}`)) - return - } - - oldworkflow.IsValid = false - err = setWorkflow(ctx, *oldworkflow, fileId) - if err != nil { - log.Printf("Failed saving workflow to database: %s", err) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false}`)) - return - } - } - - // FIXME - more checks here - force reload of data or something - //if len(allNodes) == 0 { - // resp.WriteHeader(401) - // resp.Write([]byte(`{"success": false, "reason": "Please insert a node"}`)) - // return - //} - - // Allowed with only a start node - //if len(allNodes) != 1 { - // resp.WriteHeader(401) - // resp.Write([]byte(`{"success": false, "reason": "There are nodes with no branches"}`)) - // return - //} - } - - // FIXME - might be a sploit to run someone elses app if getAllWorkflowApps - // doesn't check sharing=true - // Have to do it like this to add the user's apps - //log.Println("Apps set starting") - //log.Printf("EXIT ON ERROR: %#v", workflow.Configuration.ExitOnError) - //workflowapps, apperr := getAllWorkflowApps(ctx, 500) - - // Started getting the single apps, but if it's weird, this is faster - // 1. Check workflow.Start - // 2. Check if any node has "isStartnode" - if len(workflow.Actions) > 0 { - index := -1 - for indexFound, action := range workflow.Actions { - //log.Println("Apps set done") - if workflow.Start == action.ID { - index = indexFound - } - } - - if index >= 0 { - workflow.Actions[0].IsStartNode = true - } else { - resp.WriteHeader(401) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "You need to set a startnode."}`))) - return - } - } - - allAuths, err := getAllWorkflowAppAuth(ctx, user.ActiveOrg.Id) - if userErr != nil { - log.Printf("Api authentication failed in get all apps: %s", userErr) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false}`)) - return - } - - // Check every app action and param to see whether they exist - //log.Printf("PRE ACTIONS 2") - newActions = []Action{} - for _, action := range workflow.Actions { - reservedApps := []string{ - "0ca8887e-b4af-4e3e-887c-87e9d3bc3d3e", - } - - //log.Printf("%s Action execution var: %s", action.Label, action.ExecutionVariable.Name) - - builtin := false - for _, id := range reservedApps { - if id == action.AppID { - builtin = true - break - } - } - - // Check auth - // 1. Find the auth in question - // 2. Update the node and workflow info in the auth - // 3. Get the values in the auth and add them to the action values - if len(action.AuthenticationId) > 0 { - authFound := false - for _, auth := range allAuths { - if auth.Id == action.AuthenticationId { - authFound = true - - // Updates the auth item itself IF necessary - go updateAppAuth(auth, workflow.ID, action.ID, true) - break - } - } - - if !authFound { - log.Printf("App auth %s doesn't exist. Setting error", action.AuthenticationId) - workflow.Errors = append(workflow.Errors, fmt.Sprintf("App authentication for %s doesn't exist!", action.AppName)) - workflow.IsValid = false - - action.Errors = append(action.Errors, "App authentication doesn't exist") - action.IsValid = false - action.AuthenticationId = "" - //resp.WriteHeader(401) - //resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "App auth %s doesn't exist"}`, action.AuthenticationId))) - //return - } - } - - if builtin { - newActions = append(newActions, action) - } else { - curapp := WorkflowApp{} - // FIXME - can this work with ONLY AppID? - for _, app := range workflowapps { - if app.ID == action.AppID { - curapp = app - break - } - - // Has to NOT be generated - if app.Name == action.AppName && app.AppVersion == action.AppVersion { - curapp = app - break - } - } - - // Check to see if the whole app is valid - if curapp.Name != action.AppName { - workflow.Errors = append(workflow.Errors, fmt.Sprintf("App %s doesn't exist", action.AppName)) - action.Errors = append(action.Errors, "This app doesn't exist.") - action.IsValid = false - workflow.IsValid = false - - // Append with errors - newActions = append(newActions, action) - log.Printf("App %s doesn't exist. Adding as error.", action.AppName) - //resp.WriteHeader(401) - //resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "App %s doesn't exist"}`, action.AppName))) - //return - } else { - // Check tosee if the appaction is valid - curappaction := WorkflowAppAction{} - for _, curAction := range curapp.Actions { - if action.Name == curAction.Name { - curappaction = curAction - break - } - } - - // Check to see if the action is valid - if curappaction.Name != action.Name { - log.Printf("[ERROR] Action %s in app %s doesn't exist.", action.Name, curapp.Name) - resp.WriteHeader(401) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Action %s in app %s doesn't exist"}`, action.Name, curapp.Name))) - return - } - - // FIXME - check all parameters to see if they're valid - // Includes checking required fields - - newParams := []WorkflowAppActionParameter{} - for _, param := range curappaction.Parameters { - found := false - - // Handles check for parameter exists + value not empty in used fields - for _, actionParam := range action.Parameters { - if actionParam.Name == param.Name { - found = true - - if actionParam.Value == "" && actionParam.Variant == "STATIC_VALUE" && actionParam.Required == true { - log.Printf("Appaction %s with required param '%s' is empty.", action.Name, param.Name) - resp.WriteHeader(401) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Appaction %s with required param '%s' is empty."}`, action.Name, param.Name))) - return - - } - - if actionParam.Variant == "" { - actionParam.Variant = "STATIC_VALUE" - } - - newParams = append(newParams, actionParam) - break - } - } - - // Handles check for required params - if !found && param.Required { - log.Printf("Appaction %s with required param %s doesn't exist.", action.Name, param.Name) - action.Errors = append(action.Errors, "Parameter %s is required", param.Name) - //newActions = append(newActions, action) - //resp.WriteHeader(401) - //resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Appaction %s with required param '%s' is empty."}`, action.Name, param.Name))) - //return - } - - } - - action.Parameters = newParams - newActions = append(newActions, action) - } - } - } + //log.Printf("PRE TRIGGERS") + //workflow.Actions = newActions workflow.Actions = newActions workflow.IsValid = true @@ -2834,9 +2874,11 @@ func saveWorkflow(resp http.ResponseWriter, request *http.Request) { err = setWorkflow(ctx, workflow, fileId) if err != nil { log.Printf("Failed saving workflow to database: %s", err) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false}`)) - return + if workflow.PreviouslySaved { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } } totalOldActions := len(tmpworkflow.Actions) @@ -3235,7 +3277,7 @@ func handleExecution(id string, workflow Workflow, request *http.Request) (Workf } // This one doesn't really matter. - log.Printf("[INFO] Running POST execution with body of length %d", len(string(body))) + log.Printf("[INFO] Running POST execution with body of length %d for workflow %s", len(string(body)), workflowExecution.Workflow.ID) if len(body) >= 4 { if body[0] == 34 && body[len(body)-1] == 34 { @@ -3446,7 +3488,7 @@ func handleExecution(id string, workflow Workflow, request *http.Request) (Workf log.Printf("[INFO] No execution source (trigger) specified. Setting to default") workflowExecution.ExecutionSource = "default" } else { - log.Printf("[INFO] Execution source is %s for execution ID %s", workflowExecution.ExecutionSource, workflowExecution.ExecutionId) + log.Printf("[INFO] Execution source is %s for execution ID %s in workflow %s", workflowExecution.ExecutionSource, workflowExecution.ExecutionId, workflowExecution.Workflow.ID) } workflowExecution.ExecutionVariables = workflow.ExecutionVariables @@ -3648,7 +3690,7 @@ func handleExecution(id string, workflow Workflow, request *http.Request) (Workf var allEnvs []Environment if len(workflowExecution.ExecutionOrg) > 0 { - log.Printf("[INFO] Executing ORG: %s", workflowExecution.ExecutionOrg) + //log.Printf("[INFO] Executing ORG: %s", workflowExecution.ExecutionOrg) allEnvironments, err := getEnvironments(ctx, workflowExecution.ExecutionOrg) if err != nil { @@ -3751,7 +3793,7 @@ func handleExecution(id string, workflow Workflow, request *http.Request) (Workf // FIXME - tmp name based on future companyname-companyId // This leads to issues with overlaps. Should set limits and such instead for _, environment := range environments { - log.Printf("[INFO] Execution: %s should execute onprem with execution environment \"%s\"", workflowExecution.ExecutionId, environment) + log.Printf("[INFO] Execution: %s should execute onprem with execution environment \"%s\". Workflow: %s", workflowExecution.ExecutionId, environment, workflowExecution.Workflow.ID) executionRequest := ExecutionRequest{ ExecutionId: workflowExecution.ExecutionId, diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index 321d664f..9bb3ae21 100644 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -780,6 +780,11 @@ const AngularWorkflow = (props) => { workflow.isValid = true } + for (var key in workflow.errors) { + //console.log("Error: ", workflow.errors[key]) + alert.info(workflow.errors[key]) + } + setWorkflow(workflow) } //alert.success("Successfully saved workflow") @@ -5131,7 +5136,7 @@ const AngularWorkflow = (props) => {
- Login to either: + Login to either
{outlookButton} diff --git a/frontend/src/views/Workflows.jsx b/frontend/src/views/Workflows.jsx index 0229f694..00bffbe7 100644 --- a/frontend/src/views/Workflows.jsx +++ b/frontend/src/views/Workflows.jsx @@ -12,6 +12,7 @@ import Dropzone from '../components/Dropzone'; import {Link} from 'react-router-dom'; import { useAlert } from "react-alert"; import ChipInput from 'material-ui-chip-input' +import uuid from "uuid" const inputColor = "#383B40" const surfaceColor = "#27292D" @@ -92,31 +93,6 @@ const Workflows = (props) => { } }) - // DEBUG HERE - const handleClickLogout = () => { - //console.log("Cookies: ", cookies) - //console.log("SHOULD LOG OUT") - //console.log(isLoggedIn) - - // Don't really care about the logout - //fetch(globalUrl+"/api/v1/logout", { - // credentials: "include", - // method: 'POST', - // headers: { - // 'Content-Type': 'application/json', - // }, - //}) - //.then(() => { - // // Log out anyway - // removeCookie("session_token", {path: "/"}) - // //window.location = "/login" - //}) - //.catch(error => { - // console.log(error) - // removeCookie("session_token", {path: "/"}) - //}); - } - const deleteModal = deleteModalOpen ? { setSelectedExecution({}) setWorkflowExecutions([]) + console.log(responseJson) + if (responseJson !== undefined) { setWorkflows(responseJson) setWorkflowDone(true) } else { if (isLoggedIn) { alert.error("An error occurred while loading workflows") - } else { - handleClickLogout() } return } if (responseJson.length > 0){ - setSelectedWorkflow(responseJson[0]) + //setSelectedWorkflow(responseJson[0]) //getWorkflowExecution(responseJson[0].id) } }) @@ -417,12 +393,10 @@ const Workflows = (props) => { const exportWorkflow = (data) => { console.log("export") - let dataStr = JSON.stringify(data) - - let dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr); let exportFileDefaultName = data.name+'.json'; data["owner"] = "" + console.log(data) if (data.triggers !== null && data.triggers !== undefined) { for (var key in data.triggers) { const trigger = data.triggers[key] @@ -435,6 +409,22 @@ const Workflows = (props) => { if (trigger.status == "running") { trigger.status = "stopped" } + + const newId = uuid.v4() + for (var branchkey in data.branches) { + const branch = data.branches[branchkey] + if (branch.source_id === trigger.id) { + console.log("CHANGING SOURCE ID") + branch.source_id = newId + } + + if (branch.destination_id === trigger.id) { + console.log("CHANGING DESTINATION ID") + branch.destination_id = newId + } + } + + trigger.id = newId } } @@ -445,9 +435,31 @@ const Workflows = (props) => { for (var subkey in data.actions[key].parameters) { const param = data.actions[key].parameters[subkey] if (param.name.includes("key") || param.name.includes("user") || param.name.includes("pass") || param.name.includes("api") || param.name.includes("auth") || param.name.includes("secret")) { - param.value = "" + // FIXME: This may be a vuln if api-keys are generated that start with $ + if (param.value.startsWith("$")) { + console.log("Skipping field, as it's referencing a variable") + } else { + param.value = "" + param.is_valid = false + } } } + + const newId = uuid.v4() + for (var branchkey in data.branches) { + const branch = data.branches[branchkey] + if (branch.source_id === data.actions[key].id) { + console.log("CHANGING SOURCE ID IN ACTION") + branch.source_id = newId + } + + if (branch.destination_id === data.actions[key].id) { + console.log("CHANGING DESTINATION ID IN ACTION") + branch.destination_id = newId + } + } + + data.actions[key].id = newId } } @@ -456,6 +468,7 @@ const Workflows = (props) => { const param = data.workflow_variables[key] if (param.name.includes("key") || param.name.includes("user") || param.name.includes("pass") || param.name.includes("api") || param.name.includes("auth") || param.name.includes("secret")) { param.value = "" + param.is_valid = false } } } @@ -465,9 +478,16 @@ const Workflows = (props) => { data["org"] = [] data["org_id"] = "" + data["execution_org"] = {} + + // These are backwards.. True = saved before. Very confuse. + data["previously_saved"] = false + data["first_save"] = false data.execution_org = {"id": ""} console.log(data) + let dataStr = JSON.stringify(data) + let dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr); let linkElement = document.createElement('a'); linkElement.setAttribute('href', dataUri); linkElement.setAttribute('download', exportFileDefaultName); @@ -477,9 +497,9 @@ const Workflows = (props) => { const copyWorkflow = (data) => { data = JSON.parse(JSON.stringify(data)) alert.success("Copying workflow "+data.name) - console.log("data: ", data) data.id = "" data.name = data.name+"_copy" + console.log("COPIED DATA: ", data) //return fetch(globalUrl+"/api/v1/workflows", { @@ -503,7 +523,7 @@ const Workflows = (props) => { }) .catch(error => { alert.error(error.toString()) - }); + }) } @@ -571,6 +591,7 @@ const Workflows = (props) => { } const imgSize = 25 + //console.log("TOP INFO: ", data) return ( { }}> @@ -580,7 +601,7 @@ const Workflows = (props) => {
{ if (selectedWorkflow.id !== data.id) { - setSelectedWorkflow(data) + //setSelectedWorkflow(data) //getWorkflowExecution(data.id) } }}> @@ -637,7 +658,7 @@ const Workflows = (props) => {
{ if (selectedWorkflow.id !== data.id) { - setSelectedWorkflow(data) + //setSelectedWorkflow(data) //getWorkflowExecution(data.id) } }}> @@ -1064,6 +1085,9 @@ const Workflows = (props) => { if (response !== undefined) { // SET THE FULL THING data.id = response.id + data.first_save = false + data.previously_saved = false + data.is_valid = false // Actually create it const ret = setNewWorkflow(data.name, data.description, data.tags, data, false)