Added authenticationSSO file

This commit is contained in:
frikky
2021-09-23 15:31:04 +02:00
parent 137df9f20a
commit 58524258c7
9 changed files with 354 additions and 31 deletions
+2 -2
View File
@@ -2,7 +2,7 @@ module shuffle
go 1.13 go 1.13
replace github.com/frikky/shuffle-shared => ../../../../git/shuffle-shared //replace github.com/frikky/shuffle-shared => ../../../../git/shuffle-shared
//replace github.com/frikky/kin-openapi => ../../../../git/kin-openapi //replace github.com/frikky/kin-openapi => ../../../../git/kin-openapi
@@ -23,7 +23,7 @@ require (
github.com/docker/go-units v0.4.0 // indirect github.com/docker/go-units v0.4.0 // indirect
github.com/elastic/go-elasticsearch/v7 v7.13.1 // indirect github.com/elastic/go-elasticsearch/v7 v7.13.1 // indirect
github.com/frikky/kin-openapi v0.39.0 github.com/frikky/kin-openapi v0.39.0
github.com/frikky/shuffle-shared v0.0.98 github.com/frikky/shuffle-shared v0.0.99
github.com/fsouza/go-dockerclient v1.7.2 github.com/fsouza/go-dockerclient v1.7.2
github.com/ghodss/yaml v1.0.0 github.com/ghodss/yaml v1.0.0
github.com/go-git/go-billy/v5 v5.0.0 github.com/go-git/go-billy/v5 v5.0.0
+3 -3
View File
@@ -1,7 +1,7 @@
version: '3' version: '3'
services: services:
frontend: frontend:
build: ./frontend #build: ./frontend
image: ghcr.io/frikky/shuffle-frontend:nightly image: ghcr.io/frikky/shuffle-frontend:nightly
container_name: shuffle-frontend container_name: shuffle-frontend
hostname: shuffle-frontend hostname: shuffle-frontend
@@ -16,7 +16,7 @@ services:
depends_on: depends_on:
- backend - backend
backend: backend:
build: ./backend #build: ./backend
image: ghcr.io/frikky/shuffle-backend:nightly image: ghcr.io/frikky/shuffle-backend:nightly
container_name: shuffle-backend container_name: shuffle-backend
hostname: ${BACKEND_HOSTNAME} hostname: ${BACKEND_HOSTNAME}
@@ -62,7 +62,7 @@ services:
- SHUFFLE_PASS_WORKER_PROXY=${SHUFFLE_PASS_WORKER_PROXY} - SHUFFLE_PASS_WORKER_PROXY=${SHUFFLE_PASS_WORKER_PROXY}
- SHUFFLE_PASS_APP_PROXY=${SHUFFLE_PASS_APP_PROXY} - SHUFFLE_PASS_APP_PROXY=${SHUFFLE_PASS_APP_PROXY}
- SHUFFLE_ORBORUS_EXECUTION_TIMEOUT=600 - SHUFFLE_ORBORUS_EXECUTION_TIMEOUT=600
- SHUFFLE_ORBORUS_EXECUTION_CONCURRENCY=15 - SHUFFLE_ORBORUS_EXECUTION_CONCURRENCY=5
- CLEANUP=${SHUFFLE_CONTAINER_AUTO_CLEANUP} - CLEANUP=${SHUFFLE_CONTAINER_AUTO_CLEANUP}
restart: unless-stopped restart: unless-stopped
opensearch: opensearch:
File diff suppressed because one or more lines are too long
+44 -5
View File
@@ -52,6 +52,7 @@ const Admin = (props) => {
const [selectedNamespace, setSelectedNamespace] = React.useState("default") const [selectedNamespace, setSelectedNamespace] = React.useState("default")
const [fileNamespaces, setFileNamespaces] = React.useState([]); const [fileNamespaces, setFileNamespaces] = React.useState([]);
const [selectedUser, setSelectedUser] = React.useState({}) const [selectedUser, setSelectedUser] = React.useState({})
const [newUsername, setNewUsername] = React.useState("");
const [newPassword, setNewPassword] = React.useState(""); const [newPassword, setNewPassword] = React.useState("");
const [selectedUserModalOpen, setSelectedUserModalOpen] = React.useState(false) const [selectedUserModalOpen, setSelectedUserModalOpen] = React.useState(false)
const [selectedAuthentication, setSelectedAuthentication] = React.useState({}) const [selectedAuthentication, setSelectedAuthentication] = React.useState({})
@@ -1201,6 +1202,7 @@ const Admin = (props) => {
alert.error("Failed setting user: " + responseJson.reason) alert.error("Failed setting user: " + responseJson.reason)
} else { } else {
alert.success("Set the user field " + field + " to " + value) alert.success("Set the user field " + field + " to " + value)
setSelectedUserModalOpen(false)
} }
}) })
.catch(error => { .catch(error => {
@@ -1340,6 +1342,44 @@ const Admin = (props) => {
> >
<DialogTitle><span style={{ color: "white" }}><EditIcon style={{marginTop: 5}}/> Editing {selectedUser.username}</span></DialogTitle> <DialogTitle><span style={{ color: "white" }}><EditIcon style={{marginTop: 5}}/> Editing {selectedUser.username}</span></DialogTitle>
<DialogContent> <DialogContent>
{isCloud ?
null
:
<div style={{ display: "flex" }}>
<TextField
style={{ marginTop: 0, backgroundColor: theme.palette.inputColor, flex: 3 , marginRight: 10,}}
InputProps={{
style: {
height: 50,
color: "white",
},
}}
color="primary"
required
fullWidth={true}
placeholder="New username"
type="text"
id="standard-required"
autoComplete="username"
margin="normal"
variant="outlined"
defaultValue={selectedUser.username}
onChange={e => {
setNewUsername(e.target.value)
}}
/>
<Button
style={{ maxHeight: 50, flex: 1 }}
variant="outlined"
color="primary"
onClick={() => {
setUser(selectedUser.id, "username", newUsername)
}}
>
Submit
</Button>
</div>
}
{isCloud ? {isCloud ?
null null
: :
@@ -1626,7 +1666,7 @@ const Admin = (props) => {
</IconButton> </IconButton>
</Tooltip> </Tooltip>
{selectedOrganization.name.length > 0 ? {selectedOrganization.name.length > 0 ?
<OrgHeader setSelectedOrganization={setSelectedOrganization} globalUrl={globalUrl} selectedOrganization={selectedOrganization}/> <OrgHeader userdata={userdata} setSelectedOrganization={setSelectedOrganization} globalUrl={globalUrl} selectedOrganization={selectedOrganization}/>
: :
<div style={{paddingTop: 250, width: 250, margin: "auto", textAlign: "center"}}> <div style={{paddingTop: 250, width: 250, margin: "auto", textAlign: "center"}}>
<CircularProgress /> <CircularProgress />
@@ -2069,10 +2109,9 @@ const Admin = (props) => {
value={data.role} value={data.role}
fullWidth fullWidth
onChange={(e) => { onChange={(e) => {
console.log("VALUE: ", e.target.value) console.log("VALUE: ", e.target.value)
setUser(data.id, "role", e.target.value)
setUser(data.id, "role", e.target.value) }}
}}
style={{ backgroundColor: theme.palette.surfaceColor, color: "white", height: "50px" }} style={{ backgroundColor: theme.palette.surfaceColor, color: "white", height: "50px" }}
> >
<MenuItem style={{ backgroundColor: theme.palette.inputColor, color: "white" }} value={"admin"}> <MenuItem style={{ backgroundColor: theme.palette.inputColor, color: "white" }} value={"admin"}>
+1 -1
View File
@@ -8186,7 +8186,7 @@ const AngularWorkflow = (props) => {
} }
} }
if (data.action.app_name === "Shuffle Tools") { if (data.action.app_name === "Shuffle Tools" && data.action.id !== undefined && cy !== undefined) {
//console.log("APP (TOOLS): ", data.action) //console.log("APP (TOOLS): ", data.action)
const nodedata = cy.getElementById(data.action.id).data() const nodedata = cy.getElementById(data.action.id).data()
+99 -11
View File
@@ -1146,8 +1146,37 @@ const AppCreator = (props) => {
if (queryitem.name.toLowerCase() == "url") { if (queryitem.name.toLowerCase() == "url") {
console.log(item.name+" uses a bad query: url") console.log(item.name+" uses a bad query: url")
skipped = true continue
break //skipped = true
//break
}
if (queryitem.name.toLowerCase() == "url"
|| queryitem.name.toLowerCase() == "body"
|| queryitem.name.toLowerCase() == "queries"
|| queryitem.name.toLowerCase() == "headers"
|| queryitem.name.includes("[")
|| queryitem.name.includes("]")
|| queryitem.name.includes("{")
|| queryitem.name.includes("}")
|| queryitem.name.includes("(")
|| queryitem.name.includes(")")
|| queryitem.name.includes("!")
|| queryitem.name.includes("@")
|| queryitem.name.includes("#")
|| queryitem.name.includes("$")
|| queryitem.name.includes("%")
|| queryitem.name.includes("^")
|| queryitem.name.includes("&")
|| queryitem.name.includes(":")
|| queryitem.name.includes(";")
|| queryitem.name.includes("<")
|| queryitem.name.includes(">")
|| queryitem.name.includes("\"")
|| queryitem.name.includes("\'")
) {
console.log(item.name+" error: uses a bad query - not adding: ", queryitem.name)
continue
} }
var newitem = { var newitem = {
@@ -1344,10 +1373,13 @@ const AppCreator = (props) => {
return return
} }
var newparamName = parameterName.replaceAll("\"", "")
newparamName = newparamName.replaceAll("\'", "")
data.components.securitySchemes["ApiKeyAuth"] = { data.components.securitySchemes["ApiKeyAuth"] = {
"type": "apiKey", "type": "apiKey",
"in": parameterLocation.toLowerCase(), "in": parameterLocation.toLowerCase(),
"name": parameterName, "name": newparamName,
} }
} else if (authenticationOption === "Bearer auth") { } else if (authenticationOption === "Bearer auth") {
data.components.securitySchemes["BearerAuth"] = { data.components.securitySchemes["BearerAuth"] = {
@@ -1367,13 +1399,16 @@ const AppCreator = (props) => {
"scheme": "basic", "scheme": "basic",
} }
} else if (authenticationOption === "Oauth2") { } else if (authenticationOption === "Oauth2") {
var newparamName = parameterName.replaceAll("\"", "")
newparamName = newparamName.replaceAll("\'", "")
//parameterName, parameterValue, revocationUrl //parameterName, parameterValue, revocationUrl
data.components.securitySchemes["Oauth2"] = { data.components.securitySchemes["Oauth2"] = {
"type": "oauth2", "type": "oauth2",
"description": "Oauth2.0 authorizationCode authentication", "description": "Oauth2.0 authorizationCode authentication",
"flow": { "flow": {
"authorizationCode": { "authorizationCode": {
"authorizationUrl": parameterName, "authorizationUrl": newparamName,
"tokenUrl": parameterLocation, "tokenUrl": parameterLocation,
"refreshUrl": refreshUrl, "refreshUrl": refreshUrl,
"scopes": [], "scopes": [],
@@ -1769,8 +1804,10 @@ const AppCreator = (props) => {
margin="normal" margin="normal"
variant="outlined" variant="outlined"
value={parameterName} value={parameterName}
helperText={<span style={{color:"white", marginBottom: "2px",}}>Can't be empty. Can't contain any of the following characters: !#$%&'^+-._~|]+$</span>} helperText={<span style={{color:"white", marginBottom: "2px",}}>Can't be empty. Can't contain any of the following characters: !#$%&'^"+-._~|]+$</span>}
onChange={e => setParameterName(e.target.value)} onChange={e => {
setParameterName(e.target.value)
}}
InputProps={{ InputProps={{
classes: { classes: {
notchedOutline: classes.notchedOutline, notchedOutline: classes.notchedOutline,
@@ -1954,11 +1991,60 @@ const AppCreator = (props) => {
} }
} }
const HandleIndividualChip = (props) => {
const { chipData, index } = props;
const [chipRequired, setChipRequired] = useState(false)
return (
<Tooltip title={chipRequired ? "Make not required" : "Make required"}>
<Chip
style={{
backgroundColor: chipRequired ? "#f86a3e" : "#3d3f43",
height: 30,
margin: 3,
paddingLeft: 5,
paddingRight: 5,
height: 28,
cursor: "pointer",
borderColor: "#3d3f43",
color: "white",
}}
label={chipData}
onClick={() => {
console.log("CLICK: ", chipData)
setChipRequired(!chipRequired)
}}
/>
</Tooltip>
)
}
const SetExtraBodyField = (props) => {
const { extraBodyFields } = props;
if (extraBodyFields === undefined || extraBodyFields === null) {
return null
}
//const parsedlist = extraBodyFields.join(", ")
//console.log("LIST: ", parsedlist)
return (
<span>
{extraBodyFields.map((data, index) => {
return (
<HandleIndividualChip key={index} chipData={data} />
)
})}
</span>
)
}
const bodyInfo = actionBodyRequest.includes(currentActionMethod) ? const bodyInfo = actionBodyRequest.includes(currentActionMethod) ?
<div style={{marginTop: 10}}> <div style={{marginTop: 10}}>
<b>Request Body</b>: {extraBodyFields.length > 0 ? <b>Request Body</b>: {extraBodyFields.length > 0 ?
<Typography style={{display: "inline-block"}}> <Typography style={{display: "inline-block"}}>
Variables: {extraBodyFields.join(", ")} <SetExtraBodyField extraBodyFields={extraBodyFields}/>
</Typography> </Typography>
: :
<Typography style={{display: "inline-block"}}> <Typography style={{display: "inline-block"}}>
@@ -2988,10 +3074,13 @@ const AppCreator = (props) => {
</Button> </Button>
: null} : null}
</div> </div>
<h2>Actions {actionAmount > 0 ? <span>({actionAmount} / {filteredActions.length})</span> : null}</h2> <Typography variant="h6">Actions {actionAmount > 0 ? <span>({actionAmount} / {filteredActions.length})</span> : null}</Typography>
<Typography variant="body2" style={{marginTop: 10, marginBottom: 10}}>
Actions are the tasks performed by an app - usually single URL paths for REST API's.
</Typography>
{projectCategories !== undefined && projectCategories !== null && projectCategories.length > 1 ? {projectCategories !== undefined && projectCategories !== null && projectCategories.length > 1 ?
<div style={{marginTop: 5, marginBottom: 5}}> <div style={{marginTop: 10, marginBottom: 10}}>
{projectCategories.map((tag, index) => { {projectCategories.map((tag, index) => {
const newname = tag.charAt(0).toUpperCase() + tag.slice(1) const newname = tag.charAt(0).toUpperCase() + tag.slice(1)
return ( return (
@@ -3000,7 +3089,7 @@ const AppCreator = (props) => {
style={{ style={{
backgroundColor: tag === selectedCategory ? "#f86a3e" : "#3d3f43", backgroundColor: tag === selectedCategory ? "#f86a3e" : "#3d3f43",
height: 30, height: 30,
marginRight: 5, margin: 3,
paddingLeft: 5, paddingLeft: 5,
paddingRight: 5, paddingRight: 5,
height: 28, height: 28,
@@ -3037,7 +3126,6 @@ const AppCreator = (props) => {
})} })}
</div> </div>
: null} : null}
Actions are the tasks performed by an app - usually single URL paths for REST API's.
<div> <div>
{loopActions} {loopActions}
<div style={{display: "flex"}}> <div style={{display: "flex"}}>
+2 -2
View File
@@ -243,9 +243,9 @@ const Docs = (props) => {
const path = "/docs/"+item const path = "/docs/"+item
const newname = item.charAt(0).toUpperCase()+item.substring(1).split("_").join(" ").split("-").join(" ") const newname = item.charAt(0).toUpperCase()+item.substring(1).split("_").join(" ").split("-").join(" ")
return ( return (
<li key={index} style={{marginTop: 15,}}> <li key={index} style={{marginTop: 10,}}>
<Link key={index} style={hrefStyle} to={path} onClick={() => {fetchDocs(item)}}> <Link key={index} style={hrefStyle} to={path} onClick={() => {fetchDocs(item)}}>
<Typography variant="h6"><b>{newname}</b></Typography> <Typography style={{color: props.match.params.key.toLowerCase() === item.toLowerCase() ? "#f86a3e" : "inherit"}} variant="body1"><b>> {newname}</b></Typography>
</Link> </Link>
</li> </li>
) )
+143
View File
@@ -0,0 +1,143 @@
import React, {useRef, useState, useEffect, useLayoutEffect} from 'react';
import { Typography, CircularProgress } from '@material-ui/core';
const SetAuthentication = (props) => {
const { globalUrl, isLoggedIn, isLoaded, userdata } = props;
const [firstRequest, setFirstRequest] = useState(true)
const [finished, setFinished] = useState(false)
const [response, setResponse] = useState("")
const [failed, setFailed] = useState(false)
if (firstRequest) {
setFirstRequest(false)
//code
//session_state
const urlSearchParams = new URLSearchParams(window.location.search);
const params = Object.fromEntries(urlSearchParams.entries());
const authenticationStore = []
var appAuthData = {
"label": "",
"app": {
"name": "",
"id": "",
"app_version": "",
},
"fields": [],
"type": "oauth2",
}
if (window !== undefined && window !== null) {
console.log(window.location)
appAuthData.fields.push({"key": "redirect_uri", "value": window.location.origin+window.location.pathname})
}
if (params.code !== undefined && params.code !== null) {
appAuthData.fields.push({"key": "code", "value": params.code})
}
if (params.session_state !== undefined && params.session_state !== null) {
appAuthData.fields.push({"key": "session_state", "value": params.session_state})
}
if (params.state !== undefined && params.state !== null) {
const paramsplit = params.state.split("&")
console.log(paramsplit)
for (var key in paramsplit) {
const query = paramsplit[key].split("=")
console.log(query)
if (query.length !== 2) {
console.log("INVALID QUERY: ", query)
continue
}
if (query[0] === "workflow_id") {
appAuthData.reference_workflow = query[1]
}
if (query[0] === "reference_action_id") {
//appAuthData.ReferenceWorkflow = query[1]
}
if (query[0] === "app_name") {
appAuthData.app.name = query[1]
appAuthData.label = "Oauth2 for "+query[1]
}
if (query[0] === "app_id") {
appAuthData.app.id = query[1]
}
if (query[0] === "app_version") {
appAuthData.app.app_version = query[1]
}
if (query[0] === "authentication_url") {
appAuthData.fields.push({"key": "authentication_url", "value": query[1]})
}
if (query[0] === "scope") {
appAuthData.fields.push({"key": "scope", "value": query[1]})
}
if (query[0] === "client_id") {
appAuthData.fields.push({"key": "client_id", "value": query[1]})
}
if (query[0] === "client_secret") {
appAuthData.fields.push({"key": "client_secret", "value": query[1]})
}
}
}
console.log(appAuthData)
fetch(globalUrl+"/api/v1/apps/authentication", {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
credentials: "include",
body: JSON.stringify(appAuthData),
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for oauth2 authentication")
setFailed(true)
}
return response.json()
})
.then((responseJson) => {
//setUserSettings(responseJson)
console.log("Resp: ", responseJson)
setFinished(true)
setResponse(responseJson.reason)
setTimeout(() => {
window.close()
}, 1000)
})
.catch(error => {
console.log(error)
});
}
return (
<div style={{width: 1000, margin: "auto", itemAlign: "center",}}>
<Typography variant="h6" style={{marginLeft: "auto", marginRight: "auto", marginTop: 200, }}>
{!finished ? <CircularProgress /> : "DONE WITH AUTH - this will close soon!!"}
<div />
{failed ? "Failed setup. Error: " : ""} {response}
</Typography>
</div>
)
}
export default SetAuthentication;
+55 -4
View File
@@ -354,6 +354,8 @@ const Workflows = (props) => {
const [downloadUrl, setDownloadUrl] = React.useState("https://github.com/frikky/shuffle-workflows") const [downloadUrl, setDownloadUrl] = React.useState("https://github.com/frikky/shuffle-workflows")
const [downloadBranch, setDownloadBranch] = React.useState("master") const [downloadBranch, setDownloadBranch] = React.useState("master")
const [loadWorkflowsModalOpen, setLoadWorkflowsModalOpen] = React.useState(false) const [loadWorkflowsModalOpen, setLoadWorkflowsModalOpen] = React.useState(false)
const [exportModalOpen, setExportModalOpen] = React.useState(false)
const [exportData, setExportData] = React.useState("");
const [modalOpen, setModalOpen] = React.useState(false); const [modalOpen, setModalOpen] = React.useState(false);
const [newWorkflowName, setNewWorkflowName] = React.useState(""); const [newWorkflowName, setNewWorkflowName] = React.useState("");
@@ -473,6 +475,50 @@ const Workflows = (props) => {
findWorkflow(newfilters) findWorkflow(newfilters)
} }
const exportVerifyModal = exportModalOpen ?
<Dialog
open={exportModalOpen}
onClose={() => {
setExportModalOpen(false)
setSelectedWorkflow({})
}}
PaperProps={{
style: {
backgroundColor: surfaceColor,
color: "white",
minWidth: 500,
padding: 30,
},
}}
>
<DialogTitle style={{marginBottom: 0, }}>
<div style={{textAlign: "center", color: "rgba(255,255,255,0.9)"}}>
Want to auto-sanitize this workflow before exporting?
</div>
</DialogTitle>
<DialogContent style={{color: "rgba(255,255,255,0.65)", textAlign: "center"}}>
<Typography variant="body1" style={{}}>
This will make potentially sensitive fields such as username, password, url etc. empty
</Typography>
<Button variant="contained" style={{marginTop: 20, }} onClick={() => {
setExportModalOpen(false)
exportWorkflow(exportData, true)
setExportData("")
}} color="primary">
Yes
</Button>
<Button style={{marginTop: 20, }} onClick={() => {
setExportModalOpen(false)
exportWorkflow(exportData, false)
setExportData("")
}} color="primary">
No
</Button>
</DialogContent>
</Dialog>
: null
const publishModal = publishModalOpen ? const publishModal = publishModalOpen ?
<Dialog <Dialog
open={publishModalOpen} open={publishModalOpen}
@@ -771,7 +817,7 @@ const Workflows = (props) => {
const exportAllWorkflows = () => { const exportAllWorkflows = () => {
for (var key in workflows) { for (var key in workflows) {
exportWorkflow(workflows[key]) exportWorkflow(workflows[key], false)
} }
} }
@@ -890,10 +936,13 @@ const Workflows = (props) => {
return data return data
} }
const exportWorkflow = (data) => { const exportWorkflow = (data, sanitize) => {
data = JSON.parse(JSON.stringify(data)) data = JSON.parse(JSON.stringify(data))
let exportFileDefaultName = data.name+'.json'; let exportFileDefaultName = data.name+'.json';
data = sanitizeWorkflow(data)
if (sanitize === true) {
data = sanitizeWorkflow(data)
}
//return //return
// Add correct ID's for triggers // Add correct ID's for triggers
@@ -1269,7 +1318,8 @@ const Workflows = (props) => {
{"Duplicate Workflow"} {"Duplicate Workflow"}
</MenuItem> </MenuItem>
<MenuItem style={{backgroundColor: inputColor, color: "white"}} onClick={() => { <MenuItem style={{backgroundColor: inputColor, color: "white"}} onClick={() => {
exportWorkflow(data) setExportModalOpen(true)
setExportData(data)
setOpen(false) setOpen(false)
}} key={"export"}> }} key={"export"}>
<GetAppIcon style={{marginLeft: 0, marginRight: 8}}/> <GetAppIcon style={{marginLeft: 0, marginRight: 8}}/>
@@ -2323,6 +2373,7 @@ const Workflows = (props) => {
</Dropzone> </Dropzone>
{modalView} {modalView}
{deleteModal} {deleteModal}
{exportVerifyModal}
{publishModal} {publishModal}
{workflowDownloadModalOpen} {workflowDownloadModalOpen}
</div> </div>