From c1bfb159c63dbd8259204f80415d6abf9f0769d2 Mon Sep 17 00:00:00 2001 From: Frikky Date: Sun, 19 Nov 2023 18:03:53 +0100 Subject: [PATCH] Fixed oauth2, docs and popups for onboarding --- backend/app_sdk/app_base.py | 29 +--- frontend/src/components/AppSearchButtons.jsx | 33 +++-- frontend/src/components/Oauth2Auth.jsx | 99 +++++++++++-- frontend/src/components/SearchData.jsx | 138 +++++++----------- frontend/src/components/Searchfield.jsx | 16 +- .../src/components/WorkflowTemplatePopup.jsx | 7 +- frontend/src/views/AngularWorkflow.jsx | 19 ++- frontend/src/views/AppCreator.jsx | 113 ++++++++++---- frontend/src/views/Dashboard.jsx | 112 +++++++++----- frontend/src/views/Docs.jsx | 126 ++++------------ 10 files changed, 367 insertions(+), 325 deletions(-) diff --git a/backend/app_sdk/app_base.py b/backend/app_sdk/app_base.py index 1d75a027..84a0d9a5 100755 --- a/backend/app_sdk/app_base.py +++ b/backend/app_sdk/app_base.py @@ -3585,36 +3585,9 @@ class AppBase: }) break - - - - #thread = threading.Thread(target=func, args=(**params,)) - #thread.start() - - #thread.join(timeout) - - #if thread.is_alive(): - # # The thread is still running, so we need to stop it - # # You can handle this as needed, such as raising an exception - # timeout_handler() - - - #with Timeout(timeout): - # newres = func(**params) - # break - #except Timeout.Timeout as e: - # self.logger.info(f"[DEBUG] Timeout error: {e}") - # newres = json.dumps({ - # "success": False, - # "reason": "Timeout error within %d seconds. This typically happens if we can't reach the API you're trying to reach." % timeout, - # "exception": str(e), - # }) - - # break - except TypeError as e: newres = "" - self.logger.info(f"[DEBUG] Got exec type error: {e}") + self.logger.info(f"[ERROR] Got function exec type error: {e}") try: e = json.loads(f"{e}") except: diff --git a/frontend/src/components/AppSearchButtons.jsx b/frontend/src/components/AppSearchButtons.jsx index dbd2c055..553b7e41 100644 --- a/frontend/src/components/AppSearchButtons.jsx +++ b/frontend/src/components/AppSearchButtons.jsx @@ -47,8 +47,6 @@ const AppSearchButtons = (props) => { const [newSelectedApp, setNewSelectedApp] = useState(undefined) useEffect(() => { - console.log("AppSearchButtons: newSelectedApp: " + JSON.stringify(newSelectedApp)) - if (newSelectedApp !== undefined && setMissing != undefined) { console.log("AppSearchButtons: setMissing is defined!") @@ -131,9 +129,12 @@ const AppSearchButtons = (props) => { //setFrameworkLoaded(true) }) } + const icon = foundApp.large_image - console.log("index:", moreButton) - console.log("totalApps:", totalApps) + var foundAppImage = AppImage + if (foundApp.name !== undefined && foundApp.name !== null && !foundApp.name.includes(":default")) { + foundAppImage = foundApp.large_image + } let xsValue = 12; if (index === totalApps - 1 || index === totalApps - 2 || index === totalApps - 3 || index === totalApps - 4) { @@ -142,6 +143,8 @@ const AppSearchButtons = (props) => { if (index === totalApps - 5) { xsValue = 12; } + + // This is silly huh if (moreButton) { switch (index) { case totalApps - 1: @@ -159,7 +162,6 @@ const AppSearchButtons = (props) => { xsValue = 12; break; default: - // Handle other cases if needed } } @@ -221,10 +223,11 @@ const AppSearchButtons = (props) => { > { e.preventDefault(); setLocalSearchOpen(false) - setDefaultSearch("") + const submitDeletedApp = { "description": "", "id": "remove", @@ -233,15 +236,23 @@ const AppSearchButtons = (props) => { } setFrameworkItem(submitDeletedApp) setNewSelectedApp({}) + + if (setDefaultSearch !== undefined) { + setDefaultSearch("") + } + setTimeout(() => { - setDiscoveryData({}) + if (setDiscoveryData !== undefined) { + setDiscoveryData({}) + } + setFrameworkItem(submitDeletedApp) //setNewSelectedApp({}) }, 1000) //setAppName(discoveryData.cases.name) }} > - + @@ -283,12 +294,12 @@ const AppSearchButtons = (props) => { }} >
- {AppImage === undefined || AppImage === null || AppImage.length === 0 ? -
+ {foundAppImage === undefined || foundAppImage === null || foundAppImage.length === 0 ? +
: - + }
{ authenticationType.client_secret.length > 0 ); - const [clientId, setClientId] = React.useState( - defaultConfigSet ? authenticationType.client_id : "" - ); - const [clientSecret, setClientSecret] = React.useState( - defaultConfigSet ? authenticationType.client_secret : "" - ); + console.log("AUTH: ", authenticationType) + + const [clientId, setClientId] = React.useState(defaultConfigSet ? authenticationType.client_id : ""); + const [clientSecret, setClientSecret] = React.useState(defaultConfigSet ? authenticationType.client_secret : ""); + + const [username, setUsername] = React.useState(""); + const [password, setPassword] = React.useState(""); + const [oauthUrl, setOauthUrl] = React.useState(""); const [buttonClicked, setButtonClicked] = React.useState(false); - const [offlineAccess, setOfflineAccess] = React.useState(true); - const allscopes = authenticationType.scope !== undefined ? authenticationType.scope : []; + const allscopes = authenticationType.scope !== undefined && authenticationType.scope !== null ? authenticationType.scope : []; + const [selectedScopes, setSelectedScopes] = React.useState(allscopes !== null && allscopes !== undefined ? allscopes.length > 0 && allscopes.length <= 3 ? [allscopes[0]] : [] : []) - const [selectedScopes, setSelectedScopes] = React.useState(allscopes.length > 0 && allscopes.length <= 3 ? [allscopes[0]] : []) const [manuallyConfigure, setManuallyConfigure] = React.useState( defaultConfigSet ? false : true ); @@ -158,6 +159,7 @@ const AuthenticationOauth2 = (props) => { return null; } + const startOauth2Request = (admin_consent) => { // Admin consent also means to add refresh tokens console.log("Inside oauth2 request for app: ", selectedApp.name) @@ -319,6 +321,32 @@ const AuthenticationOauth2 = (props) => { "value": authenticationType.token_uri, }] + if (authenticationType.grant_type !== undefined && authenticationType.grant_type !== null && authenticationType.grant_type.length > 0) { + if (authenticationType.grant_type === "client_credentials") { + parsedFields.push({ + "key": "grant_type", + "value": authenticationType.grant_type, + }) + } else if (authenticationType.grant_type === "password") { + parsedFields.push({ + "key": "grant_type", + "value": authenticationType.grant_type, + }) + + parsedFields.push({ + "key": "username", + "value": username, + }) + + parsedFields.push({ + "key": "password", + "value": password, + }) + } else { + toast("Unknown grant type: " + authenticationType.grant_type) + } + } + const appAuthData = { "label": "OAuth2 for " + selectedApp.name, "app": { @@ -478,8 +506,6 @@ const AuthenticationOauth2 = (props) => { } return; - //do { - //} while ( }; authenticationOption.app.actions = []; @@ -647,7 +673,7 @@ const AuthenticationOauth2 = (props) => { )} - if (authButtonOnly === true) { + if (authButtonOnly === true && (authenticationType.grant_type === undefined || authenticationType.grant_type === null || authenticationType.grant_type === "")) { return autoAuthButton } @@ -872,8 +898,51 @@ const AuthenticationOauth2 = (props) => { //authenticationOption.label = event.target.value }} /> - {allscopes.length === 0 ? null : "Scopes (access rights)"} - {allscopes.length === 0 ? null : ( + + {authenticationType.grant_type !== "password" ? null : +
+ { + setUsername(event.target.value); + //authenticationOption.label = event.target.value + }} + /> + { + setPassword(event.target.value); + //authenticationOption.label = event.target.value + }} + /> +
+ } + + {allscopes === undefined || allscopes === null || allscopes.length === 0 ? null : "Scopes (access rights)"} + {allscopes === undefined || allscopes === null || allscopes.length === 0 ? null : (
{authenticationOption === "Oauth2" ? - + {/* - Delegated: The user will get a popup for access their personal data. - Application: Permissions are set by the app creator in the 3rd party platform. */} - { + setOauth2Type(e.target.value); + }} + value={oauth2Type} + style={{ + backgroundColor: inputColor, + color: "white", + height: "50px", + }} + > + {["delegated", "application"].map((data, index) => ( + + {data} + + ))} + +
+ + {oauth2Type === "application" ? +
+ Grant Type + + {["client_credentials", "password"].map((data, index) => ( + + {data} + + ))} + +
+ : null} : null} diff --git a/frontend/src/views/Dashboard.jsx b/frontend/src/views/Dashboard.jsx index f961076b..21c2d513 100755 --- a/frontend/src/views/Dashboard.jsx +++ b/frontend/src/views/Dashboard.jsx @@ -102,6 +102,8 @@ const UsecaseListComponent = (props) => { const [expandedItem, setExpandedItem] = useState(-1); const [inputUsecase, setInputUsecase] = useState({}); + const [prevSubcase, setPrevSubcase] = useState({}) + const [editing, setEditing] = useState(false); const [description, setDescription] = useState(""); const [video, setVideo] = useState(""); @@ -117,6 +119,42 @@ const UsecaseListComponent = (props) => { const [mitreTags, setMitreTags] = useState([]); + const parseUsecase = (subcase) => { + const srcdata = findSpecificApp(frameworkData, subcase.type) + const dstdata = findSpecificApp(frameworkData, subcase.last) + + if (srcdata !== undefined && srcdata !== null) { + subcase.srcimg = srcdata.large_image + subcase.srcapp = srcdata.name + } + + if (dstdata !== undefined && dstdata !== null) { + subcase.dstimg = dstdata.large_image + subcase.dstapp = dstdata.name + } + + return subcase + } + + useEffect(() => { + console.log("In frameworkData useEffect: frameworkData: ", frameworkData) + if (frameworkData === undefined || prevSubcase === undefined) { + return + } + + console.log("PAST!") + + var parsedUsecase = inputUsecase + const subcase = parseUsecase(prevSubcase) + + parsedUsecase.srcimg = subcase.srcimg + parsedUsecase.srcapp = subcase.srcapp + parsedUsecase.dstimg = subcase.dstimg + parsedUsecase.dstapp = subcase.dstapp + + setInputUsecase(parsedUsecase) + }, [frameworkData]) + const loadApps = () => { fetch(`${globalUrl}/api/v1/apps`, { method: "GET", @@ -163,35 +201,14 @@ const UsecaseListComponent = (props) => { if (keys === undefined || keys === null || keys.length === 0) { return null - } - - - const parseUsecase = (subcase) => { - //console.log("parseUsecase: ", subcase) - const srcdata = findSpecificApp(frameworkData, subcase.type) - const dstdata = findSpecificApp(frameworkData, subcase.last) - - if (srcdata !== undefined && srcdata !== null) { - subcase.srcimg = srcdata.large_image - subcase.srcapp = srcdata.name - } - - if (dstdata !== undefined && dstdata !== null) { - subcase.dstimg = dstdata.large_image - subcase.dstapp = dstdata.name - } - - return subcase - } + } + + // Timeout 50ms to delay it slightly const getUsecase = (subcase, index, subindex) => { subcase = parseUsecase(subcase) - - // Timeout 50ms to delay it slightly - //setTimeout(() => { - // setInputUsecase(subcase) - //}, 50) + setPrevSubcase(subcase) fetch(`${globalUrl}/api/v1/workflows/usecases/${escape(subcase.name.replaceAll(" ", "_"))}`, { method: "GET", @@ -214,8 +231,6 @@ const UsecaseListComponent = (props) => { if (responseJson.success === false) { parsedUsecase = subcase } else { - console.log("FOUND: ", JSON.parse(JSON.stringify(responseJson))) - parsedUsecase = responseJson parsedUsecase.srcimg = subcase.srcimg @@ -314,7 +329,7 @@ const UsecaseListComponent = (props) => { }) .catch((error) => { //toast(error.toString()); - //setFrameworkLoaded(true) + //setFrameworkLoaded(true) }) } @@ -416,15 +431,16 @@ const UsecaseListComponent = (props) => { return ( { + if (fixedName === "increase authentication") { + getUsecase(subcase, index, subindex) + return + } //setSelectedWorkflows([]) if (selectedItem) { } else { getUsecase(subcase, index, subindex) navigate(`/usecases?selected_object=${fixedName}`) - - //const newitem = removeParam("selected_object", cursearch); - //navigate(curpath + newitem) } }}> { @@ -594,11 +610,12 @@ const UsecaseListComponent = (props) => { > { - setExpandedItem(-1) - setExpandedIndex(-1) - setEditing(false) - setInputUsecase({}) + setExpandedItem(-1) + setExpandedIndex(-1) + setEditing(false) + setInputUsecase({}) }} > @@ -1172,13 +1189,28 @@ const Dashboard = (props) => { if (foundQuery !== null && foundQuery !== undefined) { setSelectedUsecaseCategory(foundQuery) - const newitem = removeParam("selected", cursearch); + const newitem = removeParam("selected", cursearch); navigate(curpath + newitem) } + const baseItem = document.getElementById("increase authentication") + if (baseItem !== undefined && baseItem !== null) { + baseItem.click() + + // Find close window button -> go to top + const foundButton = document.getElementById("close_selection") + if (foundButton !== undefined && foundButton !== null) { + foundButton.click() + } + + // Scroll back to top + window.scrollTo(0, 0) + } + const foundQuery2 = params["selected_object"] if (foundQuery2 !== null && foundQuery2 !== undefined) { - //console.log("Got selected_object: ", foundQuery2) + // Take a random object, quickly click it, then go to this one + // Something is weird with loading apps without it const queryName = foundQuery2.toLowerCase().replaceAll("_", " ") // Waiting a bit for it to render @@ -1198,7 +1230,7 @@ const Dashboard = (props) => { } else { //console.log("Couldn't find item with name ", queryName) } - }, 100); + }, 1000); } } @@ -1251,6 +1283,7 @@ const Dashboard = (props) => { }) } + const getAvailableWorkflows = () => { fetch(globalUrl + "/api/v1/workflows", { method: "GET", @@ -1385,8 +1418,7 @@ const Dashboard = (props) => { useEffect(() => { getAvailableWorkflows() - getFramework() - //fetchUsecases() + getFramework() }, []); const fetchdata = (stats_id) => { diff --git a/frontend/src/views/Docs.jsx b/frontend/src/views/Docs.jsx index ba56b835..350853ee 100755 --- a/frontend/src/views/Docs.jsx +++ b/frontend/src/views/Docs.jsx @@ -1,13 +1,11 @@ import React, { useEffect, useState } from "react"; -import ReactMarkdown from "react-markdown"; +import Markdown from 'react-markdown' + import { BrowserView, MobileView } from "react-device-detect"; import { useParams, useNavigate, Link } from "react-router-dom"; import { isMobile } from "react-device-detect"; import theme from '../theme.jsx'; -import remarkGfm from 'remark-gfm' -import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight'; -import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import { Grid, @@ -29,6 +27,8 @@ import { import { Link as LinkIcon, Edit as EditIcon, + KeyboardArrowRight as KeyboardArrowRightIcon, + ExpandMore as ExpandMoreIcon, } from "@mui/icons-material"; const Body = { @@ -166,8 +166,11 @@ const Docs = (defaultprops) => { }) .then((response) => response.json()) .then((responseJson) => { - if (responseJson.success) { - setData(responseJson.reason); + if (responseJson.success && responseJson.reason !== undefined) { + // Find tags and translate them into ![]() format + const imgRegex = / { }; function OuterLink(props) { - console.log("Link: ", props.href) if (props.href.includes("http") || props.href.includes("mailto")) { return ( { } function CodeHandler(props) { - console.log("PROPS: ", props) + //console.log("Codehandler PROPS: ", props) const propvalue = props.value !== undefined && props.value !== null ? props.value : props.children !== undefined && props.children !== null && props.children.length > 0 ? props.children[0] : "" @@ -603,7 +605,6 @@ const Docs = (defaultprops) => { const [hover, setHover] = useState(false); - console.log("Link: ", link) if (link === undefined || link === null) { return null } @@ -681,60 +682,20 @@ const Docs = (defaultprops) => { Organize. Whether an organization of 1000 or 1, management tools are necessary. In Shuffle we offer full user management, MFA and single-signon options, multi-tenancy and a lot more - for free!
- - {/* - - {list.map((data, index) => { - const item = data.name; - if (item === undefined) { - return null; - } - - const path = "/docs/" + item; - const newname = - item.charAt(0).toUpperCase() + - item.substring(1).split("_").join(" ").split("-").join(" "); - - const itemMatching = props.match.params.key === undefined ? false : - props.match.params.key.toLowerCase() === item.toLowerCase(); - - return ( - - - - ) - })} - - */} - - {/* - { - console.log("Change: ", event.target.value) - }} - /> - */}
+ const markdownComponents = { + img: Img, + code: CodeHandler, + h1: Heading, + h2: Heading, + h3: Heading, + h4: Heading, + h5: Heading, + h6: Heading, + a: OuterLink, + } + // PostDataBrowser Section const postDataBrowser = list === undefined || list === null ? null : ( @@ -812,32 +773,22 @@ const Docs = (defaultprops) => { mainpageInfo :
- {data} - +
}
); - // remarkPlugins={[remarkGfm]} const mobileStyle = { color: "white", @@ -849,6 +800,7 @@ const Docs = (defaultprops) => { flexDirection: "column", }; + const postDataMobile = list === undefined || list === null ? null : (
@@ -899,18 +851,8 @@ const Docs = (defaultprops) => { mainpageInfo :
- { }} > {data} - +
} {
); - //const imageModal = - // - // {imageModal} - // Padding and zIndex etc set because of footer in cloud. const loadedCheck = ( -
+
{postDataBrowser} {postDataMobile}