From 6e11e9aedba29261a97035ab942217ca0ea8e79e Mon Sep 17 00:00:00 2001 From: Simon Clow Date: Tue, 9 Apr 2024 22:35:51 +0100 Subject: [PATCH 1/3] Initial commit for task deletion/cancellation --- .../MythicComponents/TaskDeleteDialog.js | 80 +++ .../MythicComponents/TaskDisplayContainer.js | 529 ++++++++++++++++++ hasura-docker/metadata/tables.yaml | 16 + 3 files changed, 625 insertions(+) create mode 100644 MythicReactUI/src/components/MythicComponents/TaskDeleteDialog.js create mode 100644 MythicReactUI/src/components/MythicComponents/TaskDisplayContainer.js diff --git a/MythicReactUI/src/components/MythicComponents/TaskDeleteDialog.js b/MythicReactUI/src/components/MythicComponents/TaskDeleteDialog.js new file mode 100644 index 000000000..10e8b5c1e --- /dev/null +++ b/MythicReactUI/src/components/MythicComponents/TaskDeleteDialog.js @@ -0,0 +1,80 @@ +import React, {useState} from 'react'; +import Button from '@mui/material/Button'; +import DialogActions from '@mui/material/DialogActions'; +import DialogContent from '@mui/material/DialogContent'; +import DialogTitle from '@mui/material/DialogTitle'; +import MythicTextField from '../../MythicComponents/MythicTextField'; +import {useQuery, gql, useMutation} from '@apollo/client'; +import LinearProgress from '@mui/material/LinearProgress'; + +const deleteTaskByPkMutation = gql` +mutation deleteTask($task_id: Int!) { + delete_task_by_pk(id: $task_id) { + id + } +} +`; + + +const getStatusQuery= gql` +query getStatusQuery ($task_id: Int!) { + task_by_pk(id: $task_id) { + status + commentOperator { + username + } + id + } +} +`; + +export function TaskDeleteDialog(props) { + const [status, setStatus] = useState(""); + const { loading, error } = useQuery(getStatusQuery, { + variables: {task_id: props.task_id}, + onCompleted: data => { + //setStatus(data.task_by_pk.status) + setStatus("Warning: This will delete Task!"); + }, + fetchPolicy: "network-only" + }); + + const [deleteTask] = useMutation(deleteTaskByPkMutation,{ + variables: {task_id: props.task_id} + + }); + + if (loading) { + return ; + } + if (error) { + console.error(error); + return
Error!
; + } + const onCommitSubmit = () => { + deleteTask({variables: {task_id: props.task_id}}); + props.onClose(); + } + const onChange = (name, value, error) => { + setStatus(value); + } + + return ( + + Cancel Task + + Are you sure? + + + + + + + + ); +} + diff --git a/MythicReactUI/src/components/MythicComponents/TaskDisplayContainer.js b/MythicReactUI/src/components/MythicComponents/TaskDisplayContainer.js new file mode 100644 index 000000000..14924b5e8 --- /dev/null +++ b/MythicReactUI/src/components/MythicComponents/TaskDisplayContainer.js @@ -0,0 +1,529 @@ +import React, {useEffect} from 'react'; +import { copyStringToClipboard } from '../../utilities/Clipboard'; +import GetAppIcon from '@mui/icons-material/GetApp'; +import FileCopyOutlinedIcon from '@mui/icons-material/FileCopyOutlined'; +import {ResponseDisplay, ResponseDisplayConsole} from './ResponseDisplay'; +import RateReviewOutlinedIcon from '@mui/icons-material/RateReviewOutlined'; +import { MythicDialog } from '../../MythicComponents/MythicDialog'; +import {TaskCommentDialog} from './TaskCommentDialog'; +import {ViewEditTagsDialog} from '../../MythicComponents/MythicTag'; +import {useTheme} from '@mui/material/styles'; +import LockIcon from '@mui/icons-material/Lock'; +import LockOpenIcon from '@mui/icons-material/LockOpen'; +import {TaskOpsecDialog} from './TaskOpsecDialog'; +import BlockIcon from '@mui/icons-material/Block'; +import DeleteForeverIcon from '@mui/icons-material/DeleteForever'; +import {TaskDeleteDialog} from './TaskDeleteDialog'; +import {TaskViewParametersDialog} from './TaskViewParametersDialog'; +import {TaskViewStdoutStderrDialog} from './TaskViewStdoutStderrDialog'; +import {snackActions} from '../../utilities/Snackbar'; +import LocalOfferOutlinedIcon from '@mui/icons-material/LocalOfferOutlined'; +import CodeIcon from '@mui/icons-material/Code'; +import KeyboardIcon from '@mui/icons-material/Keyboard'; +import ConfirmationNumberIcon from '@mui/icons-material/ConfirmationNumber'; +import {TaskTokenDialog} from './TaskTokenDialog'; +import Grid from '@mui/material/Grid'; +import ReplayIcon from '@mui/icons-material/Replay'; +import {gql, useMutation, useLazyQuery } from '@apollo/client'; +import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; +import {faExclamationTriangle} from '@fortawesome/free-solid-svg-icons'; +import { faExternalLinkAlt, faExpandArrowsAlt } from '@fortawesome/free-solid-svg-icons'; +import SearchIcon from '@mui/icons-material/Search'; +import SpeedDial from '@mui/material/SpeedDial'; +import SpeedDialIcon from '@mui/material/SpeedDialIcon'; +import SpeedDialAction from '@mui/material/SpeedDialAction'; +import { Backdrop } from '@mui/material'; +import {downloadFileFromMemory} from '../../utilities/Clipboard'; +import InsertPhotoIcon from '@mui/icons-material/InsertPhoto'; +import html2canvas from 'html2canvas'; +import CloseFullscreenIcon from '@mui/icons-material/CloseFullscreen'; +import CodeOffIcon from '@mui/icons-material/CodeOff'; +import SettingsTwoToneIcon from '@mui/icons-material/SettingsTwoTone'; + +const ReissueTaskMutationGQL = gql` +mutation reissueTaskMutation($task_id: Int!){ + reissue_task(task_id: $task_id){ + status + error + } +} +`; +const ReissueTaskHandlerMutationGQL = gql` +mutation reissueTaskHandlerMutation($task_id: Int!){ + reissue_task_handler(task_id: $task_id){ + status + error + } +} +`; +const getAllResponsesLazyQuery = gql` +query subResponsesQuery($task_id: Int!) { + response(where: {task_id: {_eq: $task_id}}, order_by: {id: asc}) { + id + response: response_text + } +}`; + +export const TaskDisplayContainer = ({task, me}) => { + const [viewBrowserScript, setViewBrowserScript] = React.useState(true); + const [commandID, setCommandID] = React.useState(0); + const [searchOutput, setSearchOutput] = React.useState(false); + const [selectAllOutput, setSelectAllOutput] = React.useState(false); + const responseRef = React.useRef(null); + useEffect( () => { + setCommandID(task.command === null ? 0 : task.command.id); + }, [task.command]); + const toggleViewBrowserScript = React.useCallback( () => { + setViewBrowserScript(!viewBrowserScript); + }, [viewBrowserScript]); + const toggleSelectAllOutput = React.useCallback( () => { + setSelectAllOutput(!selectAllOutput); + }, [selectAllOutput]); + const toggleOpenSearch = React.useCallback( () => { + setSearchOutput(!searchOutput); + }, [searchOutput]); + + return ( + <> + + + + + + + + ); +} +export const TaskDisplayContainerFlat = ({task, me}) => { + const [viewBrowserScript, setViewBrowserScript] = React.useState(true); + const [commandID, setCommandID] = React.useState(0); + const [searchOutput, setSearchOutput] = React.useState(false); + const [selectAllOutput, setSelectAllOutput] = React.useState(false); + const responseRef = React.useRef(null); + useEffect( () => { + setCommandID(task.command === null ? 0 : task.command.id); + setSearchOutput(false); + setSelectAllOutput(false); + setViewBrowserScript(true); + }, [task.command, task.id]); + const toggleViewBrowserScript = React.useCallback( () => { + setViewBrowserScript(!viewBrowserScript); + }, [viewBrowserScript]); + const toggleSelectAllOutput = React.useCallback( () => { + setSelectAllOutput(!selectAllOutput); + }, [selectAllOutput]); + const toggleOpenSearch = React.useCallback( () => { + setSearchOutput(!searchOutput); + }, [searchOutput]); + + return ( +
+ + +
+ + ) +} +export const TaskDisplayContainerConsole = ({task, me}) => { + const [viewBrowserScript, setViewBrowserScript] = React.useState(true); + const [commandID, setCommandID] = React.useState(0); + const [searchOutput, setSearchOutput] = React.useState(false); + const [selectAllOutput, setSelectAllOutput] = React.useState(false); + const responseRef = React.useRef(null); + useEffect( () => { + setCommandID(task.command === null ? 0 : task.command.id); + }, [task.command]); + const toggleViewBrowserScript = React.useCallback( () => { + setViewBrowserScript(!viewBrowserScript); + }, [viewBrowserScript]); + const toggleSelectAllOutput = React.useCallback( () => { + setSelectAllOutput(!selectAllOutput); + }, [selectAllOutput]); + const toggleOpenSearch = React.useCallback( () => { + setSearchOutput(!searchOutput); + }, [searchOutput]); + + return ( + <> + + + + ); +} + +// the base64 decode function to handle unicode was pulled from the following stack overflow post +// https://stackoverflow.com/a/30106551 +function b64DecodeUnicode(str) { + // Going backwards: from bytestream, to percent-encoding, to original string. + //console.log("decoding", str); + try{ + return decodeURIComponent(window.atob(str)); + }catch(error){ + //console.log("Failed to base64 decode response", error) + return atob(str); + } +} +const SpeedDialDisplayGeneric = ({toggleViewBrowserScript, toggleSelectAllOutput, + toggleOpenSearch, taskData, viewAllOutput, me, + responseRef, style, fabStyle, viewBrowserScript}) => { + const theme = useTheme(); + const [task, setTask] = React.useState(taskData || {}); + const [openSpeedDial, setOpenSpeedDial] = React.useState(false); + const [openTaskTagDialog, setOpenTaskTagDialog] = React.useState(false); + const [openCommentDialog, setOpenCommentDialog] = React.useState(false); + const [openParametersDialog, setOpenParametersDialog] = React.useState(false); + const [openTokenDialog, setOpenTokenDialog] = React.useState(false); + const [openStdoutStderrDialog, setOpenStdoutStderrDialog] = React.useState(false); + const [openDeleteTaskDialog, setOpenDeleteTaskDialog] = React.useState(false); + const [openOpsecDialog, setOpenOpsecDialog] = React.useState({open: false, view: "pre"}); + const [downloadResponses] = useLazyQuery(getAllResponsesLazyQuery, { + fetchPolicy: "network-only", + onCompleted: (data) => { + const output = data.response.reduce( (prev, cur) => { + return prev + b64DecodeUnicode(cur.response); + }, b64DecodeUnicode("")); + downloadFileFromMemory(output, "task_" + task.id + ".txt"); + }, + onError: (data) => { + + } + }); + React.useEffect( () => { + setTask(taskData); + }, [taskData.id, taskData.token, taskData.original_params, taskData.opsec_pre_blocked, taskData.opsec_pre_bypassed, taskData.opsec_post_blocked, taskData.opsec_post_bypassed]) + const onDownloadResponses = () => { + downloadResponses({variables: {task_id: task.id}}); + setOpenSpeedDial(false); + }; + const copyToClipboard = () => { + let result = copyStringToClipboard(task.original_params); + if(result){ + snackActions.success("Copied text!"); + }else{ + snackActions.error("Failed to copy text"); + } + setOpenSpeedDial(false); + }; + const [reissueTask] = useMutation(ReissueTaskMutationGQL, { + onCompleted: data => { + if(data.reissue_task.status === "success"){ + snackActions.success("Successfully re-issued task to Mythic"); + }else{ + snackActions.error("Failed to re-issue task to Mythic: " + data.reissue_task.error); + } + }, + onError: data => { + console.log(data); + snackActions.error("Failed to re-issue task: " + data); + } + }); + const [reissueTaskHandler] = useMutation(ReissueTaskHandlerMutationGQL, { + onCompleted: data => { + if(data.reissue_task_handler.status === "success"){ + snackActions.success("Successfully resubmitted task for handling"); + }else{ + snackActions.warning("Failed to resubmit task for handling: " + data.reissue_task_handler.error); + } + + }, + onError: data => { + console.log(data); + snackActions.error("Error resubmitting task for handling: " + data); + } + }); + const onDownloadImageClickPng = () => { + // we calculate a transform for the nodes so that all nodes are visible + // we then overwrite the transform of the `.react-flow__viewport` element + // with the style option of the html-to-image library + snackActions.info("Saving image to png..."); + (async () => { + const canvas = await html2canvas(responseRef.current); + const image = canvas.toDataURL("image/png", 1.0); + const fakeLink = window.document.createElement("a"); + fakeLink.style = "display:none;"; + fakeLink.download = "task_output.png"; + + fakeLink.href = image; + + document.body.appendChild(fakeLink); + fakeLink.click(); + document.body.removeChild(fakeLink); + + fakeLink.remove(); + + })(); + }; + const onReissueTask = () => { + reissueTask({variables: {task_id: task.id}}); + } + const onReissueTaskHandler = () => { + reissueTaskHandler({variables: {task_id: task.id}}); + } + return ( + + {setOpenSpeedDial(false);}} style={{zIndex: 2, position: "absolute"}}/> + {openTaskTagDialog ? + ({setOpenTaskTagDialog(false);}} + innerDialog={{setOpenTaskTagDialog(false);}} />} + />) : null} + {openCommentDialog ? + ({setOpenCommentDialog(false);}} + innerDialog={{setOpenCommentDialog(false);}} />} + />) : null + } + {openParametersDialog ? + ({setOpenParametersDialog(false);}} + innerDialog={{setOpenParametersDialog(false);}} />} + />) : null + } + {openTokenDialog ? + ({setOpenTokenDialog(false);}} + innerDialog={{setOpenTokenDialog(false);}} />} + />) : null + } + {openOpsecDialog.open ? + ({setOpenOpsecDialog({...openOpsecDialog, open: false});}} + innerDialog={{setOpenOpsecDialog({...openOpsecDialog, open: false});}} />} + />) : null + } + {openDeleteTaskDialog ? + ({setOpenDeleteTaskDialog(false);}} + innerDialog={{setOpenDeleteTaskDialog(false);}} />} + />) : null + } + + {openStdoutStderrDialog ? + ({setOpenStdoutStderrDialog(false);}} + innerDialog={{setOpenStdoutStderrDialog(false);}} />} + />) : null + } + } + style={{...style}} + onClick={()=>{setOpenSpeedDial(!openSpeedDial)}} + FabProps={{...fabStyle, color: "secondary", size: "small", sx: {minHeight: "30px", height: "30px", width: "30px"}}} + open={openSpeedDial} + direction="right" + > + : } + arrow + tooltipPlacement={"top"} + tooltipTitle={"Toggle BrowserScript"} + onClick={() => {toggleViewBrowserScript();setOpenSpeedDial(false);}} + /> + : } + arrow + tooltipPlacement={"top"} + tooltipTitle={viewAllOutput ? "View Paginated Output" : "View All Output"} + onClick={() => {toggleSelectAllOutput();setOpenSpeedDial(false);}} + /> + } + arrow + tooltipPlacement={"top"} + tooltipTitle={"Search Output"} + onClick={() => {toggleOpenSearch();setOpenSpeedDial(false);}} + /> + } + arrow + tooltipPlacement={"top"} + tooltipTitle={"Download output"} + onClick={onDownloadResponses} + /> + } + arrow + tooltipPlacement={"top"} + tooltipTitle={"Download screenshot of output"} + onClick={onDownloadImageClickPng} + /> + } + arrow + tooltipPlacement={"top"} + tooltipTitle={"Edit Tags"} + onClick={()=>{setOpenTaskTagDialog(true);setOpenSpeedDial(false);}} + /> + } + arrow + tooltipPlacement={"top"} + tooltipTitle={"Open Task in New Window"} + onClick={()=> {window.open('/new/task/' + task.display_id, "_blank")}} + /> + } + arrow + tooltipPlacement={"top"} + tooltipTitle={"Copy original params to clipboard"} + onClick={copyToClipboard} + /> + } + arrow + tooltipPlacement={"top"} + tooltipTitle={"Edit Comment"} + onClick={()=>{setOpenCommentDialog(true);setOpenSpeedDial(false);}} + /> + } + arrow + tooltipPlacement={"top"} + tooltipTitle={"View All Parameters"} + onClick={()=>{setOpenParametersDialog(true);setOpenSpeedDial(false);}} + /> + } + arrow + tooltipPlacement={"top"} + tooltipTitle={"View Stdout/Stderr of Task"} + onClick={()=>{setOpenStdoutStderrDialog(true);setOpenSpeedDial(false);}} + /> + {task.opsec_pre_blocked === null ? null : ( task.opsec_pre_bypassed === false ? ( + } + arrow + tooltipPlacement={"top"} + tooltipTitle={"Submit OPSEC PreCheck Bypass Request"} + onClick={()=>{setOpenOpsecDialog({open: true, view: "pre"});setOpenSpeedDial(false);}} + /> + ): ( + } + arrow + tooltipPlacement={"top"} + tooltipTitle={"View OPSEC PreCheck Data"} + onClick={()=>{setOpenOpsecDialog({open: true, view: "pre"});setOpenSpeedDial(false);}} + /> + ) + ) + } + {task.opsec_post_blocked === null ? null : ( task.opsec_post_bypassed === false ? ( + } + arrow + tooltipPlacement={"top"} + tooltipTitle={"Submit OPSEC PostCheck Bypass Request"} + onClick={()=>{setOpenOpsecDialog({open: true, view: "post"});setOpenSpeedDial(false);}} + /> + ): ( + } + arrow + tooltipPlacement={"top"} + tooltipTitle={"View OPSEC PostCheck Data"} + onClick={()=>{setOpenOpsecDialog({open: true, view: "post"});setOpenSpeedDial(false);}} + /> + ) + ) + } + {task.status.toLowerCase().includes("submitted") ? ( + } + arrow + tooltipPlacement={"top"} + tooltipTitle={"Delete Task Before Submission"} + onClick={() => {setOpenDeleteTaskDialog(true);setOpenSpeedDial(false);}} + /> + ) : ( + task.status.toLowerCase().includes("delegating tasks") ? ( + } + arrow + tooltipPlacement={"top"} + tooltipTitle={"Delete Task Before Submission"} + onClick={() => {setOpenDeleteTaskDialog(true);setOpenSpeedDial(false);}} + /> + ) : null )} + {task.token === null ? null : ( + } + arrow + tooltipPlacement={"top"} + tooltipTitle={"View Token Information"} + onClick={()=>{setOpenTokenDialog(true);setOpenSpeedDial(false);}} + /> + )} + {task.status.toLowerCase().includes("error: container") ? ( + } + arrow + tooltipPlacement={"top"} + tooltipTitle={"Resubmit Tasking"} + onClick={onReissueTask} + /> + ) : null} + {task.status.toLowerCase().includes("error: task") ? ( + } + arrow + tooltipPlacement={"top"} + tooltipTitle={"Resubmit Task Handler"} + onClick={onReissueTaskHandler} + /> + ):null} + + + + ) +} diff --git a/hasura-docker/metadata/tables.yaml b/hasura-docker/metadata/tables.yaml index 59aa46959..0841c173a 100644 --- a/hasura-docker/metadata/tables.yaml +++ b/hasura-docker/metadata/tables.yaml @@ -6405,6 +6405,22 @@ check: {} set: comment_operator_id: x-hasura-User-Id + delete_permissions: + - role: mythic_admin + permission: + filter: + operator_id: + _eq: X-Hasura-User-Id + - role: operation_admin + permission: + filter: + operator_id: + _eq: X-Hasura-User-Id + - role: operator + permission: + filter: + operator_id: + _eq: X-Hasura-User-Id - table: name: taskartifact schema: public From 23089157ba9484bf1e94b6b4c06d073cb46424b3 Mon Sep 17 00:00:00 2001 From: Simon Clow Date: Tue, 9 Apr 2024 01:03:00 +0100 Subject: [PATCH 2/3] Initial commit for task deletion/cancellation --- .../pages/Callbacks/TaskDeleteDialog.js | 99 +++++++++++++++++++ .../pages/Callbacks/TaskDisplayContainer.js | 20 +++- 2 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 MythicReactUI/src/components/pages/Callbacks/TaskDeleteDialog.js diff --git a/MythicReactUI/src/components/pages/Callbacks/TaskDeleteDialog.js b/MythicReactUI/src/components/pages/Callbacks/TaskDeleteDialog.js new file mode 100644 index 000000000..cef5a3c0a --- /dev/null +++ b/MythicReactUI/src/components/pages/Callbacks/TaskDeleteDialog.js @@ -0,0 +1,99 @@ +import React, {useState} from 'react'; +import Button from '@mui/material/Button'; +import DialogActions from '@mui/material/DialogActions'; +import DialogContent from '@mui/material/DialogContent'; +import DialogTitle from '@mui/material/DialogTitle'; +import MythicTextField from '../../MythicComponents/MythicTextField'; +import {useQuery, gql, useMutation} from '@apollo/client'; +import LinearProgress from '@mui/material/LinearProgress'; + +const updateStatusMutation = gql` +mutation updateStatus($task_id: Int!, $status: String) { + update_task(where: {id: {_eq: $task_id}}, _set: {status: $status}) { + returning { + status + commentOperator { + username + } + id + } + } +} +`; + +const updateStatusMutation_by_pk = gql` +mutation updateStatus ($task_id: Int!, $status: String) { + update_task_by_pk(pk_columns: {id: $task_id}, _set: {status: $status}) { + status + commentOperator { + username + } + id + } + } +`; + + +const getStatusQuery= gql` +query getStatusQuery ($task_id: Int!) { + task_by_pk(id: $task_id) { + status + commentOperator { + username + } + id + } +} +`; + + +export function TaskDeleteDialog(props) { + const [status, setStatus] = useState(""); + const { loading, error } = useQuery(getStatusQuery, { + variables: {task_id: props.task_id}, + onCompleted: data => { + //setStatus(data.task_by_pk.status) + setStatus("error:cancelled"); + }, + fetchPolicy: "network-only" + }); + + const [updateStatus] = useMutation(updateStatusMutation, { + update: (cache, {data}) => { + console.log(data); + } + }); + if (loading) { + return ; + } + if (error) { + console.error(error); + return
Error!
; + } + const onCommitSubmit = () => { + updateStatus({variables: {task_id: props.task_id, status: status}}); + props.onClose(); + } + const onChange = (name, value, error) => { + setStatus(value); + } + + return ( + + Cancel Task + + Are you sure? + + + + + + + + ); +} + diff --git a/MythicReactUI/src/components/pages/Callbacks/TaskDisplayContainer.js b/MythicReactUI/src/components/pages/Callbacks/TaskDisplayContainer.js index 4fff859e7..9cd417a54 100644 --- a/MythicReactUI/src/components/pages/Callbacks/TaskDisplayContainer.js +++ b/MythicReactUI/src/components/pages/Callbacks/TaskDisplayContainer.js @@ -11,6 +11,8 @@ import {useTheme} from '@mui/material/styles'; import LockIcon from '@mui/icons-material/Lock'; import LockOpenIcon from '@mui/icons-material/LockOpen'; import {TaskOpsecDialog} from './TaskOpsecDialog'; +import BlockIcon from '@mui/icons-material/Block'; +import {TaskDeleteDialog} from './TaskDeleteDialog'; import {TaskViewParametersDialog} from './TaskViewParametersDialog'; import {TaskViewStdoutStderrDialog} from './TaskViewStdoutStderrDialog'; import {snackActions} from '../../utilities/Snackbar'; @@ -220,6 +222,7 @@ const SpeedDialDisplayGeneric = ({toggleViewBrowserScript, toggleSelectAllOutput const [openParametersDialog, setOpenParametersDialog] = React.useState(false); const [openTokenDialog, setOpenTokenDialog] = React.useState(false); const [openStdoutStderrDialog, setOpenStdoutStderrDialog] = React.useState(false); + const [openDeleteTaskDialog, setOpenDeleteTaskDialog] = React.useState(false); const [openOpsecDialog, setOpenOpsecDialog] = React.useState({open: false, view: "pre"}); const [downloadResponses] = useLazyQuery(getAllResponsesLazyQuery, { fetchPolicy: "network-only", @@ -336,6 +339,12 @@ const SpeedDialDisplayGeneric = ({toggleViewBrowserScript, toggleSelectAllOutput innerDialog={{setOpenOpsecDialog({...openOpsecDialog, open: false});}} />} />) : null } + {openDeleteTaskDialog ? + ({setOpenDeleteTaskDialog(false);}} + innerDialog={{setOpenDeleteTaskDialog(false);}} />} + />) : null + } {openStdoutStderrDialog ? (} + arrow + tooltipPlacement={"top"} + tooltipTitle={"Block Task Before Submission"} + onClick={() => {setOpenDeleteTaskDialog(true);setOpenSpeedDial(false);}} + /> + )} {task.token === null ? null : ( } @@ -498,4 +516,4 @@ const SpeedDialDisplayGeneric = ({toggleViewBrowserScript, toggleSelectAllOutput ) -} \ No newline at end of file +} From d6e9b7881aa162e58c9fdfed68e2ca6a99867ab2 Mon Sep 17 00:00:00 2001 From: Simon Clow Date: Mon, 22 Apr 2024 18:18:09 +0100 Subject: [PATCH 3/3] Changing from deleting tasks, to clearing them following @its-a-feature feedback --- .../pages/Callbacks/TaskDeleteDialog.js | 64 ++++++++++--------- .../pages/Callbacks/TaskDisplayContainer.js | 19 ++++-- 2 files changed, 48 insertions(+), 35 deletions(-) diff --git a/MythicReactUI/src/components/pages/Callbacks/TaskDeleteDialog.js b/MythicReactUI/src/components/pages/Callbacks/TaskDeleteDialog.js index cef5a3c0a..c18ceb94c 100644 --- a/MythicReactUI/src/components/pages/Callbacks/TaskDeleteDialog.js +++ b/MythicReactUI/src/components/pages/Callbacks/TaskDeleteDialog.js @@ -7,32 +7,23 @@ import MythicTextField from '../../MythicComponents/MythicTextField'; import {useQuery, gql, useMutation} from '@apollo/client'; import LinearProgress from '@mui/material/LinearProgress'; -const updateStatusMutation = gql` -mutation updateStatus($task_id: Int!, $status: String) { - update_task(where: {id: {_eq: $task_id}}, _set: {status: $status}) { - returning { - status - commentOperator { - username - } - id - } +const deleteTaskByPkMutation = gql` +mutation createTask($callback_display_id: Int!, $task_id: String!, $parent_task_id: Int) { + createTask( + callback_id: $callback_display_id, + command: "clear", + params: $task_id, + token_id: null, + is_interactive_task: false, + interactive_task_type: null, + parent_task_id: $parent_task_id, + tasking_location: "scripting", + files: null){ + id } } `; -const updateStatusMutation_by_pk = gql` -mutation updateStatus ($task_id: Int!, $status: String) { - update_task_by_pk(pk_columns: {id: $task_id}, _set: {status: $status}) { - status - commentOperator { - username - } - id - } - } -`; - const getStatusQuery= gql` query getStatusQuery ($task_id: Int!) { @@ -42,27 +33,39 @@ query getStatusQuery ($task_id: Int!) { username } id + display_id + agent_task_id + callback_id + parent_task_id + callback { + display_id + } } } `; - export function TaskDeleteDialog(props) { const [status, setStatus] = useState(""); + const [callback_display_id, setCallbackID] = useState(""); + const [task_id, setTaskID] = useState(""); + const [parent_task_id, setParentTaskID] = useState(""); const { loading, error } = useQuery(getStatusQuery, { variables: {task_id: props.task_id}, onCompleted: data => { - //setStatus(data.task_by_pk.status) - setStatus("error:cancelled"); + setCallbackID(data.task_by_pk.callback.display_id); + setTaskID(data.task_by_pk.display_id.toString()); + setParentTaskID(data.task_by_pk.parent_task_id); + + setStatus("Warning: This will 'clear' the task with display id of " + data.task_by_pk.display_id); }, fetchPolicy: "network-only" }); + + const [deleteTask] = useMutation(deleteTaskByPkMutation,{ + variables: {callback_display_id: callback_display_id , task_id: task_id, parent_task_id: parent_task_id} - const [updateStatus] = useMutation(updateStatusMutation, { - update: (cache, {data}) => { - console.log(data); - } }); + if (loading) { return ; } @@ -71,7 +74,8 @@ export function TaskDeleteDialog(props) { return
Error!
; } const onCommitSubmit = () => { - updateStatus({variables: {task_id: props.task_id, status: status}}); + //alert(callback_display_id + ":" + props.task_id + ":" + parent_task_id); + deleteTask({variables: {callback_display_id: callback_display_id , task_id: task_id, parent_task_id: parent_task_id}}); props.onClose(); } const onChange = (name, value, error) => { diff --git a/MythicReactUI/src/components/pages/Callbacks/TaskDisplayContainer.js b/MythicReactUI/src/components/pages/Callbacks/TaskDisplayContainer.js index 9cd417a54..4e7270556 100644 --- a/MythicReactUI/src/components/pages/Callbacks/TaskDisplayContainer.js +++ b/MythicReactUI/src/components/pages/Callbacks/TaskDisplayContainer.js @@ -11,7 +11,7 @@ import {useTheme} from '@mui/material/styles'; import LockIcon from '@mui/icons-material/Lock'; import LockOpenIcon from '@mui/icons-material/LockOpen'; import {TaskOpsecDialog} from './TaskOpsecDialog'; -import BlockIcon from '@mui/icons-material/Block'; +import DeleteForeverIcon from '@mui/icons-material/DeleteForever'; import {TaskDeleteDialog} from './TaskDeleteDialog'; import {TaskViewParametersDialog} from './TaskViewParametersDialog'; import {TaskViewStdoutStderrDialog} from './TaskViewStdoutStderrDialog'; @@ -476,15 +476,24 @@ const SpeedDialDisplayGeneric = ({toggleViewBrowserScript, toggleSelectAllOutput ) ) } - {task.status === "submitted" ? "submitted" : ( + {task.status.toLowerCase().includes("submitted") ? ( } + icon={} arrow tooltipPlacement={"top"} - tooltipTitle={"Block Task Before Submission"} + tooltipTitle={"Delete Task Before Submission"} onClick={() => {setOpenDeleteTaskDialog(true);setOpenSpeedDial(false);}} /> - )} + ) : ( + task.status.toLowerCase().includes("delegating tasks") ? ( + } + arrow + tooltipPlacement={"top"} + tooltipTitle={"Delete Task Before Submission"} + onClick={() => {setOpenDeleteTaskDialog(true);setOpenSpeedDial(false);}} + /> + ) : null )} {task.token === null ? null : ( }