import React, { useState, useEffect } from 'react';
import theme from '../theme.jsx';
import CytoscapeComponent from 'react-cytoscapejs';
import frameworkStyle from '../frameworkStyle.jsx';
import { v4 as uuidv4 } from "uuid";
import AppSearch from '../components/Appsearch.jsx';
import PaperComponent from "../components/PaperComponent.jsx"
import { usecaseTypes } from "../components/UsecaseSearch.jsx"
import SuggestedWorkflows from "../components/SuggestedWorkflows.jsx"
import { securityFramework} from "../components/LandingpageUsecases.jsx";
import {
Paper,
Typography,
Divider,
IconButton,
Badge,
CircularProgress,
Tooltip,
Dialog,
Chip,
Avatar,
Button,
} from "@mui/material";
import {
Close as CloseIcon,
Delete as DeleteIcon,
} from "@mui/icons-material";
import * as edgehandles from "cytoscape-edgehandles";
import cytoscape from "cytoscape";
import { toast } from 'react-toastify';
cytoscape.use(edgehandles);
export const findSpecificApp = (framework, inputcategory) => {
// Get the frameworkinfo for the org and fill in
if (framework === undefined || framework === null) {
console.log("findSpecificApp: framework is null")
return null
}
if (inputcategory === undefined || inputcategory === null) {
console.log("findSpecificApp: category is null")
return null
}
const category = inputcategory.toLowerCase().split(":")[0].trim()
//console.log("findSpecificApp: ", category, framework)
if (category === "edr" || category === "eradication" || category === "edr & av") {
if (framework["EDR & AV"] !== undefined && framework["EDR & AV"].name !== undefined) {
return framework["EDR & AV"]
}
return {
name: "EDR :default",
large_image: parsedDatatypeImages["EDR & AV"],
count: 0,
description: "",
id: "",
}
} else if (category === "communication" || category === "comms") {
if (framework["Comms"] !== undefined && framework["Comms"].name !== undefined) {
return framework["Comms"]
}
return {
name: "COMMS :default",
large_image: parsedDatatypeImages["COMMS"],
count: 0,
description: "",
id: "",
}
} else if (category === "email") {
if (framework["Email"] !== undefined && framework["Email"].name !== undefined) {
return framework["Email"]
}
return {
name: "COMMS :default",
large_image: parsedDatatypeImages["COMMS"],
count: 0,
description: "",
id: "",
}
} else if (category === "assets") {
if (framework["Assets"] !== undefined && framework["Assets"].name !== undefined) {
return framework["Assets"]
}
return {
name: "ASSETS :default",
large_image: parsedDatatypeImages["ASSETS"],
count: 0,
description: "",
id: "",
}
} else if (category === "cases") {
if (framework["Cases"] !== undefined && framework["Cases"].name !== undefined) {
return framework["Cases"]
}
return {
name: "CASES :default",
large_image: parsedDatatypeImages["CASES"],
count: 0,
description: "",
id: "",
}
} else if (category === "iam") {
if (framework["IAM"] !== undefined && framework["IAM"].name !== undefined) {
return framework["IAM"]
}
return {
name: "EDR :default",
large_image: parsedDatatypeImages["EDR & AV"],
count: 0,
description: "",
id: "",
}
} else if (category === "network") {
if (framework["Network"] !== undefined && framework["Network"].name !== undefined) {
return framework["Network"]
}
return {
name: "Network :default",
large_image: parsedDatatypeImages["NETWORK"],
count: 0,
description: "",
id: "",
}
} else if (category === "intel") {
if (framework["Intel"] !== undefined && framework["Intel"].name !== undefined) {
return framework["Intel"]
}
return {
name: "INTEL :default",
large_image: parsedDatatypeImages["INTEL"],
count: 0,
description: "",
id: "",
}
} else if (category === "siem") {
if (framework["SIEM"] !== undefined && framework["SIEM"].name !== undefined) {
return framework["SIEM"]
}
return {
name: "SIEM :default",
large_image: parsedDatatypeImages["SIEM"],
count: 0,
description: "",
id: "",
}
} else {
console.log("findSpecificApp: unknown category: ", category)
}
return null
}
const svgSize = "40px"
export const parsedDatatypeImages = {
"SIEM": encodeURI(`data:image/svg+xml;utf-8,`),
"CASES": encodeURI(`data:image/svg+xml;utf-8,`),
"EDR & AV": encodeURI(`data:image/svg+xml;utf-8,`),
"INTEL": encodeURI(`data:image/svg+xml;utf-8,`),
"COMMS": encodeURI(`data:image/svg+xml;utf-8,`),
"NETWORK": encodeURI(`data:image/svg+xml;utf-8,`),
"INTEL": encodeURI(`data:image/svg+xml;utf-8,`),
"ASSETS": encodeURI(`data:image/svg+xml;utf-8,`),
"IAM": encodeURI(`data:image/svg+xml;utf-8,`),
}
export const usecases = {
"None": {
"manual": [],
"automated": [],
},
"Phishing": {
"manual": [],
"automated": [
{
"source": "BOTTOM_LEFT",
"target": "COMMS",
"description": "Email received",
"human": false,
},
{
"source": "COMMS",
"target": "SHUFFLE",
"human": false,
},
{
"source": "SHUFFLE",
"target": "CASES",
"human": false,
},
{
"source": "INTEL",
"target": "SHUFFLE",
"human": false,
},
{
"source": "ASSETS",
"target": "SHUFFLE",
"human": false,
},
{
"source": "SIEM",
"target": "SHUFFLE",
"human": false,
},
{
"source": "SHUFFLE",
"target": "INTEL",
"human": false,
},
{
"source": "SHUFFLE",
"target": "COMMS",
"human": false,
},
{
"source": "CASES",
"target": "EDR & AV",
"human": true,
},
]},
"Exploits": {
"manual": [],
"automated": [
{
"source": "TOP_LEFT",
"target": "NETWORK",
"description": "Exploit",
"human": false,
},
{
"source": "NETWORK",
"target": "SIEM",
"description": "WAF alert",
"human": false,
},
{
"source": "SIEM",
"target": "SHUFFLE",
"human": false,
},
{
"source": "SHUFFLE",
"target": "CASES",
"human": false,
},
{
"source": "SHUFFLE",
"target": "COMMS",
"human": false,
},
{
"source": "INTEL",
"target": "SHUFFLE",
"human": false,
},
{
"source": "ASSETS",
"target": "SHUFFLE",
"human": false,
},
{
"source": "IAM",
"target": "SHUFFLE",
"human": false,
},
{
"source": "CASES",
"target": "EDR & AV",
"human": true,
},
]
},
"SIEM alerts": {
"manual": [],
"automated": [
{
"source": "TOP_LEFT",
"target": "SIEM",
"description": "Syslog",
"human": false,
},
{
"source": "SIEM",
"target": "SHUFFLE",
"description": "Alerts",
"human": false,
},
{
"source": "SHUFFLE",
"target": "INTEL",
"description": "Enrich",
"human": false,
},
{
"source": "INTEL",
"target": "SHUFFLE",
"human": false,
},
{
"source": "IAM",
"target": "SHUFFLE",
"human": false,
"description": "enrich",
},
{
"source": "SHUFFLE",
"target": "IAM",
"human": false,
},
{
"source": "SHUFFLE",
"target": "CASES",
"human": false,
},
{
"source": "SHUFFLE",
"target": "COMMS",
"human": false,
},
{
"source": "SHUFFLE",
"target": "EDR & AV",
"human": false,
},
{
"source": "SHUFFLE",
"target": "EDR & AV",
"human": true,
},
]
},
"New Detections": {
"manual": [],
"automated": [
{
"source": "TOP_LEFT",
"target": "SIEM",
"description": "Hypothesis",
"human": true,
},
{
"source": "SIEM",
"target": "SIEM",
"description": "Create rule",
"human": true,
},
{
"source": "SIEM",
"target": "NETWORK",
"description": "Create rule",
"human": true,
},
{
"source": "NETWORK",
"target": "EDR & AV",
"description": "Create rule",
"human": true,
},
{
"source": "SIEM",
"target": "SHUFFLE",
"description": "Send alert",
"human": false,
},
{
"source": "NETWORK",
"target": "SHUFFLE",
"description": "Send alert",
"human": false,
},
{
"source": "EDR & AV",
"target": "SHUFFLE",
"description": "Send alert",
"human": false,
},
{
"source": "INTEL",
"target": "SHUFFLE",
"description": "Enrich IOCs",
"human": false,
},
{
"source": "ASSETS",
"target": "SHUFFLE",
"description": "Enrich hostnames etc.",
"human": false,
},
{
"source": "SHUFFLE",
"target": "CASES",
"description": "Create enriched alert",
"human": false,
},
]
},
"Vulnerabilities": {
"manual": [],
"automated": [
{
"source": "TOP_RIGHT",
"target": "ASSETS",
"description": "New vuln",
"human": false,
},
{
"source": "ASSETS",
"target": "SHUFFLE",
"description": "Get vuln",
"human": false,
},
{
"source": "SHUFFLE",
"target": "CASES",
"description": "Raise ticket",
"human": false,
},
{
"source": "SHUFFLE",
"target": "COMMS",
"description": "Notify owner",
"human": false,
},
{
"source": "COMMS",
"target": "SHUFFLE",
"description": "",
"human": true,
},
{
"source": "SHUFFLE",
"target": "ASSETS",
"description": "Auto-patch",
"human": false,
},
]
},
"Approvals": {
"manual": [],
"automated": [
{
"source": "TOP_LEFT",
"target": "CASES",
"description": "New inquiry",
"human": false,
},
{
"source": "CASES",
"target": "SHUFFLE",
"description": "Get tickets",
"human": false,
},
{
"source": "SHUFFLE",
"target": "COMMS",
"description": "Ask for approval",
"human": false,
},
{
"source": "COMMS",
"target": "SHUFFLE",
"human": true,
},
{
"source": "SHUFFLE",
"target": "ASSETS",
"description": "Add to user",
"human": false,
},
{
"source": "SHUFFLE",
"target": "IAM",
"description": "Approve access",
"human": false,
},
{
"source": "SHUFFLE",
"target": "CASES",
"human": false,
},
]
},
"Enrichment": {
"manual": [],
"automated": [
{
"source": "TOP_LEFT",
"target": "CASES",
"description": "Case updated",
"human": false,
},
{
"source": "SHUFFLE",
"target": "CASES",
"description": "Get and enrich ticket",
"human": false,
},
{
"source": "IAM",
"target": "SHUFFLE",
"description": "Get access rights",
"human": false,
},
{
"source": "ASSETS",
"target": "SHUFFLE",
"description": "Get relevant assets",
"human": false,
},
{
"source": "INTEL",
"target": "SHUFFLE",
"description": "Get relevant IPs",
"human": false,
},
{
"source": "COMMS",
"target": "SHUFFLE",
"description": "Find relevant mails & chats",
"human": false,
},
{
"source": "EDR & AV",
"target": "SHUFFLE",
"description": "Find incidents for host",
"human": false,
},
{
"source": "SIEM",
"target": "SHUFFLE",
"description": "Find info about hostname and user",
"human": false,
},
{
"source": "SHUFFLE",
"target": "SHUFFLE",
"description": "Format info",
"human": false,
},
{
"source": "SHUFFLE",
"target": "CASES",
"description": "",
"human": false,
},
]
},
"Ransomware": {
"manual": [],
"automated": [
{
"source": "BOTTOM_LEFT",
"target": "EDR & AV",
"description": "EDR & AV alert",
"human": false,
},
{
"source": "EDR & AV",
"target": "SHUFFLE",
"description": "",
"human": false,
},
{
"source": "SHUFFLE",
"target": "EDR & AV",
"human": false,
"description": "isolate",
},
{
"source": "SHUFFLE",
"target": "IAM",
"human": false,
"description": "Block access",
},
{
"source": "SHUFFLE",
"target": "COMMS",
"description": "Notify oncall and affected user",
"human": false,
},
{
"source": "SHUFFLE",
"target": "CASES",
"description": "Create enriched alert",
"human": false,
},
{
"source": "SHUFFLE",
"target": "CASES",
"human": false,
},
{
"source": "CASES",
"target": "EDR & AV",
"description": "Validate alert",
"human": true,
},
]
},
"Draw": {
}
}
const AppFramework = (props) => {
const { globalUrl, isLoaded, showOptions, selectedOption, rolling, frameworkData, setFrameworkData, size, inputUsecase, isLoggedIn, color, discoveryWrapper, setDiscoveryWrapper, userdata, apps, inputUsecases, setInputUsecases } = props;
const [cy, setCy] = React.useState()
const [edgesStarted, setEdgesStarted] = React.useState(false)
const [graphDone, setGraphDone] = React.useState(false)
const [cyDone, setCyDone] = React.useState(false)
const [discoveryData, setDiscoveryData] = React.useState({})
const [selectionOpen, setSelectionOpen] = React.useState(true)
const [frameworkSuggestions, setFrameworkSuggestions] = React.useState([])
const [newSelectedApp, setNewSelectedApp] = React.useState({})
const [defaultSearch, setDefaultSearch] = React.useState("")
const [animationStarted, setAnimationStarted] = React.useState(false)
const [paperTitle, setPaperTitle] = React.useState("")
const [changedApp, setChangedApp] = React.useState("")
const [usecaseType, setUsecaseType] = React.useState(0)
const [selectedUsecase, setSelectedUsecase] = React.useState(selectedOption !== undefined ? selectedOption : "Phishing")
const [injectedApps, setInjectedApps] = React.useState([])
const scale = size === undefined ? 1 : size > 5 ? 3 : size
//const alert = useAlert()
const handleLoadNextSuggestion = (frameworkData) => {
console.log("Should check for next apps to load from App suggestion model")
//fetch(globalUrl + "/api/v1/workflows/usecases", {
//credentials: "include",
//cors: "no-cors",
const max_suggestions = 5
const max_per_category = 2
const priorities = {
"edr": 1,
"siem": 2,
"cases": 3,
"intel": 4,
"comms": 5,
}
// Based on apps recommended from repo https://github.com/Shuffle/app-recommender
//fetch("http://localhost:8080/app_recommendations", {
fetch("https://europe-west2-shuffler.cloudfunctions.net/app_recommendations", {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify(frameworkData),
})
.then((response) => {
//if (response.status !== 200) {
// console.log("Status not 200 for framework!");
//}
return response.json();
})
.then((responseJson) => {
// Loop the response dict
var suggestions = []
var suggestion_cnt = 0
var category_count = {}
for (const [key, value] of Object.entries(responseJson)) {
if (suggestion_cnt >= max_suggestions) {
break
}
if (category_count[key] === undefined) {
category_count[key] = 1
} else {
category_count[key] += 1
}
if (category_count[key] > max_per_category) {
continue
}
//console.log("Found: " + key + " with value of len: " + value.length)
//suggestion_cnt += value.slice(0,1).length
suggestions.push(value.slice(0,1))
}
setInjectedApps(suggestions)
})
.catch((error) => {
console.log("Recommendation error: ", error);
})
}
const showRecommendations = (changed, frameworkData) => {
console.log("Inside recommendation loader")
setChangedApp(changed)
handleLoadNextSuggestion(frameworkData)
// Alternative changed
// This is for secondary values like email = comms
var alternativeChanged = changed
if (changed == "COMMS") {
alternativeChanged = "email"
}
// FIX:
// 0. Get workflows loaded in from usecasesearch
// 1. Search through workflow templates for matching app types
// 2. Validate if template is already in use~ (workflows with same tools)
// 3. Generate the workflow(s) - PS: Fix new workflow templates
// 4. Moving on!
// How can we load templates? UsecaseSearch?
var showusecases = []
//const foundusecase = usecaseTypes.find(data => data.name.toLowerCase() === defaultSearch.toLowerCase())
for (var key in usecaseTypes) {
for (var subkey in usecaseTypes[key].value) {
const usecase = usecaseTypes[key].value[subkey]
if (usecase.active === false) {
continue
}
var potential = false
var matches = []
for (var itemtype in usecase.items) {
var apptype = usecase.items[itemtype].app_type.toLowerCase()
if (apptype.toLowerCase() === "email" || apptype.toLowerCase() === "comms" || apptype === "communication") {
apptype = "Comms"
}
//console.log("OLD: ", changed, "USECASE: ", apptype)
//console.log("APptype, changed, framework: ", apptype.toLowerCase(), alternativeChanged.toLowerCase(), changed.toLowerCase(), frameworkData)
if (changed.toLowerCase() === apptype.toLowerCase() || changed.toLowerCase().includes(apptype.toLowerCase()) || alternativeChanged.toLowerCase() === apptype.toLowerCase() || alternativeChanged.toLowerCase().includes(apptype.toLowerCase())) {
potential = true
console.log("Potential: !", apptype)
if (frameworkData[apptype] !== undefined && frameworkData[apptype].name !== undefined && frameworkData[apptype].name !== null && frameworkData[apptype].name.length > 0) {
usecase.items[itemtype].app = frameworkData[apptype]
}
matches.push(usecase.items[itemtype])
} else {
// Check if the type is done in frameworkData
if (frameworkData[apptype] !== undefined) {
//console.log("NOT UNDEFINED: ", frameworkData[apptype])
if (frameworkData[apptype].name !== undefined && frameworkData[apptype].name !== null && frameworkData[apptype].name.length > 0) {
//console.log("FOUND: ", frameworkData[apptype])
usecase.items[itemtype].app = frameworkData[apptype]
//console.log("Real app!")
matches.push(usecase.items[itemtype])
}
//if (frameworkData[apptype] !== undefined) {
} else {
console.log("UNDEFINED APP (bad name?): ", apptype)
}
}
}
// Adds to list if it's all matching and unhandled
if (potential) {
// Check finished usecases.
if (inputUsecases !== undefined && setInputUsecases !== undefined && usecase.usecase_references !== undefined && usecase.usecase_references.length > 0) {
var foundUsecase = false
for (var usecaseKey in inputUsecases) {
const usecaseCategory = inputUsecases[usecaseKey]
for (var subUsecaseKey in usecaseCategory.list) {
const loopUsecase = usecaseCategory.list[subUsecaseKey]
if (loopUsecase.matches === undefined || loopUsecase.matches === null || loopUsecase.matches.length === 0) {
//console.log("No matches - continuing")
continue
}
if (usecase.usecase_references.includes(loopUsecase.name)) {
foundUsecase = true
break
}
}
if (foundUsecase) {
break
}
}
if (!foundUsecase) {
console.log("Usecase NOT found!")
} else {
console.log("FOUND usecase existing in ", usecase.usecase_references)
continue
}
} else {
console.log("No usecase to try to match it to (usecase.usecase_references in UsecaseSearch)")
}
console.log("Usecase: ", usecase)
if (matches.length === usecase.items.length) {
usecase.color = "#c51152"
usecase.type = usecaseTypes[key].name
showusecases.push(usecase)
}
}
}
}
// FIXME: Check if a usecase has already been handled
console.log("")
console.log("GOT USECASES: ", showusecases)
// FIXME: Just showing one usecase at a time for now
if (showusecases.length > 0) {
setFrameworkSuggestions(showusecases.slice(0,1))
}
}
useEffect(() => {
//console.log("DISCWRAP CHANG: ", discoveryWrapper)
if (discoveryWrapper === undefined || discoveryWrapper.id === "SHUFFLE" || discoveryWrapper.id === undefined || cy === undefined) {
setDiscoveryData({})
if (cy !== undefined) {
cy.nodes().unselect()
}
return
}
// Find the node and click it?
//setTimeout(() => {
const nodes = cy.nodes().jsons()
for (var key in nodes) {
const node = nodes[key]
var newSearchName = discoveryWrapper.id.valueOf()
if (newSearchName === "EMAIL") {
newSearchName = "COMMS"
}
if (newSearchName === "ERADICATION" || newSearchName === "ENDPOINT") {
newSearchName = "EDR & AV"
}
if (node.data.id === newSearchName) {
const tmpnode = cy.getElementById(node.data.id)
if (tmpnode !== undefined) {
tmpnode.select()
}
setDefaultSearch(discoveryWrapper.id)
setPaperTitle(discoveryWrapper.id)
}
}
//}, 50,)
//setDiscoveryData(discoveryWrapper)
}, [discoveryWrapper])
const setUsecaseItem = (inputUsecase) => {
var parsedUsecase = inputUsecase
const edges = cy.edges().jsons()
parsedUsecase.process = []
for (var key in edges) {
const edge = edges[key]
console.log("Edge: ", edge)
parsedUsecase.process.push(edge.data)
}
fetch(globalUrl + "/api/v1/workflows/usecases", {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify(parsedUsecase),
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for framework!");
}
return response.json();
})
.then((responseJson) => {
if (responseJson.success === false) {
if (responseJson.reason !== undefined) {
toast("Failed updating: " + responseJson.reason)
} else {
toast("Failed to update framework for your org.")
}
} else {
toast("Updated usecase.")
}
})
.catch((error) => {
toast(error.toString());
//setFrameworkLoaded(true)
})
}
const activateApp = (appid) => {
fetch(globalUrl+"/api/v1/apps/"+appid+"/activate", {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Failed to activate")
}
return response.json()
})
.then((responseJson) => {
if (responseJson.success === false) {
toast("Failed to activate the app")
} else {
//toast("App activated for your organization! Refresh the page to use the app.")
}
})
.catch(error => {
//toast(error.toString())
console.log("Activate app error: ", error.toString())
});
}
const setFrameworkItem = (data) => {
console.log("Setting framework item: ", data, isCloud)
if (!isCloud) {
activateApp(data.id)
}
fetch(globalUrl + "/api/v1/apps/frameworkConfiguration", {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify(data),
credentials: "include",
})
.then((response) => {
if (response.status !== 200) {
console.log("Status not 200 for framework!");
}
return response.json();
})
.then((responseJson) => {
if (responseJson.success === false) {
if (responseJson.reason !== undefined) {
toast("Failed updating: " + responseJson.reason)
} else {
toast("Failed to update framework for your org.")
}
}
//setFrameworkLoaded(true)
//setFrameworkData(responseJson)
})
.catch((error) => {
toast(error.toString());
//setFrameworkLoaded(true)
})
}
useEffect(() => {
if (!window.location.pathname.includes("usecases")) {
handleLoadNextSuggestion(frameworkData)
}
}, [])
useEffect(() => {
//console.log("New selected app: ", newSelectedApp, discoveryData)
if (newSelectedApp.objectID === undefined || newSelectedApp.objectID === undefined || newSelectedApp.objectID.length === 0) {
return
}
//if (paperTitle.length > 0) {
// console.log("No papertitle (parent button)")
// cy.elements().unselect()
// return
//}
if (discoveryData.id === undefined) {
console.log("No discoverydata (parent button)")
cy.elements().unselect()
return
}
const submitValue = {
"type": discoveryData.id,
"name": newSelectedApp.name,
"id": newSelectedApp.objectID,
"large_image": newSelectedApp.image_url,
"description": newSelectedApp.description === undefined ? "" : newSelectedApp.description,
}
const foundelement = cy.getElementById(discoveryData.id)
if (foundelement !== undefined && foundelement !== null) {
foundelement.data("large_image", newSelectedApp.image_url)
foundelement.data("margin_x", "0px")
foundelement.data("margin_y", "0px")
foundelement.data("text_margin_y", `${60*scale}px`)
foundelement.data("width", `${85*scale}px`)
foundelement.data("height", `${85*scale}px`)
}
if (setFrameworkData !== undefined) {
// Find discoveryData.id
var keys = []
for (const [key, value] of Object.entries(frameworkData)) {
if (key.toLowerCase() === discoveryData.id.toLowerCase()) {
keys.push(key)
}
}
if (keys.length === 0) {
console.log("Failed to find: ", discoveryData.id, " IN ", frameworkData)
} else {
for (var key in keys) {
frameworkData[keys[key]] = submitValue
}
console.log("Frameworkdata: ", frameworkData)
setFrameworkData(frameworkData)
if (discoveryData.large_image !== undefined && discoveryData.large_image !== null && discoveryData.large_image.includes("storage.googleapis.com")) {
showRecommendations(discoveryData.id, frameworkData)
} else {
console.log("Skipping recommendations during unselect")
}
}
}
setFrameworkItem(submitValue)
cy.elements().unselect()
}, [newSelectedApp])
const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io";
const imgSize = 50;
var parsedFrameworkData = frameworkData === undefined ? {} : frameworkData
// Awful mapping to make sure all access is always there
if (frameworkData !== undefined) {
if (frameworkData.cases !== undefined) {
if (frameworkData.cases.large_image === undefined && frameworkData.cases.large_image === null || frameworkData.cases.large_image === "") {
frameworkData.cases = {}
}
parsedFrameworkData.Cases = frameworkData.cases
} else {
parsedFrameworkData.Cases = {}
}
if (frameworkData.siem !== undefined) {
if (frameworkData.siem.large_image === undefined && frameworkData.siem.large_image === null || frameworkData.siem.large_image === "") {
frameworkData.siem = {}
}
parsedFrameworkData.SIEM = frameworkData.siem
} else {
parsedFrameworkData.SIEM = {}
}
if (frameworkData.assets !== undefined) {
if (frameworkData.assets.large_image === undefined && frameworkData.assets.large_image === null || frameworkData.assets.large_image === "") {
frameworkData.assets = {}
}
parsedFrameworkData.Assets = frameworkData.assets
} else {
parsedFrameworkData.Assets = {}
}
if (frameworkData.intel !== undefined) {
if (frameworkData.intel.large_image === undefined && frameworkData.intel.large_image === null || frameworkData.intel.large_image === "") {
frameworkData.intel = {}
}
parsedFrameworkData.Intel = frameworkData.intel
} else {
parsedFrameworkData.Intel= {}
}
if (frameworkData.communication !== undefined) {
if (frameworkData.communication.large_image === undefined && frameworkData.communication.large_image === null || frameworkData.communication.large_image === "") {
frameworkData.communication = {}
}
parsedFrameworkData.Comms = frameworkData.communication
} else {
parsedFrameworkData.Comms = {}
}
if (frameworkData.network !== undefined) {
if (frameworkData.network.large_image === undefined && frameworkData.network.large_image === null || frameworkData.network.large_image === "") {
frameworkData.network = {}
}
parsedFrameworkData.Network = frameworkData.network
} else {
parsedFrameworkData.Network = {}
}
if (frameworkData.iam !== undefined) {
if (frameworkData.iam.large_image === undefined && frameworkData.iam.large_image === null || frameworkData.iam.large_image === "") {
frameworkData.iam = {}
}
parsedFrameworkData.IAM = frameworkData.iam
} else {
parsedFrameworkData.IAM = {}
}
if (frameworkData.edr !== undefined) {
if (frameworkData.edr.large_image === undefined && frameworkData.edr.large_image === null || frameworkData.edr.large_image === "") {
frameworkData.edr = {}
}
parsedFrameworkData["EDR & AV"] = frameworkData.edr
} else {
parsedFrameworkData["EDR & AV"] = {}
}
} else {
//console.log("No frameworkdata for org! Setting default")
parsedFrameworkData["Cases"] = {}
parsedFrameworkData["SIEM"] = {}
parsedFrameworkData["Assets"] = {}
parsedFrameworkData["IAM"] = {}
parsedFrameworkData["Intel"] = {}
parsedFrameworkData["Comms"] = {}
parsedFrameworkData["Network"] = {}
parsedFrameworkData["EDR & AV"] = {}
}
//console.log("Framework - update? ", parsedFrameworkData)
// 0 = automated, 1 = manual
const elements = []
const surfaceColor = "#27292D"
const changeUsecase = (value, type) => {
//console.log("Value: ", value)
if (value === "Draw" && !edgesStarted) {
setEdgesStarted(true)
cy.edgehandles({
handleNodes: (el) => {
if (el.isNode() &&
!el.data("isButton") &&
!el.data("isDescriptor") &&
el.data("type") !== "COMMENT") {
return true
}
return false
},
preview: true,
toggleOffOnLeave: true,
loopAllowed: function (node) {
return false;
},
})
}
setSelectedUsecase(value)
const edges = cy.edges()
if (edges !== undefined) {
const allEdges = edges.jsons()
for (var key in allEdges) {
const newedge = allEdges[key]
const foundelement = cy.getElementById(newedge.data.id)
if (foundelement !== undefined && foundelement !== null) {
foundelement.remove()
}
}
}
var found = false
var parsedType = type === 1 ? "manual" : "automated"
if (usecases === undefined || usecases === null) {
return
}
const newedges = usecases[value][parsedType]
for (var key in newedges) {
newedges[key].label = parseInt(key)+1
if (newedges[key].description !== undefined && newedges[key].description !== null && newedges[key].description.length > 0) {
newedges[key].label = (parseInt(key)+1)+" "+newedges[key].description
}
cy.add({
group: "edges",
data: newedges[key],
})
}
setGraphDone(true)
}
const animationDuration = 150;
const nodeAddDuration = 2500;
if (cy !== undefined && cyDone && !animationStarted) {
const foundelement = cy.getElementById("CASES")
if (foundelement !== undefined && foundelement !== null) {
setAnimationStarted(true)
var parsedStyle = {
"ghost": "yes",
"border-color": "#f86a3e",
"border-width": "10px",
"border-opacity": ".7",
}
var parsedStyle2 = {
"ghost": "yes",
"border-width": "7px",
}
/*
// Animation
for (var i = 0; i < 10; i++) {
foundelement.animate(
{
style: parsedStyle,
},
{
duration: nodeAddDuration,
}
).delay(1000)
foundelement.animate(
{
style: parsedStyle2,
},
{
duration: nodeAddDuration,
}
)
}
*/
}
}
const onNodeAdded = (event) => {
//if (event.target.data("animate") === true) {
//}
}
const onNodeHover = (event) => {
const nodedata = event.target.data();
const cytoscapeElement = document.getElementById("cytoscape_view")
if (cytoscapeElement !== undefined && cytoscapeElement !== null) {
cytoscapeElement.style.cursor = "pointer"
}
var parsedStyle = {
"border-width": "7px",
"border-opacity": ".7",
}
//if (nodedata.type !== "COMMENT") {
// parsedStyle.color = "white";
//}
if (event.target !== undefined && event.target !== null) {
event.target.animate(
{
style: parsedStyle,
},
{
duration: animationDuration,
}
);
}
}
const onNodeHoverOut = (event) => {
const nodedata = event.target.data();
//console.log("Node OUT: ", nodedata)
const cytoscapeElement = document.getElementById("cytoscape_view")
if (cytoscapeElement !== undefined && cytoscapeElement !== null) {
cytoscapeElement.style.cursor = "default"
}
var parsedStyle = {
"border-width": "3px",
//"cursor": "default",
}
event.target.animate(
{
style: parsedStyle,
},
{
duration: animationDuration,
}
)
}
const onNodeUnselect = (event) => {
var data = event.target.data();
console.log("UNSELECT: ", data)
var parsedStyle = {
"border-width": "10px",
"border-opacity": ".7",
"border-color": "#7fe57f",
}
// Some error here?
if (event.target !== undefined && event.target !== null) {
event.target.animate(
{
style: parsedStyle,
},
{
duration: animationDuration,
}
)
setTimeout(() => {
event.target.animate(
{
style: {
"border-width": "3px",
},
},
{
duration: animationDuration,
}
)
}, 2500)
}
//setDiscoveryData({})
if (setDiscoveryWrapper !== undefined) {
setDiscoveryWrapper({})
}
setSelectionOpen(false)
setDefaultSearch("")
setPaperTitle("")
//setDiscoveryData({})
}
const onNodeSelect = (event) => {
var data = event.target.data();
console.log("Node: ", data)
if (data.id === "SHUFFLE") {
event.target.unselect()
return
}
if (data.label === "EDR & AV") {
data.label = "ERADICATION"
}
setDiscoveryData(data)
setSelectionOpen(true)
setNewSelectedApp({})
setDefaultSearch(data.label.charAt(0).toUpperCase()+(data.label.substring(1)).toLowerCase())
}
const onEdgeAdded = (event) => {
//event.target.data("human", false)
}
const onEdgeSelect = (event) => {
console.log(event)
if (event.target.data("human") === undefined || event.target.data("human") === false) {
event.target.data("human", true)
} else if (event.target.data("human") === true) {
event.target.remove()
return
//event.target.data("human", false)
}
console.log("Edge selected!", event.target.data())
event.target.unselect()
}
if (graphDone && cyDone === false) {
cy.fit(null, 200);
cy.on("select", "edge", (e) => onEdgeSelect(e));
cy.on("add", "edge", (e) => onEdgeAdded(e));
cy.on("select", "node", (e) => {
onNodeSelect(e)
})
cy.on("unselect", "node", (e) => {
onNodeUnselect(e)
})
cy.on("mouseover", "node", (e) => {onNodeHover(e)})
cy.on("mouseout", "node", (e) => onNodeHoverOut(e));
cy.on("add", "node", (e) => onNodeAdded(e));
setCyDone(true)
}
if (cy !== undefined && cy.elements().length === 0 && parsedFrameworkData !== undefined) {
//'background-image': 'data(small_image)',
const shiftradius = 115*scale
const baselocationX = 285*scale
const baselocationY = 50*scale
const shiftmodifier = 3*scale
//const svgSize = `${40*scale}px`
//const svgSize = `${40}px`
const foundMiddleImage = userdata !== undefined && userdata !== null && userdata.active_org !== undefined && userdata.active_org.image !== undefined && userdata.active_org.image !== null && userdata.active_org.image !== "" ? userdata.active_org.image : '/images/Shuffle_logo.png'
const fontSize = `${12*scale}px`
const defaultSize = `${85*scale}px`
const iconSize = `${45*scale}px`
const textMarginDefault = `${14*scale}px`
const textMarginImage = `${60*scale}px`
const nodes = [
{
group: "nodes",
data: {
boxwidth: defaultSize,
boxheight: defaultSize,
font_size: fontSize,
is_valid: true,
isValid: true,
errors: [],
name: parsedFrameworkData.Cases.name === undefined ? "" : parsedFrameworkData.Cases.name,
description: parsedFrameworkData.Cases.description === undefined ? "" : parsedFrameworkData.Cases.description,
app_id: parsedFrameworkData.Cases.id === undefined ? "" : parsedFrameworkData.Cases.id,
text_margin_y: parsedFrameworkData.Cases.large_image === undefined ? textMarginDefault : textMarginImage,
margin_x: parsedFrameworkData.Cases.large_image === undefined ? `${32*scale}px` : "0px",
margin_y: parsedFrameworkData.Cases.large_image === undefined ? `${19*scale}px` : `0px`,
width: parsedFrameworkData.Cases.large_image === undefined ? iconSize : defaultSize,
height: parsedFrameworkData.Cases.large_image === undefined ? iconSize : defaultSize,
large_image: parsedFrameworkData.Cases.large_image === undefined ? parsedDatatypeImages["CASES"] : parsedFrameworkData.Cases.large_image,
label: securityFramework[0].text.toUpperCase(),
id: securityFramework[0].text.toUpperCase(),
animate: true,
},
renderedPosition: {
x: baselocationX,
y: baselocationY,
}
},
{
group: "nodes",
data: {
font_size: fontSize,
boxwidth: defaultSize,
boxheight: defaultSize,
is_valid: true,
isValid: true,
errors: [],
name: parsedFrameworkData.IAM.name === undefined ? "" : parsedFrameworkData.IAM.name,
description: parsedFrameworkData.IAM.description === undefined ? "" : parsedFrameworkData.IAM.description,
app_id: parsedFrameworkData.IAM.id === undefined ? "" : parsedFrameworkData.IAM.id,
text_margin_y: parsedFrameworkData.IAM.large_image === undefined ? textMarginDefault : textMarginImage,
margin_x: parsedFrameworkData.IAM.large_image === undefined ? `${32*scale}px` : "0px",
margin_y: parsedFrameworkData.IAM.large_image === undefined ? `${19*scale}px` : `0px`,
width: parsedFrameworkData.IAM.large_image === undefined ? iconSize : defaultSize,
height: parsedFrameworkData.IAM.large_image === undefined ? iconSize : defaultSize,
large_image: parsedFrameworkData.IAM.large_image === undefined ? parsedDatatypeImages["IAM"] : parsedFrameworkData.IAM.large_image,
label: securityFramework[3].text.toUpperCase(),
id: securityFramework[3].text.toUpperCase(),
animate: false,
},
renderedPosition: {
x: baselocationX+shiftradius+(shiftradius/shiftmodifier),
y: baselocationY+shiftradius-(shiftradius/shiftmodifier),
}
},
{
group: "nodes",
data: {
font_size: fontSize,
boxwidth: defaultSize,
boxheight: defaultSize,
is_valid: true,
isValid: true,
errors: [],
name: parsedFrameworkData.Assets.name === undefined ? "" : parsedFrameworkData.Assets.name,
description: parsedFrameworkData.Assets.description === undefined ? "" : parsedFrameworkData.Assets.description,
app_id: parsedFrameworkData.Assets.id === undefined ? "" : parsedFrameworkData.Assets.id,
text_margin_y: parsedFrameworkData.Assets.large_image === undefined ? textMarginDefault : textMarginImage,
margin_x: parsedFrameworkData.Assets.large_image === undefined ? `${32*scale}px` : "0px",
margin_y: parsedFrameworkData.Assets.large_image === undefined ? `${19*scale}px` : `0px`,
width: parsedFrameworkData.Assets.large_image === undefined ? iconSize : defaultSize,
height: parsedFrameworkData.Assets.large_image === undefined ? iconSize : defaultSize,
large_image: parsedFrameworkData.Assets.large_image === undefined ? parsedDatatypeImages["ASSETS"] : parsedFrameworkData.Assets.large_image,
label: securityFramework[2].text.toUpperCase(),
id: securityFramework[2].text.toUpperCase(),
animate: false,
},
renderedPosition: {
x: baselocationX+shiftradius*2,
y: baselocationY+shiftradius*2,
}
},
{
group: "nodes",
data: {
font_size: fontSize,
boxwidth: defaultSize,
boxheight: defaultSize,
is_valid: true,
isValid: true,
errors: [],
name: parsedFrameworkData.Intel.name === undefined ? "" : parsedFrameworkData.Intel.name,
description: parsedFrameworkData.Intel.description === undefined ? "" : parsedFrameworkData.Intel.description,
app_id: parsedFrameworkData.Intel.id === undefined ? "" : parsedFrameworkData.Intel.id,
text_margin_y: parsedFrameworkData.Intel.large_image === undefined ? textMarginDefault : textMarginImage,
margin_x: parsedFrameworkData.Intel.large_image === undefined ? `${32*scale}px` : "0px",
margin_y: parsedFrameworkData.Intel.large_image === undefined ? `${19*scale}px` : `0px`,
width: parsedFrameworkData.Intel.large_image === undefined ? iconSize : defaultSize,
height: parsedFrameworkData.Intel.large_image === undefined ? iconSize : defaultSize,
large_image: parsedFrameworkData.Intel.large_image === undefined ? parsedDatatypeImages["INTEL"] : parsedFrameworkData.Intel.large_image,
label: securityFramework[4].text.toUpperCase(),
id: securityFramework[4].text.toUpperCase(),
animate: false,
},
renderedPosition: {
x: baselocationX+shiftradius+(shiftradius/shiftmodifier),
y: baselocationY+shiftradius*3+(shiftradius/shiftmodifier),
}
},
{
group: "nodes",
data: {
font_size: fontSize,
boxwidth: defaultSize,
boxheight: defaultSize,
is_valid: true,
isValid: true,
errors: [],
name: parsedFrameworkData.Comms.name === undefined ? "" : parsedFrameworkData.Comms.name,
description: parsedFrameworkData.Comms.description === undefined ? "" : parsedFrameworkData.Comms.description,
app_id: parsedFrameworkData.Comms.id === undefined ? "" : parsedFrameworkData.Comms.id,
text_margin_y: parsedFrameworkData.Comms.large_image === undefined ? textMarginDefault : textMarginImage,
margin_x: parsedFrameworkData.Comms.large_image === undefined ? `${32*scale}px` : "0px",
margin_y: parsedFrameworkData.Comms.large_image === undefined ? `${19*scale}px` : `0px`,
width: parsedFrameworkData.Comms.large_image === undefined ? iconSize : defaultSize,
height: parsedFrameworkData.Comms.large_image === undefined ? iconSize : defaultSize,
large_image: parsedFrameworkData.Comms.large_image === undefined ? parsedDatatypeImages["COMMS"] : parsedFrameworkData.Comms.large_image,
label: securityFramework[5].text.toUpperCase(),
id: securityFramework[5].text.toUpperCase(),
animate: false,
},
renderedPosition: {
x: baselocationX,
y: baselocationY+shiftradius*4,
}
},
{
group: "nodes",
data: {
font_size: fontSize,
boxwidth: defaultSize,
boxheight: defaultSize,
is_valid: true,
isValid: true,
errors: [],
name: parsedFrameworkData["EDR & AV"].name === undefined ? "" : parsedFrameworkData["EDR & AV"].name,
description: parsedFrameworkData["EDR & AV"].description === undefined ? "" : parsedFrameworkData["EDR & AV"].description,
app_id: parsedFrameworkData["EDR & AV"].id === undefined ? "" : parsedFrameworkData["EDR & AV"].id,
text_margin_y: parsedFrameworkData["EDR & AV"].large_image === undefined ? textMarginDefault : textMarginImage,
margin_x: parsedFrameworkData["EDR & AV"].large_image === undefined ? `${32*scale}px` : "0px",
margin_y: parsedFrameworkData["EDR & AV"].large_image === undefined ? `${19*scale}px` : `0px`,
width: parsedFrameworkData["EDR & AV"].large_image === undefined ? iconSize : defaultSize,
height: parsedFrameworkData["EDR & AV"].large_image === undefined ? iconSize : defaultSize,
large_image: parsedFrameworkData["EDR & AV"].large_image === undefined ? parsedDatatypeImages["EDR & AV"] : parsedFrameworkData["EDR & AV"].large_image,
label: securityFramework[7].text.toUpperCase(),
id: securityFramework[7].text.toUpperCase(),
animate: false,
},
renderedPosition: {
x: baselocationX-shiftradius-(shiftradius/shiftmodifier),
y: baselocationY+shiftradius*3+(shiftradius/shiftmodifier),
}
},
{
group: "nodes",
data: {
font_size: fontSize,
boxwidth: defaultSize,
boxheight: defaultSize,
is_valid: true,
isValid: true,
errors: [],
name: parsedFrameworkData.Network.name === undefined ? "" : parsedFrameworkData.Network.name,
description: parsedFrameworkData.Network.description === undefined ? "" : parsedFrameworkData.Network.description,
app_id: parsedFrameworkData.Network.id === undefined ? "" : parsedFrameworkData.Network.id,
text_margin_y: parsedFrameworkData.Network.large_image === undefined ? textMarginDefault : textMarginImage,
margin_x: parsedFrameworkData.Network.large_image === undefined ? `${32*scale}px` : "0px",
margin_y: parsedFrameworkData.Network.large_image === undefined ? `${19*scale}px` : `0px`,
width: parsedFrameworkData.Network.large_image === undefined ? iconSize : defaultSize,
height: parsedFrameworkData.Network.large_image === undefined ? iconSize : defaultSize,
large_image: parsedFrameworkData.Network.large_image === undefined ? parsedDatatypeImages["NETWORK"] : parsedFrameworkData.Network.large_image,
label: securityFramework[6].text.toUpperCase(),
id: securityFramework[6].text.toUpperCase(),
animate: false,
},
renderedPosition: {
x: baselocationX-shiftradius*2,
y: baselocationY+shiftradius*2,
}
},
{
group: "nodes",
data: {
font_size: fontSize,
boxwidth: defaultSize,
boxheight: defaultSize,
is_valid: true,
isValid: true,
errors: [],
name: parsedFrameworkData.SIEM.name === undefined ? "" : parsedFrameworkData.SIEM.name,
description: parsedFrameworkData.SIEM.description === undefined ? "" : parsedFrameworkData.SIEM.description,
app_id: parsedFrameworkData.SIEM.id === undefined ? "" : parsedFrameworkData.SIEM.id,
text_margin_y: parsedFrameworkData.SIEM.large_image === undefined ? textMarginDefault : textMarginImage,
margin_x: parsedFrameworkData.SIEM.large_image === undefined ? `${32*scale}px` : "0px",
margin_y: parsedFrameworkData.SIEM.large_image === undefined ? `${19*scale}px` : `0px`,
width: parsedFrameworkData.SIEM.large_image === undefined ? iconSize : defaultSize,
height: parsedFrameworkData.SIEM.large_image === undefined ? iconSize : defaultSize,
large_image: parsedFrameworkData.SIEM.large_image === undefined ? parsedDatatypeImages["SIEM"] : parsedFrameworkData.SIEM.large_image,
label: securityFramework[1].text.toUpperCase(),
id: securityFramework[1].text.toUpperCase(),
animate: false,
},
renderedPosition: {
x: baselocationX-shiftradius-(shiftradius/shiftmodifier),
y: baselocationY+shiftradius-(shiftradius/shiftmodifier),
}
},
]
// Middlenode
nodes.push({
group: "nodes",
data: {
font_size: fontSize,
width: defaultSize,
height: defaultSize,
id: "SHUFFLE",
is_valid: true,
isValid: true,
errors: [],
middle_node: true,
large_image: foundMiddleImage,
},
renderedPosition: {
x: baselocationX,
y: baselocationY+shiftradius*2,
}
})
// Extra nodes
nodes.push({
group: "nodes",
data: {
is_valid: true,
isValid: true,
errors: [],
large_image: encodeURI(`data:image/svg+xml;utf-8,`),
id: "TOP_LEFT",
invisible: true,
},
renderedPosition: {
x: baselocationX-shiftradius*2.5,
y: baselocationY-50,
}
})
nodes.push({
group: "nodes",
data: {
is_valid: true,
isValid: true,
errors: [],
large_image: encodeURI(`data:image/svg+xml;utf-8,`),
id: "BOTTOM_LEFT",
invisible: true,
},
renderedPosition: {
x: baselocationX-shiftradius*2.5-10,
y: baselocationY+shiftradius*4-10,
}
})
nodes.push({
group: "nodes",
data: {
is_valid: true,
isValid: true,
errors: [],
large_image: encodeURI(`data:image/svg+xml;utf-8,`),
id: "BOTTOM_RIGHT",
invisible: true,
},
renderedPosition: {
x: baselocationX+shiftradius*2+50,
y: baselocationY+shiftradius*4+50,
}
})
nodes.push({
group: "nodes",
data: {
is_valid: true,
isValid: true,
errors: [],
large_image: encodeURI(`data:image/svg+xml;utf-8,`),
id: "TOP_RIGHT",
invisible: true,
},
renderedPosition: {
x: baselocationX+shiftradius*2,
y: baselocationY-150,
}
})
//console.log("NODES: " , nodes)
for (var key in nodes) {
cy.add(nodes[key]).lock()
}
changeUsecase(selectedUsecase, usecaseType)
if (inputUsecase !== undefined && inputUsecase !== null) {
for (var key in inputUsecase.process) {
if (inputUsecase.process[key].source === "" || inputUsecase.process[key].target === "") {
continue
}
inputUsecase.process[key].label = parseInt(key)+1
inputUsecase.process[key].id = uuidv4();
cy.add({
group: "edges",
data: inputUsecase.process[key],
})
}
//changeUsecase(inputUsecase, 0)
//setSelectedUsecase(value)
}
}
if (selectedOption !== undefined && selectedUsecase !== selectedOption) {
setSelectedUsecase(selectedOption)
changeUsecase(selectedOption, usecaseType)
}
const UsecaseHandler = (props) => {
const { data, index, diff } = props
const leftImage = data.left_image !== undefined ? parsedDatatypeImages[data.left_image.toUpperCase()] : undefined
const rightImage = data.right_image !== undefined ? parsedDatatypeImages[data.right_image.toUpperCase()] : undefined
if (leftImage === undefined) {
console.log("LEFT MISSING: ", leftImage)
}
if (rightImage === undefined) {
console.log("Right MISSING: ", rightImage)
}
const parsedRightImage =
const parsedRightText =