package shuffle import ( "context" "crypto/sha1" "encoding/json" "fmt" "io/ioutil" "log" "net/http" "errors" "sort" "strings" "time" uuid "github.com/satori/go.uuid" "gopkg.in/yaml.v2" ) func HandleGetDetectionRules(resp http.ResponseWriter, request *http.Request) { cors := HandleCors(resp, request) if cors { return } user, err := HandleApiAuthentication(resp, request) if err != nil { log.Printf("[WARNING] Api authentication failed in get detection rules: %s", err) resp.WriteHeader(401) resp.Write([]byte(`{"success": false}`)) return } // Extract detection_type location := strings.Split(request.URL.String(), "/") if len(location) < 5 { log.Printf("[WARNING] Path too short: %d", len(location)) resp.WriteHeader(401) resp.Write([]byte(`{"success": false}`)) return } detectionType := strings.ToLower(location[4]) log.Printf("[AUDIT] User '%s' (%s) is trying to get detections from namespace %#v", user.Username, user.Id, detectionType) ctx := GetContext(request) files, err := GetAllFiles(ctx, user.ActiveOrg.Id, detectionType) if err != nil && len(files) == 0 { log.Printf("[ERROR] Failed to get files: %s", err) resp.WriteHeader(500) resp.Write([]byte(`{"success": false, "reason": "Error getting files."}`)) return } log.Printf("[DEBUG] Loaded %d files for user %s from namespace %s", len(files), user.Username, detectionType) disabledRules, err := GetDisabledRules(ctx, user.ActiveOrg.Id) if err != nil && err.Error() != "rules doesn't exist" { log.Printf("[ERROR] Failed to get disabled rules: %s", err) //resp.WriteHeader(500) //resp.Write([]byte(`{"success": false, "reason": "Error getting disabled rules."}`)) //return } sort.Slice(files[:], func(i, j int) bool { return files[i].UpdatedAt > files[j].UpdatedAt }) var sigmaFileInfo []DetectionFileInfo // FIXME: Goroutine + Cache necessary for _, file := range files { if file.OrgId != user.ActiveOrg.Id { continue } if file.Status != "active" { continue } var fileContent []byte //if project.CacheDb { // detectionContentId := fmt.Sprintf("detectionfile-%s", file.Id) // cachedContent, err := GetCache(ctx, detectionContentId) // if err == nil { if len(fileContent) == 0 { fileContent, err = GetFileContent(ctx, &file, nil) if err != nil { log.Printf("[ERROR] Failed getting detection file content for %s (%s): %s", file.Filename, file.Id, err) } } var rule DetectionFileInfo err = yaml.Unmarshal(fileContent, &rule) if err != nil { log.Printf("[ERROR] Failed to parse YAML file %s: %s", file.Filename, err) continue } isDisabled := disabledRules.DisabledFolder found := false if isDisabled { rule.IsEnabled = false } else { for _, disabledFile := range disabledRules.Files { if disabledFile.Id == file.Id { found = true break } } if found { rule.IsEnabled = false } else { rule.IsEnabled = true } } rule.FileId = file.Id rule.Tags = file.Tags rule.FileName = strings.TrimSuffix(strings.TrimSuffix(file.Filename, ".yaml"), ".yml") sigmaFileInfo = append(sigmaFileInfo, rule) } var isTenzirAlive bool if time.Now().Unix() > disabledRules.LastActive+10 { isTenzirAlive = false } else { isTenzirAlive = true } response := DetectionResponse{ DetectionName: detectionType, Category: "", OrgId: user.ActiveOrg.Id, DetectionInfo: sigmaFileInfo, FolderDisabled: disabledRules.DisabledFolder, IsConnectorActive: isTenzirAlive, } detections := GetPublicDetections() for _, detection := range detections { if strings.ToLower(detection.DetectionName) != strings.ToLower(response.DetectionName) { continue } response.Title = detection.Title response.Category = detection.Category response.DownloadRepo = detection.DownloadRepo break } responseData, err := json.Marshal(response) if err != nil { log.Printf("[ERROR] Failed to marshal response data: %s", err) resp.WriteHeader(500) resp.Write([]byte(`{"success": false, "reason": "Error processing rules."}`)) return } resp.WriteHeader(200) resp.Write(responseData) } func HandleToggleRule(resp http.ResponseWriter, request *http.Request) { cors := HandleCors(resp, request) if cors { return } var fileId string location := strings.Split(request.URL.String(), "/") if location[1] == "api" { if len(location) <= 4 { log.Printf("Path too short: %d", len(location)) resp.WriteHeader(401) resp.Write([]byte(`{"success": false}`)) return } fileId = location[5] } ctx := GetContext(request) if len(fileId) != 36 && !strings.HasPrefix(fileId, "file_") { log.Printf("[WARNING] Bad format for fileId %s", fileId) resp.WriteHeader(401) resp.Write([]byte(`{"success": false, "reason": "Badly formatted fileId"}`)) return } user, err := HandleApiAuthentication(resp, request) if err != nil { log.Printf("[WARNING] Api authentication failed in toggle rule: %s", err) resp.WriteHeader(401) resp.Write([]byte(`{"success": false}`)) return } file, err := GetFile(ctx, fileId) if err != nil { log.Printf("[ERROR] File %s not found: %s", fileId, err) resp.WriteHeader(400) resp.Write([]byte(`{"success": false, "reason": "File not found"}`)) return } if user.Role == "org-reader" { log.Printf("[WARNING] Org-reader doesn't have access to delete files: %s (%s)", user.Username, user.Id) resp.WriteHeader(401) resp.Write([]byte(`{"success": false, "reason": "Read only user"}`)) return } var action string switch location[6] { case "disable_rule": action = "disable" case "enable_rule": action = "enable" default: log.Printf("[WARNING] path not found: %s", location[6]) resp.WriteHeader(404) resp.Write([]byte(`{"success": false, "message": "The URL doesn't exist or is not allowed."}`)) return } if action == "disable" { err := disableRule(*file) if err != nil { log.Printf("[ERROR] Failed to %s file", action) resp.WriteHeader(500) resp.Write([]byte(`{"success": false}`)) return } } else if action == "enable" { err := enableRule(*file) if err != nil { if err.Error() != "rules doesn't exist" { log.Printf("[ERROR] Failed to %s file, reason: %s", action, err) resp.WriteHeader(404) resp.Write([]byte(`{"success": false}`)) return } else { log.Printf("[ERROR] Failed to %s file, reason: %s", action, err) resp.WriteHeader(500) resp.Write([]byte(`{"success": false}`)) return } } } var execType string if action == "disable" { execType = "DISABLE_SIGMA_FILE" } else if action == "enable" { execType = "ENABLE_SIGMA_FILE" } err = SetDetectionOrborusRequest(ctx, user.ActiveOrg.Id, execType, file.Filename, "SIGMA", "SHUFFLE_DISCOVER") if err != nil { log.Printf("[ERROR] Failed setting workflow queue for env %s (6): %s", "SIGMA", err) //resp.WriteHeader(500) //resp.Write([]byte(`{"success": false}`)) //return } resp.WriteHeader(200) resp.Write([]byte((`{"success": true}`))) } func HandleFolderToggle(resp http.ResponseWriter, request *http.Request) { cors := HandleCors(resp, request) if cors { return } user, err := HandleApiAuthentication(resp, request) if err != nil { log.Printf("[WARNING] Api authentication failed in toggle folder: %s", err) resp.WriteHeader(401) resp.Write([]byte(`{"success": false}`)) return } if user.Role == "org-reader" { log.Printf("[WARNING] Org-reader doesn't have access to toggle folder: %s (%s)", user.Username, user.Id) resp.WriteHeader(403) resp.Write([]byte(`{"success": false, "reason": "Read only user"}`)) return } location := strings.Split(request.URL.String(), "/") if location[1] != "api" || len(location) < 7 { log.Printf("[ERROR] Path too short or incorrect for detection toggle (2): %s", request.URL.String()) resp.WriteHeader(401) resp.Write([]byte(`{"success": false}`)) return } ctx := GetContext(request) detectionType := location[4] _ = detectionType action := location[6] rules, err := GetDisabledRules(ctx, user.ActiveOrg.Id) if err != nil { resp.WriteHeader(404) resp.Write([]byte(`{"success": false}`)) return } if action == "disable_folder" { rules.DisabledFolder = true } else if action == "enable_folder" { rules.DisabledFolder = false } else { log.Printf("[WARNING] path not found: %s", action) resp.WriteHeader(404) resp.Write([]byte(`{"success": false, "message": "The URL doesn't exist or is not allowed."}`)) return } err = StoreDisabledRules(ctx, *rules) if err != nil { log.Printf("[ERROR] Failed to store disabled rules: %s", err) resp.WriteHeader(500) resp.Write([]byte(`{"success": false}`)) return } var execType string if action == "disable_folder" { execType = "DISABLE_SIGMA_FOLDER" } else { execType = "CATEGORY_UPDATE" } err = SetDetectionOrborusRequest(ctx, user.ActiveOrg.Id, execType, "", "SIGMA", "SHUFFLE_DISCOVER") if err != nil { log.Printf("[ERROR] Failed setting workflow queue for env (4): %s", err) //resp.WriteHeader(500) //resp.Write([]byte(`{"success": false}`)) //return } resp.WriteHeader(200) resp.Write([]byte(`{"success": true}`)) } func disableRule(file File) error { ctx := context.Background() resp, err := GetDisabledRules(ctx, file.OrgId) if err != nil { if err.Error() == "rules doesn't exist" { // FIX ME :- code duplication : ( disabRules := &DisabledRules{} disabRules.Files = append(disabRules.Files, file) err = StoreDisabledRules(ctx, *disabRules) if err != nil { return err } log.Printf("[INFO] file with ID %s is disabled successfully", file.Id) return nil } else { return err } } resp.Files = append(resp.Files, file) err = StoreDisabledRules(ctx, *resp) if err != nil { return err } log.Printf("[INFO] file with ID %s is disabled successfully", file.Id) return nil } func enableRule(file File) error { ctx := context.Background() resp, err := GetDisabledRules(ctx, file.OrgId) if err != nil { return err } // Check if resp.Files is empty if len(resp.Files) == 0 { log.Printf("[INFO] No disabled rules found.") return nil } found := false for i, innerFile := range resp.Files { if innerFile.Id == file.Id { resp.Files = append(resp.Files[:i], resp.Files[i+1:]...) found = true break } } if !found { log.Printf("[INFO] File with ID %s not found in disabled rules", file.Id) return nil } err = StoreDisabledRules(ctx, *resp) if err != nil { return err } log.Printf("[INFO] File with ID %s is enabled successfully", file.Id) return nil } func HandleGetSelectedRules(resp http.ResponseWriter, request *http.Request) { cors := HandleCors(resp, request) if cors { return } _, err := HandleApiAuthentication(resp, request) if err != nil { log.Printf("[WARNING] Api authentication failed in get env stats executions: %s", err) resp.WriteHeader(401) resp.Write([]byte(`{"success": false}`)) return } var triggerId string location := strings.Split(request.URL.String(), "/") if len(location) < 5 || location[1] != "api" { log.Printf("[ERROR] Path too short or incorrect: %d", len(location)) resp.WriteHeader(400) resp.Write([]byte(`{"success": false}`)) return } triggerId = location[4] selectedRules, err := GetSelectedRules(request.Context(), triggerId) if err != nil { if err.Error() != "rules doesnt exists" { log.Printf("[ERROR] Error getting selected rules for %s: %s", triggerId, err) resp.WriteHeader(http.StatusInternalServerError) resp.Write([]byte(`{"success": false}`)) return } } responseData, err := json.Marshal(selectedRules) if err != nil { log.Printf("[ERROR] Failed to marshal response data: %s", err) resp.WriteHeader(500) resp.Write([]byte(`{"success": false"}`)) return } resp.WriteHeader(200) resp.Write(responseData) } func HandleSaveSelectedRules(resp http.ResponseWriter, request *http.Request) { cors := HandleCors(resp, request) if cors { return } user, err := HandleApiAuthentication(resp, request) if err != nil { log.Printf("[WARNING] Api authentication failed in save selected rules: %s", err) resp.WriteHeader(http.StatusUnauthorized) resp.Write([]byte(`{"success": false}`)) return } if user.Role == "org-reader" { log.Printf("[WARNING] Org-reader doesn't have access to save rules: %s (%s)", user.Username, user.Id) resp.WriteHeader(http.StatusForbidden) resp.Write([]byte(`{"success": false, "reason": "Read only user"}`)) return } location := strings.Split(request.URL.String(), "/") if len(location) < 5 || location[1] != "api" { log.Printf("[INFO] Path too short or incorrect (1): %d", len(location)) resp.WriteHeader(400) resp.Write([]byte(`{"success": false}`)) return } triggerId := location[4] selectedRules := SelectedDetectionRules{} decoder := json.NewDecoder(request.Body) err = decoder.Decode(&selectedRules) if err != nil { log.Printf("[ERROR] Failed to decode request body: %s", err) resp.WriteHeader(http.StatusBadRequest) resp.Write([]byte(`{"success": false, "reason": "Invalid request body"}`)) return } err = StoreSelectedRules(request.Context(), triggerId, selectedRules) if err != nil { log.Printf("[ERROR] Error storing selected rules for %s: %s", triggerId, err) resp.WriteHeader(http.StatusInternalServerError) resp.Write([]byte(`{"success": false}`)) return } responseData, err := json.Marshal(selectedRules) if err != nil { log.Printf("[ERROR] Failed to marshal response data: %s", err) resp.WriteHeader(http.StatusInternalServerError) resp.Write([]byte(`{"success": false}`)) return } resp.WriteHeader(http.StatusOK) resp.Write(responseData) } // FIXME: Should be generic - not just for SIEM/Sigma // E.g. try for Email/Sublime func HandleDetectionAutoConnect(resp http.ResponseWriter, request *http.Request) { cors := HandleCors(resp, request) if cors { return } user, err := HandleApiAuthentication(resp, request) if err != nil { log.Printf("[WARNING] Api authentication failed in conenct siem: %s", err) resp.WriteHeader(401) resp.Write([]byte(`{"success": false}`)) return } if user.Role == "org-reader" { resp.WriteHeader(403) resp.Write([]byte(`{"success": false, "reason": "Org reader does not have permission to connect to SIEM"}`)) return } // Check if url is /api/v1/detections/siem/ location := strings.Split(request.URL.String(), "/") if len(location) < 5 { log.Printf("[WARNING] Path too short: %d", len(location)) resp.WriteHeader(401) resp.Write([]byte(`{"success": false}`)) return } detectionType := strings.ToLower(location[4]) log.Printf("[DEBUG] Validating if the org %s (%s) has a %s sandbox handling workflow/system", user.ActiveOrg.Name, user.ActiveOrg.Id, detectionType) log.Printf("[AUDIT] User '%s' (%s) is trying to detection-connect to %s", user.Username, user.Id, strings.ToUpper(detectionType)) // Uses the same system we are using in the ai.go standard workflow creation workflow := Workflow{} if detectionType == "siem" || detectionType == "sigma" { categoryAction := CategoryAction{ Label: "Ingest Tickets_webhook", Category: "cases", } seedString := fmt.Sprintf("%s_%s", user.ActiveOrg.Id, categoryAction.Label) hash := sha1.New() hash.Write([]byte(seedString)) hashBytes := hash.Sum(nil) uuidBytes := make([]byte, 16) copy(uuidBytes, hashBytes) workflowId := uuid.Must(uuid.FromBytes(uuidBytes)).String() ctx := GetContext(request) foundWorkflow, err := GetWorkflow(ctx, workflowId) if err != nil || workflow.ID == "" { log.Printf("[WARNING] Failed to get workflow by ID '%s' in GenerateSingulWorkflows: %s", workflowId, err) //initialising = true newWorkflow, err := GetDefaultWorkflowByType(*foundWorkflow, user.ActiveOrg.Id, categoryAction) if err != nil { log.Printf("[ERROR] Failed to get default workflow in GenerateSingulWorkflows: %s", err) resp.WriteHeader(http.StatusInternalServerError) resp.Write([]byte(`{"success": false, "reason": "Failed to get default workflow for this category. Please contact support@shuffler.io"}`)) return } workflow = newWorkflow } else { workflow = *foundWorkflow } workflow.ID = workflowId log.Printf("[DEBUG] Sending orborus request to start Sigma handling IF an available environment is found.") execType := "START_TENZIR" err = SetDetectionOrborusRequest(ctx, user.ActiveOrg.Id, execType, "", "SIGMA", "SHUFFLE_DISCOVER") if err != nil { if strings.Contains(strings.ToLower(err.Error()), "must be started") { resp.WriteHeader(200) resp.Write([]byte(`{"success": true, "reason": "Please start the environment by running the relevant command.", "action": "environment_start"}`)) return } log.Printf("[ERROR] Failed setting workflow queue for env (5): %s", err) if strings.Contains(strings.ToLower(err.Error()), "no valid environments") { resp.WriteHeader(400) resp.Write([]byte(`{"success": false, "reason": "No valid environments found. Go to /admin?tab=environments to create one.", "action": "environment_create"}`)) return } resp.WriteHeader(500) resp.Write([]byte(`{"success": false}`)) return } } else if detectionType == "email" { // FIXME: // 1. Can we track if it's active based on a workflow + validation? // 2. The workflow should get email // 3. It should track unread AND read emails separately // 4. When a new email is received, we should automatically track the statistics for it ctx := GetContext(request) workflow, err = ConfigureDetectionWorkflow(ctx, user.ActiveOrg.Id, "EMAIL-DETECTION") if err != nil { log.Printf("\n\n\n[ERROR] Failed to create email handling workflow: %s\n\n\n", err) resp.WriteHeader(500) resp.Write([]byte(`{"success": false, "reason": "Failed to create email handling workflow. Please try again or contact support@shuffler.io"}`)) return } } else { log.Printf("[ERROR] Detection Type '%s' not implemented", detectionType) resp.WriteHeader(400) resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Detection Type '%s' not implemented"}`, detectionType))) return } success := true if len(workflow.ID) == 0 { success = false } else { log.Printf("[INFO] '%s' detection workflow in org '%s' ID: %s", detectionType, workflow.OrgId, workflow.ID) } resp.WriteHeader(200) resp.Write([]byte(fmt.Sprintf(`{"success": %v, "workflow_id": "%s", "workflow_valid": %v}`, success, workflow.ID, workflow.Validation.Valid))) } func SetDetectionOrborusRequest(ctx context.Context, orgId, execType, fileName, executionSource, environmentName string) error { if len(orgId) == 0 { log.Printf("[ERROR] No org ID provided for Orborus") return fmt.Errorf("No org ID provided") } environments, err := GetEnvironments(ctx, orgId) if err != nil { log.Printf("[ERROR] Failed to get environments: %s", err) return err } lakeNodes := 0 selectedEnvironments := []Environment{} for _, env := range environments { if env.Archived { continue } if env.Type == "cloud" { continue } if env.Name != environmentName && environmentName != "SHUFFLE_DISCOVER" { continue } // Validates if the environment already has a lake running /* cacheKey := fmt.Sprintf("queueconfig-%s-%s", env.Name, env.OrgId) cache, err := GetCache(ctx, cacheKey) if err == nil { newEnv := OrborusStats{} err = json.Unmarshal(cache.([]uint8), &newEnv) if err == nil { // No point in adding a job if the lake is already running if env.DataLake.Enabled && execType == "START_TENZIR" { lakeNodes += 1 continue } } } */ selectedEnvironments = append(selectedEnvironments, env) } if len(selectedEnvironments) == 0 { if lakeNodes > 0 { log.Printf("[ERROR] No environments needing a lake. Found lake nodes: %d", lakeNodes) return nil } else { return fmt.Errorf("No valid environments found for detection distribution") } } log.Printf("[DEBUG] Found %d potentially valid environment for detection distribution (s)", len(selectedEnvironments)) deployedToActiveEnv := false for _, env := range selectedEnvironments { execRequest := ExecutionRequest{ Type: execType, ExecutionId: uuid.NewV4().String(), ExecutionSource: executionSource, ExecutionArgument: fileName, Priority: 11, } parsedEnv := fmt.Sprintf("%s_%s", strings.ToLower(strings.ReplaceAll(strings.ReplaceAll(env.Name, " ", "-"), "_", "-")), orgId) if project.Environment != "cloud" { parsedEnv = strings.ToLower(strings.ReplaceAll(strings.ReplaceAll(env.Name, " ", "-"), "_", "-")) } err = SetWorkflowQueue(ctx, execRequest, parsedEnv) if err != nil { log.Printf("[ERROR] Failed to set workflow queue: %s", err) return err } else { if env.RunningIp != "" { deployedToActiveEnv = true } } } if !deployedToActiveEnv { return errors.New("This environment must be started first. Please start the environment by running it onprem") } go DeleteCache(ctx, fmt.Sprintf("environments_%s", orgId)) return nil } func HandleListDetectionCategories(resp http.ResponseWriter, request *http.Request) { cors := HandleCors(resp, request) if cors { return } /* user, err := HandleApiAuthentication(resp, request) if err != nil { log.Printf("[WARNING] Api authentication failed in get detection rules: %s", err) resp.WriteHeader(401) resp.Write([]byte(`{"success": false}`)) return } */ publicDetections := GetPublicDetections() data, err := json.Marshal(publicDetections) if err != nil { resp.WriteHeader(500) resp.Write([]byte(fmt.Sprintf(`{"success": false}`))) return } resp.WriteHeader(200) resp.Write(data) } // FIXME: This is not ready - just a starting point func ConfigureDetectionWorkflow(ctx context.Context, orgId, workflowType string) (Workflow, error) { log.Printf("[ERROR] Creating detection workflow for org %s (not implemented for all types). Type: %s", orgId, workflowType) /* // FIXME: Use Org to find the correct tools according to the Usecase // SHOULD map usecase from workflowType -> actual Usecase in blobs foundOrg, err := GetOrg(ctx, orgId) if err != nil { log.Printf("[ERROR] Failed to get org '%s' during detection workflow creation: %s", err) return err } */ user := User{ Role: "admin", ActiveOrg: OrgMini{ Id: orgId, }, } workflows, err := GetAllWorkflowsByQuery(ctx, user, 250, "") if err != nil && len(workflows) == 0 { log.Printf("[ERROR] Failed to loading workflows to validate email: %s", err) return Workflow{}, err } workflow := Workflow{} workflowValid := false for _, foundworkflow := range workflows { if foundworkflow.WorkflowType != workflowType { continue } if foundworkflow.Validation.Valid { workflowValid = true } workflow = foundworkflow break } _ = workflowValid if len(workflow.ID) > 0 { return workflow, nil } workflow = Workflow{ WorkflowType: workflowType, Actions: []Action{}, Triggers: []Trigger{}, } // Do this based on public workflows cloudWorkflowId := "" usecaseNames := []string{} if workflowType == "TENZIR-SIGMA" { log.Printf("[INFO] Creating SIEM handling workflow for org %s", orgId) // FIXME: Fix the detection workflow cloudWorkflowId = "b7b878c8-4302-4ab5-9492-de2539f7dc6b" usecaseNames = []string{"Search SIEM (Sigma)"} } else if workflowType == "EMAIL-DETECTION" { // How do we check what email tool they use? //log.Printf("[INFO] Creating email handling workflow for org %s", orgId) cloudWorkflowId = "31d1a492-9fe0-4c4a-807d-b44d9cb81fc0" usecaseNames = []string{"Search emails (Sublime)"} } if len(cloudWorkflowId) == 0 { return workflow, errors.New("No valid workflow found") } // Load it in from cloud with a normal GET request url := fmt.Sprintf("https://shuffler.io/api/v1/workflows/%s", cloudWorkflowId) client := GetExternalClient(url) req, err := http.NewRequest("GET", url, nil) if err != nil { log.Printf("[ERROR] Failed to create request for workflow: %s", err) return workflow, err } resp, err := client.Do(req) if err != nil { log.Printf("[ERROR] Failed to get workflow from cloud: %s", err) return workflow, err } defer resp.Body.Close() if resp.StatusCode != 200 { log.Printf("[ERROR] Failed to get workflow from cloud: %s", resp.Status) return workflow, errors.New("Failed to get workflow from cloud") } body, err := ioutil.ReadAll(resp.Body) if err != nil { log.Printf("[ERROR] Failed to read response body: %s", err) return workflow, err } err = json.Unmarshal(body, &workflow) if err != nil { log.Printf("[ERROR] Failed to unmarshal response body: %s", err) return workflow, err } // Clear out and reset IDs workflow.Created = time.Now().Unix() workflow.ID = uuid.NewV4().String() workflow.OrgId = orgId workflow.Org = []OrgMini{ OrgMini{ Id: orgId, }, } workflow.ExecutingOrg = OrgMini{ Id: orgId, } workflow.Public = false workflow.WorkflowType = workflowType workflow.Validation = TypeValidation{} for _, usecaseName := range usecaseNames { workflow.UsecaseIds = append(workflow.UsecaseIds, usecaseName) } workflow.ParentWorkflowId = "" for actionIndex, _ := range workflow.Actions { newId := uuid.NewV4().String() if workflow.Start == workflow.Actions[actionIndex].ID { workflow.Start = newId } for branchIndex, _ := range workflow.Branches { if workflow.Actions[actionIndex].ID == workflow.Branches[branchIndex].SourceID { workflow.Branches[branchIndex].SourceID = newId } if workflow.Actions[actionIndex].ID == workflow.Branches[branchIndex].DestinationID { workflow.Branches[branchIndex].DestinationID = newId } } workflow.Actions[actionIndex].ID = newId } for triggerIndex, _ := range workflow.Triggers { newId := uuid.NewV4().String() for branchIndex, _ := range workflow.Branches { if workflow.Triggers[triggerIndex].ID == workflow.Branches[branchIndex].SourceID { workflow.Branches[branchIndex].SourceID = newId } if workflow.Triggers[triggerIndex].ID == workflow.Branches[branchIndex].DestinationID { workflow.Branches[branchIndex].DestinationID = newId } } workflow.Triggers[triggerIndex].ID = newId // FIXME: Check if it's a schedule, then set the interval + start it if workflow.Triggers[triggerIndex].TriggerType == "schedule" { //workflow.Triggers[triggerIndex].Interval = 60 for paramIndex, param := range workflow.Triggers[triggerIndex].Parameters { if param.Name == "interval" { if project.Environment == "cloud" { param.Value = "*/5 * * * *" } else { param.Value = "300" } } workflow.Triggers[triggerIndex].Parameters[paramIndex] = param } // FIXME: Start the schedule automatically } } /* for branchIndex, _ := range workflow.Branches { workflow.Branches[branchIndex].ID = uuid.NewV4().String() } */ // FIXME: Add a changeout for ANY schemaless node to use the correct // action in it workflow.BackgroundProcessing = true log.Printf("[DEBUG] Saving workflow for org %s", orgId) err = SetWorkflow(ctx, workflow, workflow.ID) if err != nil { log.Printf("[ERROR] Failed to set workflow during detection save: %s", err) return Workflow{}, err } return workflow, nil }