diff --git a/backend/go-app/codegen.go b/backend/go-app/codegen.go index 78dbd8ff..57576fd5 100644 --- a/backend/go-app/codegen.go +++ b/backend/go-app/codegen.go @@ -475,6 +475,7 @@ func generateYaml(swagger *openapi3.Swagger, newmd5 string) (*openapi3.Swagger, api.Authentication.Parameters[0].Description = securitySchemes["BearerAuth"].Value.Description api.Authentication.Parameters[0].Name = securitySchemes["BearerAuth"].Value.Name api.Authentication.Parameters[0].In = securitySchemes["BearerAuth"].Value.In + api.Authentication.Parameters[0].Schema.Type = securitySchemes["BearerAuth"].Value.Scheme api.Authentication.Parameters[0].Scheme = securitySchemes["BearerAuth"].Value.Scheme //log.Printf("HANDLE BEARER AUTH") extraParameters = append(extraParameters, WorkflowAppActionParameter{ @@ -492,6 +493,7 @@ func generateYaml(swagger *openapi3.Swagger, newmd5 string) (*openapi3.Swagger, api.Authentication.Parameters[0].Description = securitySchemes["ApiKeyAuth"].Value.Description api.Authentication.Parameters[0].Name = securitySchemes["ApiKeyAuth"].Value.Name api.Authentication.Parameters[0].In = securitySchemes["ApiKeyAuth"].Value.In + api.Authentication.Parameters[0].Schema.Type = securitySchemes["ApiKeyAuth"].Value.Scheme api.Authentication.Parameters[0].Scheme = securitySchemes["ApiKeyAuth"].Value.Scheme //log.Printf("HANDLE APIKEY AUTH") extraParameters = append(extraParameters, WorkflowAppActionParameter{ @@ -509,6 +511,7 @@ func generateYaml(swagger *openapi3.Swagger, newmd5 string) (*openapi3.Swagger, api.Authentication.Parameters[0].Description = securitySchemes["BasicAuth"].Value.Description api.Authentication.Parameters[0].Name = securitySchemes["BasicAuth"].Value.Name api.Authentication.Parameters[0].In = securitySchemes["BasicAuth"].Value.In + api.Authentication.Parameters[0].Schema.Type = securitySchemes["BasicAuth"].Value.Scheme api.Authentication.Parameters[0].Scheme = securitySchemes["BasicAuth"].Value.Scheme extraParameters = append(extraParameters, WorkflowAppActionParameter{ Name: "username", diff --git a/backend/go-app/main.go b/backend/go-app/main.go index 4ed2899d..2c114a84 100644 --- a/backend/go-app/main.go +++ b/backend/go-app/main.go @@ -6452,6 +6452,10 @@ func init() { r.HandleFunc("/api/v1/apps", setNewWorkflowApp).Methods("PUT", "OPTIONS") r.HandleFunc("/api/v1/apps/search", getSpecificApps).Methods("POST", "OPTIONS") + r.HandleFunc("/api/v1/apps/authentication", getAppAuthentication).Methods("GET", "OPTIONS") + r.HandleFunc("/api/v1/apps/authentication", addAppAuthentication).Methods("PUT", "OPTIONS") + r.HandleFunc("/api/v1/apps/authentication/{appauthId}", deleteAppAuthentication).Methods("DELETE", "OPTIONS") + // Legacy app things r.HandleFunc("/api/v1/workflows/apps/validate", validateAppInput).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/workflows/apps", getWorkflowApps).Methods("GET", "OPTIONS") diff --git a/backend/go-app/walkoff.go b/backend/go-app/walkoff.go index 5221fef0..03ef8e81 100644 --- a/backend/go-app/walkoff.go +++ b/backend/go-app/walkoff.go @@ -77,6 +77,21 @@ type Org struct { Id string `json:"id"` } +type AppAuthenticationStorage struct { + Active bool `json:"active" datastore:"active"` + Label string `json:"label" datastore:"label"` + Id string `json:"id" datastore:"id"` + App WorkflowApp `json:"app" datastore:"app"` + Fields []AuthenticationStore `json:"fields" datastore:"fields"` + Usage []AuthenticationUsage `json:"usage" datastore:"usage"` +} + +type AuthenticationUsage struct { + WorkflowId string `json:"workflow_id" datastore:"workflow_id"` + Nodes []string `json:"nodes" datastore:"nodes"` +} + +// An app inside Shuffle type WorkflowApp struct { Name string `json:"name" yaml:"name" required:true datastore:"name"` IsValid bool `json:"is_valid" yaml:"is_valid" required:true datastore:"is_valid"` @@ -146,7 +161,9 @@ type WorkflowAppAction struct { ID string `json:"id" datastore:"id" yaml:"id,omitempty"` Schema SchemaDefinition `json:"schema" datastore:"schema" yaml:"schema"` } `json:"returns" datastore:"returns"` - Example string `json:"example" datastore:"example" yaml:"example"` + AuthenticationId string `json:"authentication_id" datastore:"authentication_id"` + Example string `json:"example" datastore:"example" yaml:"example"` + AuthNotRequired bool `json:"auth_not_required" datastore:"auth_not_required" yaml:"auth_not_required"` } // FIXME: Generate a callback authentication ID? @@ -202,8 +219,10 @@ type Action struct { X float64 `json:"x" datastore:"x"` Y float64 `json:"y" datastore:"y"` } `json:"position"` - Priority int `json:"priority" datastore:"priority"` - Example string `json:"example" datastore:"example"` + Priority int `json:"priority" datastore:"priority"` + AuthenticationId string `json:"authentication_id" datastore:"authentication_id"` + Example string `json:"example" datastore:"example"` + AuthNotRequired bool `json:"auth_not_required" datastore:"auth_not_required" yaml:"auth_not_required"` } // Added environment for location to execute @@ -304,15 +323,16 @@ type Authentication struct { } type AuthenticationParams struct { - Description string `json:"description" datastore:"description" yaml:"description"` - ID string `json:"id" datastore:"id" yaml:"id"` - Name string `json:"name" datastore:"name" yaml:"name"` - Example string `json:"example" datastore:"example" yaml:"example"` - Value string `json:"value,omitempty" datastore:"value" yaml:"value"` - Multiline bool `json:"multiline" datastore:"multiline" yaml:"multiline"` - Required bool `json:"required" datastore:"required" yaml:"required"` - In string `json:"in" datastore:"in" yaml:"in"` - Scheme string `json:"scheme" datastore:"scheme" yaml:"scheme"` + Description string `json:"description" datastore:"description" yaml:"description"` + ID string `json:"id" datastore:"id" yaml:"id"` + Name string `json:"name" datastore:"name" yaml:"name"` + Example string `json:"example" datastore:"example" yaml:"example"` + Value string `json:"value,omitempty" datastore:"value" yaml:"value"` + Multiline bool `json:"multiline" datastore:"multiline" yaml:"multiline"` + Required bool `json:"required" datastore:"required" yaml:"required"` + In string `json:"in" datastore:"in" yaml:"in"` + Schema SchemaDefinition `json:"schema" datastore:"schema" yaml:"schema"` + Scheme string `json:"scheme" datastore:"scheme" yaml:"scheme"` // Deprecated } type AuthenticationStore struct { @@ -1473,8 +1493,10 @@ func saveWorkflow(resp http.ResponseWriter, request *http.Request) { // FIXME - this shouldn't be necessary with proper API checks newActions := []Action{} allNodes := []string{} - //log.Println("Pre") + + //log.Printf("Action: %#v", action.Authentication) for _, action := range workflow.Actions { + log.Printf("Auth: %s", action.AuthenticationId) allNodes = append(allNodes, action.ID) if action.Environment == "" { @@ -1685,6 +1707,9 @@ func saveWorkflow(resp http.ResponseWriter, request *http.Request) { for _, param := range curappaction.Parameters { found := false + // FIXME: Check if the name exists in authentication.parameters and doesn't use the auth required field + // If it does, the auth should be saved somehow. + // Handles check for parameter exists + value not empty in used fields for _, actionParam := range action.Parameters { if actionParam.Name == param.Name { @@ -2970,6 +2995,46 @@ func setWorkflow(ctx context.Context, workflow Workflow, id string) error { return nil } +func deleteAppAuthentication(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + user, userErr := handleApiAuthentication(resp, request) + if userErr != nil { + log.Printf("Api authentication failed in edit workflow: %s", userErr) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + if user.Role != "admin" { + log.Printf("Need to be admin to delete appauth") + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + location := strings.Split(request.URL.String(), "/") + log.Printf("%#v", location) + var fileId string + if location[1] == "api" { + if len(location) <= 4 { + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + fileId = location[4] + } + + log.Printf("ID: %s", fileId) + + resp.WriteHeader(200) + resp.Write([]byte(`{"success": true}`)) +} + func deleteWorkflowApp(resp http.ResponseWriter, request *http.Request) { cors := handleCors(resp, request) if cors { @@ -3194,6 +3259,195 @@ func getWorkflowAppConfig(resp http.ResponseWriter, request *http.Request) { resp.Write(data) } +func addAppAuthentication(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + // FIXME - need to be logged in? + _, userErr := handleApiAuthentication(resp, request) + if userErr != nil { + log.Printf("Api authentication failed in get all apps: %s", userErr) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + body, err := ioutil.ReadAll(request.Body) + if err != nil { + log.Printf("Error with body read: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + var appAuth AppAuthenticationStorage + err = json.Unmarshal(body, &appAuth) + if err != nil { + log.Printf("Failed unmarshaling (appauth): %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + if len(appAuth.Id) == 0 { + appAuth.Id = uuid.NewV4().String() + } + + ctx := context.Background() + if len(appAuth.Label) == 0 { + resp.WriteHeader(409) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Label can't be empty"}`))) + return + } + + if len(appAuth.App.ID) != 36 { + log.Printf("Bad ID for app: %s", appAuth.App.ID) + resp.WriteHeader(409) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "App has to be defined"}`))) + return + } + + app, err := getApp(ctx, appAuth.App.ID) + if err != nil { + log.Printf("Failed finding app %s while setting auth.", appAuth.App.ID) + resp.WriteHeader(409) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) + return + } + + // Check if the items are correct + for _, field := range appAuth.Fields { + found := false + for _, param := range app.Authentication.Parameters { + if field.Key == param.Name { + found = true + } + } + + if !found { + log.Printf("Failed finding field %s in appauth fields", field.Key) + resp.WriteHeader(409) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "All auth fields required"}`))) + return + } + } + + err = setWorkflowAppAuthDatastore(ctx, appAuth, appAuth.Id) + if err != nil { + log.Printf("Failed setting up app auth %s: %s", appAuth.Id, err) + resp.WriteHeader(409) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) + return + } + + resp.WriteHeader(200) + resp.Write([]byte(`{"success": true}`)) +} + +func getAppAuthentication(resp http.ResponseWriter, request *http.Request) { + cors := handleCors(resp, request) + if cors { + return + } + + _, userErr := handleApiAuthentication(resp, request) + if userErr != nil { + log.Printf("Api authentication failed in get all apps: %s", userErr) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + // FIXME: Auth to get the right ones only + //if user.Role != "admin" { + // log.Printf("User isn't admin") + // resp.WriteHeader(401) + // resp.Write([]byte(`{"success": false}`)) + // return + //} + ctx := context.Background() + allAuths, err := getAllWorkflowAppAuth(ctx) + if userErr != nil { + log.Printf("Api authentication failed in get all app auth: %s", userErr) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + if len(allAuths) == 0 { + resp.WriteHeader(200) + resp.Write([]byte(`{"success": true, "data": []}`)) + return + } + + newbody, err := json.Marshal(allAuths) + if err != nil { + log.Printf("Failed unmarshalling all app auths: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed unpacking workflow app auth"}`))) + return + } + + data := fmt.Sprintf(`{"success": true, "data": %s}`, string(newbody)) + + resp.WriteHeader(200) + resp.Write([]byte(data)) + + /* + data := `{ + "success": true, + "data": [ + { + "app": { + "name": "thehive", + "description": "what", + "app_version": "1.0.0", + "id": "4f97da9d-1caf-41cc-aa13-67104d8d825c", + "large_image": "asd" + }, + "fields": { + "apikey": "hello", + "url": "url" + }, + "usage": [{ + "workflow_id": "asd", + "nodes": [{ + "node_id": "" + }] + }], + "label": "Original", + "id": "4f97da9d-1caf-41cc-aa13-67104d8d825d", + "active": true + }, + { + "app": { + "name": "thehive", + "description": "what", + "app_version": "1.0.0", + "id": "4f97da9d-1caf-41cc-aa13-67104d8d825c", + "large_image": "asd" + }, + "fields": { + "apikey": "hello", + "url": "url" + }, + "usage": [{ + "workflow_id": "asd", + "nodes": [{ + "node_id": "" + }] + }], + "label": "Number 2", + "id": "4f97da9d-1caf-41cc-aa13-67104d8d825d", + "active": true + } + ] + }` + */ +} + func getWorkflowApps(resp http.ResponseWriter, request *http.Request) { cors := handleCors(resp, request) if cors { @@ -4208,20 +4462,54 @@ func iterateAppGithubFolders(fs billy.Filesystem, dir []os.FileInfo, extra strin } } - /* - if workflowapp.Name == "thehive" { - for _, action := range workflowapp.Actions { - if len(action.Returns.Example) > 0 { - log.Printf("ACTION: %#v", action) - } - } - } - */ - if skip { continue } + // Fixes (appends) authentication parameters if they're required + if workflowapp.Authentication.Required { + log.Printf("Checking authentication fields and appending for %s!", workflowapp.Name) + // FIXME: + // Might require reflection into the python code to append the fields as well + for index, action := range workflowapp.Actions { + if action.AuthNotRequired { + log.Printf("Skipping auth setup: %s", action.Name) + continue + } + + // 1. Check if authentication params exists at all + // 2. Check if they're present in the action + // 3. Add them IF they DONT exist + // 4. Fix python code with reflection (FIXME) + appendParams := []WorkflowAppActionParameter{} + for _, fieldname := range workflowapp.Authentication.Parameters { + found := false + for _, param := range action.Parameters { + if param.Name == fieldname.Name { + found = true + break + } + } + + if !found { + appendParams = append(appendParams, WorkflowAppActionParameter{ + Name: fieldname.Name, + Description: fieldname.Description, + Example: fieldname.Example, + Required: fieldname.Required, + Schema: fieldname.Schema, + }) + } + } + + if len(appendParams) > 0 { + log.Printf("Appending %d params to the START of %s", len(appendParams), action.Name) + workflowapp.Actions[index].Parameters = append(appendParams, workflowapp.Actions[index].Parameters...) + } + + } + } + err = checkWorkflowApp(workflowapp) if err != nil { log.Printf("%s for app %s:%s", err, workflowapp.Name, workflowapp.AppVersion) @@ -4270,7 +4558,7 @@ func iterateAppGithubFolders(fs billy.Filesystem, dir []os.FileInfo, extra strin if len(tags) > 0 { log.Printf("Successfully built image %s", tags[0]) } else { - log.Printf("Successfully built image docker img") + log.Printf("Successfully built Docker image") } } } @@ -4473,6 +4761,30 @@ func getAllWorkflowApps(ctx context.Context) ([]WorkflowApp, error) { return allworkflowapps, nil } +func getAllWorkflowAppAuth(ctx context.Context) ([]AppAuthenticationStorage, error) { + var allworkflowapps []AppAuthenticationStorage + q := datastore.NewQuery("workflowappauth") + + _, err := dbclient.GetAll(ctx, q, &allworkflowapps) + if err != nil { + return []AppAuthenticationStorage{}, err + } + + return allworkflowapps, nil +} + +func setWorkflowAppAuthDatastore(ctx context.Context, workflowappauth AppAuthenticationStorage, id string) error { + key := datastore.NameKey("workflowappauth", id, nil) + + // New struct, to not add body, author etc + if _, err := dbclient.Put(ctx, key, &workflowappauth); err != nil { + log.Printf("Error adding workflow app: %s", err) + return err + } + + return nil +} + // Hmm, so I guess this should use uuid :( // Consistency PLX func setWorkflowAppDatastore(ctx context.Context, workflowapp WorkflowApp, id string) error { diff --git a/frontend/src/Admin.js b/frontend/src/Admin.js index a4762fc4..7d66bcac 100644 --- a/frontend/src/Admin.js +++ b/frontend/src/Admin.js @@ -33,10 +33,13 @@ const Admin = (props) => { const [curTab, setCurTab] = React.useState(0); const [users, setUsers] = React.useState([]); const [environments, setEnvironments] = React.useState([]); + const [authentication, setAuthentication] = React.useState([]); const [schedules, setSchedules] = React.useState([]) const [selectedUser, setSelectedUser] = React.useState({}) const [newPassword, setNewPassword] = React.useState(""); const [selectedUserModalOpen, setSelectedUserModalOpen] = React.useState(false) + const [selectedAuthentication, setSelectedAuthentcation] = React.useState({}) + const [selectedAuthenticationModalOpen, setSelectedAuthenticationModalOpen] = React.useState(false) const alert = useAlert() @@ -253,6 +256,35 @@ const Admin = (props) => { }); } + const getAppAuthentication = () => { + fetch(globalUrl+"/api/v1/apps/authentication", { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for apps :O!") + return + } + + return response.json() + }) + .then((responseJson) => { + if (responseJson.success) { + setAuthentication(responseJson.data) + } else { + alert.error("Failed getting authentications") + } + }) + .catch(error => { + alert.error(error.toString()) + }); + } + const getEnvironments = () => { fetch(globalUrl+"/api/v1/getenvironments", { method: 'GET', @@ -393,6 +425,70 @@ const Admin = (props) => { }); } + const editAuthenticationModal = + {setSelectedAuthenticationModalOpen(false)}} + PaperProps={{ + style: { + backgroundColor: surfaceColor, + color: "white", + minWidth: "800px", + minHeight: "320px", + }, + }} + > + Edit authentication + + + setNewPassword(e.target.value)} + /> + onPasswordChange()} + > + Submit + + + + deleteUser(selectedUser)} + > + {selectedUser.active ? "Deactivate" : "Activate"} + + generateApikey(selectedUser.id)} + > + Get new API key + + + + const editUserModal = { : null - const schedulesView = curTab === 2 ? + const schedulesView = curTab === 3 ? Schedules @@ -708,7 +804,97 @@ const Admin = (props) => { : null - const environmentView = curTab === 1 ? + const authenticationView = curTab === 1 ? + + + Authentication + + + + + + + + + + + + {authentication === undefined ? null : authentication.map(data => { + return ( + + + style={{minWidth: 150, maxWidth: 150}} + /> + + + { + return data.key + }).join(", ")} + style={{minWidth: 320, maxWidth: 320, overflow: "hidden"}} + /> + + + { + setSelectedAuthentcation(data) + setSelectedAuthenticationModalOpen(true) + }} + > + Edit + + { + setSelectedAuthentcation(data) + setSelectedAuthenticationModalOpen(true) + }} + > + Delete + + + + ) + })} + + + : null + + const environmentView = curTab === 2 ? Environments @@ -737,8 +923,10 @@ const Admin = (props) => { const setConfig = (event, newValue) => { if (newValue === 1) { - getEnvironments() + getAppAuthentication() } else if (newValue === 2) { + getEnvironments() + } else if (newValue === 3) { getSchedules() } @@ -757,10 +945,12 @@ const Admin = (props) => { aria-label="disabled tabs example" > + + {authenticationView} {usersView} {environmentView} {schedulesView} @@ -771,6 +961,7 @@ const Admin = (props) => { {modalView} {editUserModal} + {editAuthenticationModal} {data} ) diff --git a/frontend/src/AngularWorkflow.js b/frontend/src/AngularWorkflow.js index 43e12fa7..03e02b23 100644 --- a/frontend/src/AngularWorkflow.js +++ b/frontend/src/AngularWorkflow.js @@ -37,6 +37,7 @@ import { useBeforeunload } from 'react-beforeunload'; import ArrowUpwardIcon from '@material-ui/icons/ArrowUpward'; import CachedIcon from '@material-ui/icons/Cached'; +import AddIcon from '@material-ui/icons/Add'; import DirectionsRunIcon from '@material-ui/icons/DirectionsRun'; import PolymerIcon from '@material-ui/icons/Polymer'; import CreateIcon from '@material-ui/icons/Create'; @@ -117,7 +118,7 @@ const AngularWorkflow = (props) => { const [executionText, setExecutionText] = React.useState(""); const [executionRequestStarted, setExecutionRequestStarted] = React.useState(false); - const [appAuthentication, setAppAuthentication] = React.useState({}); + const [appAuthentication, setAppAuthentication] = React.useState([]); const [variablesModalOpen, setVariablesModalOpen] = React.useState(false); const [executionVariablesModalOpen, setExecutionVariablesModalOpen] = React.useState(false); const [authenticationModalOpen, setAuthenticationModalOpen] = React.useState(false); @@ -127,7 +128,7 @@ const AngularWorkflow = (props) => { const [newVariableValue, setNewVariableValue] = React.useState(""); const [workflowDone, setWorkflowDone] = React.useState(false) const [localFirstrequest, setLocalFirstrequest] = React.useState(true) - const [requiresAuthentication, setRequiresAuthentication] = React.useState(true) + const [requiresAuthentication, setRequiresAuthentication] = React.useState(false) const [rightSideBarOpen, setRightSideBarOpen] = React.useState(false) const [showSkippedActions, setShowSkippedActions] = React.useState(false) @@ -193,6 +194,37 @@ const AngularWorkflow = (props) => { } }) + const setNewAppAuth = (appAuthData) => { + console.log("DAta: ", appAuthData) + fetch(globalUrl+"/api/v1/apps/authentication", { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + body: JSON.stringify(appAuthData), + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for setting app auth :O!") + } + + return response.json() + }) + .then((responseJson) => { + if (!responseJson.success) { + alert.error("Failed to set app auth: "+responseJson.reason) + } else { + setAuthenticationModalOpen(false) + alert.success("Successfully saved workflow") + } + }) + .catch(error => { + alert.error(error.toString()) + }) + } + const getWorkflowExecution = (id) => { fetch(globalUrl+"/api/v1/workflows/"+id+"/executions", { method: 'GET', @@ -723,6 +755,35 @@ const AngularWorkflow = (props) => { return data } + const getAppAuthentication = () => { + fetch(globalUrl+"/api/v1/apps/authentication", { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for apps :O!") + return + } + + return response.json() + }) + .then((responseJson) => { + if (responseJson.success) { + setAppAuthentication(responseJson.data) + } else { + alert.error("Failed getting authentications") + } + }) + .catch(error => { + alert.error(error.toString()) + }); + } + const getApps = () => { fetch(globalUrl+"/api/v1/workflows/apps", { method: 'GET', @@ -747,6 +808,7 @@ const AngularWorkflow = (props) => { //tmpapps = tmpapps.concat(responseJson) setApps(responseJson) setFilteredApps(responseJson) + getAppAuthentication() }) .catch(error => { alert.error(error.toString()) @@ -831,9 +893,9 @@ const AngularWorkflow = (props) => { // FIXME - unselect //console.log(cy.elements('[_id!="${data._id}"]`)) // Does it choose the wrong action? - const curaction = workflow.actions.find(a => a.id === data.id) + var curaction = workflow.actions.find(a => a.id === data.id) if (!curaction || curaction === undefined) { - //console.log("Action not found error") + //alert.error("Action not found. Please remake it.") return } @@ -848,11 +910,41 @@ const AngularWorkflow = (props) => { env = environments[0] } - setSelectedApp(curapp) - setSelectedAction(curaction) setSelectedActionEnvironment(env) setSelectedActionName(curaction.name) setRequiresAuthentication(curapp.authentication.required) + + // Setup auth here :) + const authenticationOptions = [] + var findAuthId = "" + if (curaction.authentication_id !== null && curaction.authentication_id !== undefined && curaction.authentication_id.length > 0) { + findAuthId = curaction.authentication_id + } + + for (var key in appAuthentication) { + var item = appAuthentication[key] + + const newfields = {} + for (var filterkey in item.fields) { + newfields[item.fields[filterkey].key] = item.fields[filterkey].value + } + + item.fields = newfields + if (item.app.name === curapp.name) { + authenticationOptions.push(item) + if (item.id === findAuthId) { + curaction.selectedAuthentication = item + } + } + } + + curaction.authentication = authenticationOptions + if (curaction.selectedAuthentication === null || curaction.selectedAuthentication === undefined || curaction.selectedAuthentication.length === "") { + curaction.selectedAuthentication = {} + } + + setSelectedApp(curapp) + setSelectedAction(curaction) } else if (data.type === "TRIGGER") { //console.log("Should handle trigger "+data.triggertype) //console.log(data) @@ -1130,6 +1222,7 @@ const AngularWorkflow = (props) => { setFirstrequest(false) getWorkflow() getApps() + getAppAuthentication() getEnvironments() getWorkflowExecution(props.match.params.key) return @@ -2280,10 +2373,6 @@ const AngularWorkflow = (props) => { useEffect(() => { if (selectedActionParameters !== null && selectedActionParameters.length === 0) { - if (requiresAuthentication) { - console.log("ADD AUTHENTICATION FIELDS") - } - if (selectedAction.parameters !== null && selectedAction.parameters.length > 0) { setSelectedActionParameters(selectedAction.parameters) } @@ -2493,6 +2582,16 @@ const AngularWorkflow = (props) => { data.variant = "STATIC_VALUE" } + if (!selectedAction.auth_not_required && selectedAction.selectedAuthentication !== undefined && selectedAction.selectedAuthentication.fields !== undefined) { + if (selectedAction.selectedAuthentication.fields[data.name] !== undefined) { + // FIXME - this should be skipped in the frontend + selectedActionParameters[count].value = selectedAction.selectedAuthentication.fields[data.name] + selectedAction.parameters[count].value = selectedAction.selectedAuthentication.fields[data.name] + setSelectedAction(selectedAction) + return null + } + } + var staticcolor = "inherit" var actioncolor = "inherit" var varcolor = "inherit" @@ -2935,7 +3034,65 @@ const AngularWorkflow = (props) => { placeholder={selectedAction.label} onChange={selectedNameChange} /> - {environments !== undefined && environments !== null && environments.length > 0 ? + {selectedAction.authentication.length === 0 && requiresAuthentication ? + + Authentication (reusable): + + { + setAuthenticationModalOpen(true) + }}> + + + + + : null} + {selectedAction.authentication.length > 0 ? + + Authentication + + { + console.log("CHOSE AN AUTHENTICATION OPTION: ", e.target.value) + selectedAction.selectedAuthentication = e.target.value + selectedAction.authentication_id = e.target.value.id + setSelectedAction(selectedAction) + setUpdate("update auth") + }} + style={{backgroundColor: inputColor, color: "white", height: "50px"}} + > + {selectedAction.authentication.map(data => ( + + {data.label} - ({data.app.app_version}) + + ))} + + + {/* + + setAuthenticationModalOpen(true)}> + AUTHENTICATE + + curaction.authentication = authenticationOptions + if (curaction.selectedAuthentication === null || curaction.selectedAuthentication === undefined || curaction.selectedAuthentication.length === "") + */} + + { + setAuthenticationModalOpen(true) + }}> + + + + + + : null} + {environments !== undefined && environments !== null && environments.length > 1 ? Environment { : null} - {/*requiresAuthentication ? - - setAuthenticationModalOpen(true)}> - AUTHENTICATE - - - : null*/} @@ -3033,7 +3183,7 @@ const AngularWorkflow = (props) => { newActionname = newActionname.replace("_", " ") newActionname = newActionname.charAt(0).toUpperCase()+newActionname.substring(1) return ( - + {newActionname} @@ -5243,52 +5393,136 @@ const AngularWorkflow = (props) => { : null - const AuthenticationData = () => { - console.log("AUTH: ", selectedApp.authentication) - const [tmpVar, setTmpVar] = React.useState("") + const AuthenticationData = (props) => { + const selectedApp = props.app + + const [authenticationOption, setAuthenticationOptions] = React.useState({ + app: JSON.parse(JSON.stringify(selectedApp)), + fields: {}, + label: "", + usage: [{ + workflow_id: workflow.id, + }], + id: uuid.v4(), + active: true, + }) + if (selectedApp.authentication === undefined) { return null } - if (selectedApp.authentication.parameters.length === undefined || - selectedApp.authentication.parameters.length === 0) { + if (selectedApp.authentication.parameters.length === undefined || selectedApp.authentication.parameters.length === 0) { return null } - // Yes, it should be possible to have more than one, but.. :) - // This data should be written to a KMS, then have the ID point back - const currentAuth = selectedApp.authentication.parameters[0] - if (currentAuth.scheme.toLowerCase() === "bearer") { - return - Insert your API token for {selectedApp.name} - { - setTmpVar(event.target.value) - }} - onBlur={() => { - selectedApp.authentication.parameters[0].value = tmpVar - setSelectedApp(selectedApp) - }} - /> - + authenticationOption.app.actions = [] + + for (var key in selectedApp.authentication.parameters) { + if (authenticationOption.fields[selectedApp.authentication.parameters[key].name] === undefined) { + authenticationOption.fields[selectedApp.authentication.parameters[key].name] = "" + } + } + + const handleSubmitCheck = () => { + console.log(authenticationOption) + if (authenticationOption.label.length === 0) { + alert.info("Label can't be empty") + } + + for (var key in selectedApp.authentication.parameters) { + if (authenticationOption.fields[selectedApp.authentication.parameters[key].name].length === 0) { + alert.info("Field "+selectedApp.authentication.parameters[key].name+" can't be empty") + return + } + } + + selectedAction.authentication_id = authenticationOption.id + selectedAction.selectedAuthentication = authenticationOption + selectedAction.authentication.push(authenticationOption) + setSelectedAction(selectedAction) + + var newFields = [] + for (const key in authenticationOption.fields) { + const value = authenticationOption.fields[key] + newFields.push({ + key: key, + value: value, + }) + } + + authenticationOption.fields = newFields + setNewAppAuth(authenticationOption) } return ( - NOT IMPLEMENTED - Unknown auth: {currentAuth.scheme} + + What is this? + These are required fields for authenticating with TheHive + + {selectedApp.link.length > 0 ? : null} + Label (to remember it) + { + authenticationOption.label = event.target.value + }} + /> + + + {selectedApp.authentication.parameters.map((data, index) => { + return ( + + {data.name} + { + authenticationOption.fields[data.name] = event.target.value + }} + /> + + ) + })} + + + { + setAuthenticationModalOpen(false) + }} color="primary"> + Cancel + + { + handleSubmitCheck() + }} color="primary"> + Submit + + ) } @@ -5328,40 +5562,22 @@ const AngularWorkflow = (props) => { // This whole part is redundant. Made it part of Arguments instead. const authenticationModal = authenticationModalOpen ? - { - setAuthenticationModalOpen(false) - setAppAuthentication({}) + //setAuthenticationModalOpen(false) }} PaperProps={{ style: { backgroundColor: surfaceColor, color: "white", - minWidth: "800px", + minWidth: 600, + padding: 15, }, }} > Authentication for {selectedApp.name} - - What is this? - - {selectedApp.link.length > 0 ? : null} - - - - - { - setAuthenticationModalOpen(false) - }} color="primary"> - Cancel - - {setAuthenticationModalOpen(false)}} color="primary"> - Submit - - + : null const loadedCheck = isLoaded && isLoggedIn && workflowDone ? diff --git a/frontend/src/Apps.js b/frontend/src/Apps.js index 140fdf7d..8d0a23ce 100644 --- a/frontend/src/Apps.js +++ b/frontend/src/Apps.js @@ -289,8 +289,8 @@ const Apps = (props) => { { if (selectedApp.id !== data.id) { setSelectedApp(data) + console.log(data) if (data.actions !== undefined && data.actions !== null && data.actions.length > 0) { - console.log(data.actions[0]) setSelectedAction(data.actions[0]) } else { setSelectedAction({})