#26: Added initial version of app authentication

This commit is contained in:
frikky
2020-07-16 11:11:15 +02:00
parent 2eff6c74d1
commit ada6dd9e8d
6 changed files with 831 additions and 105 deletions
+3
View File
@@ -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",
+4
View File
@@ -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")
+336 -24
View File
@@ -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 {
+194 -3
View File
@@ -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 =
<Dialog modal
open={selectedAuthenticationModalOpen}
onClose={() => {setSelectedAuthenticationModalOpen(false)}}
PaperProps={{
style: {
backgroundColor: surfaceColor,
color: "white",
minWidth: "800px",
minHeight: "320px",
},
}}
>
<DialogTitle><span style={{color: "white"}}>Edit authentication</span></DialogTitle>
<DialogContent>
<div style={{display: "flex"}}>
<TextField
style={{backgroundColor: inputColor, flex: 3}}
InputProps={{
style:{
height: 50,
color: "white",
},
}}
color="primary"
required
fullWidth={true}
placeholder="New password"
type="password"
id="standard-required"
autoComplete="password"
margin="normal"
variant="outlined"
onChange={e => setNewPassword(e.target.value)}
/>
<Button
style={{maxHeight: 50, flex: 1}}
variant="outlined"
color="primary"
onClick={() => onPasswordChange()}
>
Submit
</Button>
</div>
<Divider style={{marginTop: 20, marginBottom: 20, backgroundColor: inputColor}}/>
<Button
style={{}}
variant="outlined"
color="primary"
onClick={() => deleteUser(selectedUser)}
>
{selectedUser.active ? "Deactivate" : "Activate"}
</Button>
<Button
style={{}}
variant="outlined"
color="primary"
onClick={() => generateApikey(selectedUser.id)}
>
Get new API key
</Button>
</DialogContent>
</Dialog>
const editUserModal =
<Dialog modal
open={selectedUserModalOpen}
@@ -660,7 +756,7 @@ const Admin = (props) => {
</div>
: null
const schedulesView = curTab === 2 ?
const schedulesView = curTab === 3 ?
<div>
<h2>
Schedules
@@ -708,7 +804,97 @@ const Admin = (props) => {
</div>
: null
const environmentView = curTab === 1 ?
const authenticationView = curTab === 1 ?
<div>
<h2>
Authentication
</h2>
<Divider style={{marginTop: 20, marginBottom: 20, backgroundColor: inputColor}}/>
<List>
<ListItem>
<ListItemText
primary="Icon"
style={{minWidth: 150, maxWidth: 150}}
/>
<ListItemText
primary="Label"
style={{minWidth: 150, maxWidth: 150}}
/>
<ListItemText
primary="App Name"
style={{minWidth: 150, maxWidth: 150}}
/>
<ListItemText
primary="Fields"
style={{minWidth: 320, maxWidth: 320, overflow: "hidden"}}
/>
<ListItemText
primary="Workflow usage"
style={{minWidth: 320, maxWidth: 320, overflow: "hidden"}}
/>
<ListItemText
primary="Actions (TBD)"
/>
</ListItem>
{authentication === undefined ? null : authentication.map(data => {
return (
<ListItem>
<ListItemText
primary=<img alt="" src={data.app.large_image} style={{maxWidth: 50,}} />
style={{minWidth: 150, maxWidth: 150}}
/>
<ListItemText
primary={data.label}
style={{minWidth: 150, maxWidth: 150}}
/>
<ListItemText
primary={data.app.name}
style={{minWidth: 150, maxWidth: 150}}
/>
<ListItemText
primary={data.fields.map(data => {
return data.key
}).join(", ")}
style={{minWidth: 320, maxWidth: 320, overflow: "hidden"}}
/>
<ListItemText
primary={data.usage.length}
style={{minWidth: 320, maxWidth: 320, overflow: "hidden"}}
/>
<ListItemText>
<Button
style={{}}
variant="contained"
color="primary"
disabled={true}
onClick={() => {
setSelectedAuthentcation(data)
setSelectedAuthenticationModalOpen(true)
}}
>
Edit
</Button>
<Button
style={{}}
variant="contained"
color="primary"
disabled={true}
onClick={() => {
setSelectedAuthentcation(data)
setSelectedAuthenticationModalOpen(true)
}}
>
Delete
</Button>
</ListItemText>
</ListItem>
)
})}
</List>
</div>
: null
const environmentView = curTab === 2 ?
<div>
<h2>
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"
>
<Tab label="Users" />
<Tab label="App Authentication"/>
<Tab label="Environments"/>
<Tab label="Schedules"/>
</Tabs>
<div style={{marginBottom: 10}}/>
{authenticationView}
{usersView}
{environmentView}
{schedulesView}
@@ -771,6 +961,7 @@ const Admin = (props) => {
<div>
{modalView}
{editUserModal}
{editAuthenticationModal}
{data}
</div>
)
+293 -77
View File
@@ -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 ?
<div style={{marginTop: 15}}>
Authentication (reusable):
<Tooltip color="primary" title={"Add authentication option"} placement="top">
<Button color="primary" style={{}} variant="text" onClick={() => {
setAuthenticationModalOpen(true)
}}>
<AddIcon />
</Button>
</Tooltip>
</div>
: null}
{selectedAction.authentication.length > 0 ?
<div style={{marginTop: "20px"}}>
Authentication
<div style={{display: "flex"}}>
<Select
value={selectedAction.selectedAuthentication}
SelectDisplayProps={{
style: {
marginLeft: 10,
}
}}
fullWidth
onChange={(e) => {
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 => (
<MenuItem key={data.id} style={{backgroundColor: inputColor, color: "white"}} value={data}>
{data.label} - ({data.app.app_version})
</MenuItem>
))}
</Select>
{/*
<Button fullWidth style={{margin: "auto", marginTop: "10px",}} color="primary" variant="contained" onClick={() => setAuthenticationModalOpen(true)}>
AUTHENTICATE
</Button>
curaction.authentication = authenticationOptions
if (curaction.selectedAuthentication === null || curaction.selectedAuthentication === undefined || curaction.selectedAuthentication.length === "")
*/}
<Tooltip color="primary" title={"Add authentication option"} placement="top">
<Button color="primary" style={{}} variant="text" onClick={() => {
setAuthenticationModalOpen(true)
}}>
<AddIcon />
</Button>
</Tooltip>
</div>
</div>
: null}
{environments !== undefined && environments !== null && environments.length > 1 ?
<div style={{marginTop: "20px"}}>
Environment
<Select
@@ -2997,13 +3154,6 @@ const AngularWorkflow = (props) => {
</Select>
</div>
: null}
{/*requiresAuthentication ?
<div style={{marginTop: "20px"}}>
<Button fullWidth style={{margin: "auto", marginTop: "10px",}} color="primary" variant="contained" onClick={() => setAuthenticationModalOpen(true)}>
AUTHENTICATE
</Button>
</div>
: null*/}
<Divider style={{marginTop: "20px", height: "1px", width: "100%", backgroundColor: "rgb(91, 96, 100)"}}/>
<div style={{flex: "6", marginTop: "20px"}}>
<div style={{marginBottom: 5}}>
@@ -3033,7 +3183,7 @@ const AngularWorkflow = (props) => {
newActionname = newActionname.replace("_", " ")
newActionname = newActionname.charAt(0).toUpperCase()+newActionname.substring(1)
return (
<MenuItem style={{maxWidth: 400, overflowX: "hidden", backgroundColor: inputColor, color: "white"}} value={data.name}>
<MenuItem key={data.name} style={{maxWidth: 400, overflowX: "hidden", backgroundColor: inputColor, color: "white"}} value={data.name}>
{newActionname}
</MenuItem>
@@ -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 <div>
Insert your API token for {selectedApp.name}
<TextField
style={{backgroundColor: inputColor}}
InputProps={{
style:{
color: "white",
height: "50px",
fontSize: "1em",
},
}}
fullWidth
type="password"
color="primary"
placeholder="Bearer token"
onChange={(event) => {
setTmpVar(event.target.value)
}}
onBlur={() => {
selectedApp.authentication.parameters[0].value = tmpVar
setSelectedApp(selectedApp)
}}
/>
</div>
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 (
<div>
NOT IMPLEMENTED <div/>
Unknown auth: {currentAuth.scheme}
<DialogContent>
<a href="https://shuffler.io/docs/apps#authentication" style={{textDecoration: "none", color: "#f85a3e"}}>What is this?</a>
&nbsp;These are required fields for authenticating with TheHive
<div style={{marginTop: 15}}/>
{selectedApp.link.length > 0 ? <EndpointData /> : null}
Label (to remember it)
<TextField
style={{backgroundColor: inputColor}}
InputProps={{
style:{
color: "white",
marginLeft: "5px",
maxWidth: "95%",
height: "50px",
fontSize: "1em",
},
}}
fullWidth
color="primary"
placeholder={"Auth july 2020"}
onChange={(event) => {
authenticationOption.label = event.target.value
}}
/>
<Divider style={{marginTop: 15, marginBottom: 15}}/>
<div style={{}}/>
{selectedApp.authentication.parameters.map((data, index) => {
return (
<div key={index} style={{marginTop: 10}}>
{data.name}
<TextField
style={{backgroundColor: inputColor}}
InputProps={{
style:{
color: "white",
marginLeft: "5px",
maxWidth: "95%",
height: "50px",
fontSize: "1em",
},
}}
fullWidth
type={data.example !== undefined && data.example.includes("***") ? "password" : "text"}
color="primary"
placeholder={data.example}
onChange={(event) => {
authenticationOption.fields[data.name] = event.target.value
}}
/>
</div>
)
})}
</DialogContent>
<DialogActions>
<Button
style={{borderRadius: "0px"}}
onClick={() => {
setAuthenticationModalOpen(false)
}} color="primary">
Cancel
</Button>
<Button style={{borderRadius: "0px"}} onClick={() => {
handleSubmitCheck()
}} color="primary">
Submit
</Button>
</DialogActions>
</div>
)
}
@@ -5328,40 +5562,22 @@ const AngularWorkflow = (props) => {
// This whole part is redundant. Made it part of Arguments instead.
const authenticationModal = authenticationModalOpen ?
<Dialog modal
<Dialog
open={authenticationModalOpen}
onClose={() => {
setAuthenticationModalOpen(false)
setAppAuthentication({})
//setAuthenticationModalOpen(false)
}}
PaperProps={{
style: {
backgroundColor: surfaceColor,
color: "white",
minWidth: "800px",
minWidth: 600,
padding: 15,
},
}}
>
<DialogTitle><div style={{color: "white"}}>Authentication for {selectedApp.name}</div></DialogTitle>
<DialogContent>
<a href="https://shuffler.io/docs/apps#authentication" style={{textDecoration: "none", color: "#f85a3e"}}>What is this?</a>
<div />
{selectedApp.link.length > 0 ? <EndpointData /> : null}
<div style={{marginTop: 15, marginBottom: 15, }}/>
<AuthenticationData />
</DialogContent>
<DialogActions>
<Button
style={{borderRadius: "0px"}}
onClick={() => {
setAuthenticationModalOpen(false)
}} color="primary">
Cancel
</Button>
<Button style={{borderRadius: "0px"}} onClick={() => {setAuthenticationModalOpen(false)}} color="primary">
Submit
</Button>
</DialogActions>
<AuthenticationData app={selectedApp} />
</Dialog> : null
const loadedCheck = isLoaded && isLoggedIn && workflowDone ?
+1 -1
View File
@@ -289,8 +289,8 @@ const Apps = (props) => {
<Paper square style={paperAppStyle} onClick={() => {
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({})