From c9f8c3c28bd88ca35072ffe24f16c19e4b27c2e9 Mon Sep 17 00:00:00 2001 From: Domingo Dirutigliano Date: Sat, 14 Dec 2024 13:49:38 +0100 Subject: [PATCH] fixes + frontend UI to manage groups --- backend/routes/groups.py | 2 +- backend/workers/group_manager.py | 10 +- client/exploitfarm/models/enums.py | 2 +- client/exploitfarm/models/flags.py | 3 + client/exploitfarm/models/groups.py | 2 +- .../src/components/elements/ExploitsBar.tsx | 7 +- .../modals/AttackExecutionDetailModal.tsx | 9 +- .../components/modals/DeleteGroupModal.tsx | 36 +++ .../components/modals/EditExploitModal.tsx | 2 +- .../src/components/modals/EditGroupModal.tsx | 70 ++++++ .../components/modals/ExploitDetailModal.tsx | 11 +- .../src/components/screens/OptionScreen.tsx | 12 +- .../src/components/tables/ExploitTable.tsx | 5 +- frontend/src/components/tables/GroupTable.tsx | 57 +++++ frontend/src/utils/backend_types.ts | 233 +++++++++++++++++- frontend/src/utils/net.ts | 2 +- frontend/src/utils/queries.tsx | 31 +++ frontend/src/utils/types.ts | 1 + 18 files changed, 476 insertions(+), 19 deletions(-) create mode 100644 frontend/src/components/modals/DeleteGroupModal.tsx create mode 100644 frontend/src/components/modals/EditGroupModal.tsx create mode 100644 frontend/src/components/tables/GroupTable.tsx diff --git a/backend/routes/groups.py b/backend/routes/groups.py index 3b30e99..fa957f6 100644 --- a/backend/routes/groups.py +++ b/backend/routes/groups.py @@ -23,7 +23,7 @@ async def result(result: sqla.Row[Tuple[AttackGroup, AttackExecution]]): members = await redis_conn.smembers(f"group:{group.id}:members") status = GroupStatus.active if members is None or len(members) == 0: - status = GroupStatus.unactive + status = GroupStatus.inactive members = set() return GroupDTO( **json_like(group, mode="python", unset=True), diff --git a/backend/workers/group_manager.py b/backend/workers/group_manager.py index ffd83d0..b999538 100644 --- a/backend/workers/group_manager.py +++ b/backend/workers/group_manager.py @@ -98,6 +98,11 @@ def current_tick_calc(): raise Exception("Attack not started yet") return math.floor((this_time - start_time).total_seconds() / g.configuration.TICK_DURATION) +async def set_exploit_stopped(exploit_id: str): + redis_key = f"exploit:{exploit_id}:stopped" + await redis_conn.set(redis_key, pickle.dumps(datetime_now() + timedelta(seconds=5))) # 5 seconds for submitting the remaining flags + await redis_conn.publish(redis_channels.exploit, "update") + def calc_round_time_available(): #Needed to calculate the initial time based on attack schedule logic this_time = datetime_now() @@ -282,6 +287,7 @@ async def attack_run_actions(self): if len(client_status) == 0: if self.running: + await set_exploit_stopped(self.group.exploit_id) self.running = False return # No client available, need someone to join @@ -336,8 +342,8 @@ def trigger_next_loop(self, data="trigger"): async def handle_request(self, request: GroupResponseEvent): match request.event: case GroupEventResponseType.SET_RUNNING_STATUS: - if request.data["running"] is False: - await redis_conn.set(f"exploit:{self.group.exploit_id}:stopped", pickle.dumps(datetime_now())) + if not request.data["running"]: + await set_exploit_stopped(self.group.exploit_id) self.running = request.data["running"] await self.send_running_status() await self.loop_reset() diff --git a/client/exploitfarm/models/enums.py b/client/exploitfarm/models/enums.py index e99a82e..93c2f8c 100644 --- a/client/exploitfarm/models/enums.py +++ b/client/exploitfarm/models/enums.py @@ -17,7 +17,7 @@ class AuthStatus(Enum): class GroupStatus(Enum): active = 'active' - unactive = 'unactive' + inactive = 'inactive' class FlagStatus(Enum): ok = 'ok' diff --git a/client/exploitfarm/models/flags.py b/client/exploitfarm/models/flags.py index 5747d2d..e787727 100644 --- a/client/exploitfarm/models/flags.py +++ b/client/exploitfarm/models/flags.py @@ -1,6 +1,7 @@ from exploitfarm.models.enums import FlagStatus, AttackExecutionStatus from pydantic import AwareDatetime from exploitfarm.models.dbtypes import FlagID, AttackExecutionID, TeamID, ExploitID, ClientID, ExploitSourceID +from exploitfarm.models.dbtypes import AttackGroupID from pydantic import BaseModel, Field, AliasChoices class FlagDTOSmall(BaseModel): @@ -18,6 +19,7 @@ class AttackExecutionDTO(BaseModel): target: TeamID|None = Field(None, validation_alias=AliasChoices('target_id')) exploit: ExploitID|None = Field(None, validation_alias=AliasChoices('exploit_id')) executed_by: ClientID|None = Field(None, validation_alias=AliasChoices('executed_by_id')) + executed_by_group: AttackGroupID|None = Field(None, validation_alias=AliasChoices('executed_by_group_id')) flags: list[FlagDTOSmall] exploit_source: ExploitSourceID|None = Field(None, validation_alias=AliasChoices('exploit_source_id')) @@ -30,6 +32,7 @@ class FlagDTOAttackDetails(BaseModel): target: TeamID|None = Field(None, validation_alias=AliasChoices('target_id')) exploit: ExploitID|None = Field(None, validation_alias=AliasChoices('exploit_id')) executed_by: ClientID|None = Field(None, validation_alias=AliasChoices('executed_by_id')) + executed_by_group: AttackGroupID|None = Field(None, validation_alias=AliasChoices('executed_by_group_id')) exploit_source: ExploitSourceID|None = Field(None, validation_alias=AliasChoices('exploit_source_id')) class FlagDTO(BaseModel): diff --git a/client/exploitfarm/models/groups.py b/client/exploitfarm/models/groups.py index 8519116..bbec8de 100644 --- a/client/exploitfarm/models/groups.py +++ b/client/exploitfarm/models/groups.py @@ -31,7 +31,7 @@ class GroupDTO(BaseModel): members: list[str] = [] exploit: ExploitID = Field(validation_alias=AliasChoices('exploit_id')) last_attack_at: AwareDatetime|None = None - status: GroupStatus = GroupStatus.unactive + status: GroupStatus = GroupStatus.inactive commit: UUID|Literal["latest"] = Field("latest", validation_alias=AliasChoices('commit_id')) class Config: diff --git a/frontend/src/components/elements/ExploitsBar.tsx b/frontend/src/components/elements/ExploitsBar.tsx index 63a605d..253cc37 100644 --- a/frontend/src/components/elements/ExploitsBar.tsx +++ b/frontend/src/components/elements/ExploitsBar.tsx @@ -1,4 +1,4 @@ -import { exploitsQuery, useClientSolver, useExtendedExploitSolver } from "@/utils/queries" +import { exploitsQuery, useClientSolver, useExtendedExploitSolver, useGroupSolver } from "@/utils/queries" import { Box, Divider, Flex, Paper, ScrollArea, Space, Tooltip } from "@mantine/core" import { useState } from "react" import { GoDotFill } from "react-icons/go" @@ -9,6 +9,7 @@ export const ExploitBar = () => { const exploits = exploitsQuery() const extendedExploitSolver = useExtendedExploitSolver() const clientSolver = useClientSolver() + const groupSolver = useGroupSolver() const [modalExploitId, setModalExploitId] = useState(null) return @@ -26,7 +27,7 @@ export const ExploitBar = () => { else return b.name.localeCompare(a.name) } ).map((expl) => setModalExploitId(expl.id)} className="transparency-on-hover" key={expl.id}> - + @@ -35,7 +36,7 @@ export const ExploitBar = () => { {extendedExploitSolver(expl.id)} - {expl.status=="active"?"running":"ran"} by: {clientSolver(expl.last_execution_by)} + {expl.status=="active"?"running":"ran"} by: {expl.last_execution_group_by?`${groupSolver(expl.last_execution_group_by)}`:clientSolver(expl.last_execution_by)} diff --git a/frontend/src/components/modals/AttackExecutionDetailModal.tsx b/frontend/src/components/modals/AttackExecutionDetailModal.tsx index 484e3d0..2ca68e6 100644 --- a/frontend/src/components/modals/AttackExecutionDetailModal.tsx +++ b/frontend/src/components/modals/AttackExecutionDetailModal.tsx @@ -1,4 +1,4 @@ -import { attacksQuery, exploitsSourcesQuery, useClientSolver, useExtendedExploitSolver, useTeamSolver } from "@/utils/queries"; +import { attacksQuery, exploitsSourcesQuery, useClientSolver, useExtendedExploitSolver, useGroupSolver, useTeamSolver } from "@/utils/queries"; import { useGlobalStore } from "@/utils/stores"; import { Alert, Box, Modal, ScrollArea, Space, Title } from "@mantine/core" import { showNotification } from "@mantine/notifications"; @@ -27,6 +27,7 @@ export const AttackExecutionDetailsModal = (props:{ opened:boolean, close:()=>vo const isUsedSourceLatest = sourceQuery.data?.length??0 > 0 ? sourceQuery.data?.[0]?.id == usedSource?.id : false const setLoading = useGlobalStore((store) => store.setLoader) const clientSolver = useClientSolver() + const groupSolver = useGroupSolver() const teamSolver = useTeamSolver() const extendedExploitSolver = useExtendedExploitSolver() const scollRef = useRef() @@ -51,6 +52,12 @@ export const AttackExecutionDetailsModal = (props:{ opened:boolean, close:()=>vo Executed by {clientSolver(attack.executed_by)} + {attack.executed_by_group? + Executed by group + {groupSolver(attack.executed_by_group)} + :null + + } Target Team {teamSolver(attack.target)} diff --git a/frontend/src/components/modals/DeleteGroupModal.tsx b/frontend/src/components/modals/DeleteGroupModal.tsx new file mode 100644 index 0000000..232072b --- /dev/null +++ b/frontend/src/components/modals/DeleteGroupModal.tsx @@ -0,0 +1,36 @@ +import { useQueryClient } from "@tanstack/react-query" +import { YesOrNoModal } from "./YesOrNoModal" +import { Title } from "@mantine/core" +import { deleteGroup } from "@/utils/queries" +import { notifications } from "@mantine/notifications" +import { AttackGroup } from "@/utils/types" + + +export const DeleteGroupModal = ({ onClose, group }:{ onClose: () => void, group?: AttackGroup }) => { + const queryClient = useQueryClient() + + return Deleting an attack group} + onConfirm={()=>{ + if (group == null){ + onClose?.() + return + } + deleteGroup(group?.id).then(()=>{ + notifications.show({ title: "Group deleted", message: `The group ${group?.name} has been deleted successfully`, color: "green" }) + queryClient.invalidateQueries({ queryKey: ["groups"] }) + + }).catch((err)=>{ + notifications.show({ title: "Error deleting the group", message: `An error occurred while deleting the group ${group?.name}: ${err.message}`, color: "red" }) + }).finally(()=>{ onClose() }) + }} + size="xl" + message={ + <> + Are you sure you want to delete the group {group?.name}?
+ This action will stop the group if it is running! + + }/> +} \ No newline at end of file diff --git a/frontend/src/components/modals/EditExploitModal.tsx b/frontend/src/components/modals/EditExploitModal.tsx index 2ea7fb9..ae791db 100644 --- a/frontend/src/components/modals/EditExploitModal.tsx +++ b/frontend/src/components/modals/EditExploitModal.tsx @@ -66,7 +66,7 @@ export const EditExploitModal = ({ onClose, exploit }:{ onClose: ()=>void, explo })}> diff --git a/frontend/src/components/modals/EditGroupModal.tsx b/frontend/src/components/modals/EditGroupModal.tsx new file mode 100644 index 0000000..1dae25c --- /dev/null +++ b/frontend/src/components/modals/EditGroupModal.tsx @@ -0,0 +1,70 @@ +import { editGroup } from "@/utils/queries" +import { AttackGroup } from "@/utils/types" +import { Button, Group, Modal, TextInput } from "@mantine/core" +import { useForm } from "@mantine/form" +import { notifications } from "@mantine/notifications" +import { useQueryClient } from "@tanstack/react-query" +import { useEffect } from "react" + + +export const EditGroupModal = ({ onClose, group }:{ onClose: ()=>void, group?:AttackGroup }) => { + const form = useForm({ + initialValues: { + name: group?.name??"", + }, + validate: { + name: (value) => value == ""?"Name is required":undefined + } + }) + + const queryClient = useQueryClient() + + useEffect(() => { + form.setInitialValues({ + name: group?.name??"" + }) + form.reset() + }, [group]) + + return +
{ + if (group == null){ + onClose() + return + } + editGroup(group.id, data) + .then(()=>{ + notifications.show({ + title: `${group.name} Group edited!`, + message: "Group has been edited successfully!", + color: "green", + }) + queryClient.invalidateQueries({ queryKey: ["groups"] }) + }).catch((err) => { + notifications.show({ + title: "Error during editing!", + message: err.message??err??"Unknown error", + color: "red", + }) + }).finally(()=>{ onClose() }) + })}> + + + + + + + +
+} \ No newline at end of file diff --git a/frontend/src/components/modals/ExploitDetailModal.tsx b/frontend/src/components/modals/ExploitDetailModal.tsx index 6d8ad6c..5d1ef6f 100644 --- a/frontend/src/components/modals/ExploitDetailModal.tsx +++ b/frontend/src/components/modals/ExploitDetailModal.tsx @@ -1,4 +1,4 @@ -import { exploitsQuery, statsQuery, useClientSolver, useServiceSolverByExploitId } from "@/utils/queries"; +import { exploitsQuery, statsQuery, useClientSolver, useGroupMapping, useServiceSolverByExploitId } from "@/utils/queries"; import { useGlobalStore } from "@/utils/stores"; import { Box, Modal, Space, Title } from "@mantine/core" import { showNotification } from "@mantine/notifications"; @@ -20,6 +20,7 @@ export const ExploitDetailModal = (props:{ opened:boolean, close:()=>void, explo const stats = statsQuery() const setLoading = useGlobalStore((store) => store.setLoader) const clientSolver = useClientSolver() + const groupsMapping = useGroupMapping() const serviceSolver = useServiceSolverByExploitId() useEffect(() => { @@ -45,6 +46,8 @@ export const ExploitDetailModal = (props:{ opened:boolean, close:()=>void, explo const noFlagsAttacks = exploit?(stats.data?.ticks.reduce((a, b) => a+(b.exploits[exploit.id]?.attacks?.noflags??0), 0)??0):0 const totAttacks = exploit?(stats.data?.ticks.reduce((a, b) => a+(b.exploits[exploit.id]?.attacks?.tot??0), 0)??0):0 + const group = groupsMapping[exploit?.last_execution_group_by??""] + return @@ -58,8 +61,12 @@ export const ExploitDetailModal = (props:{ opened:boolean, close:()=>void, explo
Last execution - by {exploit.last_execution_by?clientSolver(exploit.last_execution_by):"unknown"} at {exploit.last_update?getDateFormatted(exploit.last_update):"unknown"} + by {exploit.last_execution_by?group?group.name:clientSolver(exploit.last_execution_by):"unknown"} at {exploit.last_update?getDateFormatted(exploit.last_update):"unknown"} + {group && + Group Members + {group.members.map(ele => clientSolver(ele)).join(", ")} ({group.members.length}) + } Valid Flags diff --git a/frontend/src/components/screens/OptionScreen.tsx b/frontend/src/components/screens/OptionScreen.tsx index 0ed9cbd..a606564 100644 --- a/frontend/src/components/screens/OptionScreen.tsx +++ b/frontend/src/components/screens/OptionScreen.tsx @@ -7,6 +7,7 @@ import { AddEditServiceModal } from "../modals/AddEditServiceModal"; import { useState } from "react"; import { useSettingsStore } from "@/utils/stores"; import { notifications } from "@mantine/notifications"; +import { GroupTable } from "../tables/GroupTable"; export const OptionScreen = () => { @@ -18,19 +19,24 @@ export const OptionScreen = () => { - + + Attack Groups + + + + <span>Services</span><Box style={{flexGrow:1}} /><AddButton onClick={()=>setAddNewServiceModalOpen(true)} /> - + Clients - + setAddNewServiceModalOpen(false)} /> diff --git a/frontend/src/components/tables/ExploitTable.tsx b/frontend/src/components/tables/ExploitTable.tsx index b8cb8f8..8830765 100644 --- a/frontend/src/components/tables/ExploitTable.tsx +++ b/frontend/src/components/tables/ExploitTable.tsx @@ -1,4 +1,4 @@ -import { exploitsQuery, useClientSolver, useServiceSolver } from "@/utils/queries"; +import { exploitsQuery, useClientSolver, useGroupSolver, useServiceSolver } from "@/utils/queries"; import { getDateSmallFormatted } from "@/utils/time"; import { Box, Table, Title } from "@mantine/core"; import { DeleteButton, EditButton } from "@/components/inputs/Buttons"; @@ -14,6 +14,7 @@ export const ExploitTable = () => { const exploits = exploitsQuery() const getServiceName = useServiceSolver() const getClientName = useClientSolver() + const getGroupName = useGroupSolver() const [deleteExploit, setDeleteExploit] = useState() const [editExploit, setEditExploit] = useState() @@ -24,7 +25,7 @@ export const ExploitTable = () => { {expl.name} {getServiceName(expl.service)} {expl.language} - {getClientName(expl.last_execution_by)} + {expl.last_execution_group_by?`${getGroupName(expl.last_execution_group_by)} (group)`:getClientName(expl.last_execution_by)} {expl.last_update?getDateSmallFormatted(expl.last_update):"Not started"} setEditExploit(expl)} /> setDeleteExploit(expl)} /> diff --git a/frontend/src/components/tables/GroupTable.tsx b/frontend/src/components/tables/GroupTable.tsx new file mode 100644 index 0000000..cb78ff8 --- /dev/null +++ b/frontend/src/components/tables/GroupTable.tsx @@ -0,0 +1,57 @@ +import { groupsQuery, useClientSolver, useExtendedExploitSolver } from "@/utils/queries"; +import { getDateSmallFormatted } from "@/utils/time"; +import { Box, Table, Title } from "@mantine/core"; +import { DeleteButton, EditButton } from "@/components/inputs/Buttons"; +import { AttackGroup } from "@/utils/types"; +import { useState } from "react"; +import { StatusPoint } from "../elements/ExploitsBar"; +import { DeleteGroupModal } from "../modals/DeleteGroupModal"; +import { EditGroupModal } from "../modals/EditGroupModal"; + + +export const GroupTable = () => { + + const groups = groupsQuery() + const getClientName = useClientSolver() + const exploitSolver = useExtendedExploitSolver() + + const [deleteGroup, setDeleteGroup] = useState() + const [editGroup, setEditGroup] = useState() + + + + const rows = groups.data?.map((grp) => { + return + + {grp.name} + {grp.members.map(ele => getClientName(ele)).join(", ")} + {exploitSolver(grp.exploit)} + {grp.commit} + {grp.last_attack_at?getDateSmallFormatted(grp.last_attack_at):"Never started"} + setEditGroup(grp)} /> + setDeleteGroup(grp)} /> + + })??[]; + + return <> + + + + + Name + Members + Exploit + Commit + Last Execution + Edit + Delete + + + {rows} +
+ {rows.length == 0 && No group found} + setDeleteGroup(undefined)} /> + setEditGroup(undefined)} /> + +} + diff --git a/frontend/src/utils/backend_types.ts b/frontend/src/utils/backend_types.ts index a7b8413..517d384 100644 --- a/frontend/src/utils/backend_types.ts +++ b/frontend/src/utils/backend_types.ts @@ -408,6 +408,42 @@ export interface paths { patch?: never; trace?: never; }; + "/api/groups": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Group Get */ + get: operations["group_get_api_groups_get"]; + put?: never; + /** New Group */ + post: operations["new_group_api_groups_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/groups/{group_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + /** Client Edit */ + put: operations["client_edit_api_groups__group_id__put"]; + post?: never; + /** Delete Group */ + delete: operations["delete_group_api_groups__group_id__delete"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/api/clients": { parameters: { query?: never; @@ -488,6 +524,20 @@ export interface paths { export type webhooks = Record; export interface components { schemas: { + /** AddGroupForm */ + AddGroupForm: { + /** Name */ + name: string; + /** Exploit */ + exploit: string; + /** Created By */ + created_by: string; + /** + * Commit + * @default latest + */ + commit: string | "latest"; + }; /** AttackExecutionDTO */ AttackExecutionDTO: { /** Id */ @@ -510,6 +560,8 @@ export interface components { exploit?: string | null; /** Executed By */ executed_by?: string | null; + /** Executed By Group */ + executed_by_group?: string | null; /** Flags */ flags: components["schemas"]["FlagDTOSmall"][]; /** Exploit Source */ @@ -665,6 +717,11 @@ export interface components { /** Pages */ pages?: number | null; }; + /** EditGroupForm */ + EditGroupForm: { + /** Name */ + name: string; + }; /** ExploitAddForm */ ExploitAddForm: { /** @@ -712,7 +769,7 @@ export interface components { /** Last Execution By */ last_execution_by?: string | null; /** Last Execution Group By */ - last_execution_group_by?: number | null; + last_execution_group_by?: string | null; /** Last Source */ last_source?: string | null; }; @@ -779,6 +836,8 @@ export interface components { source_hash?: string | null; /** Target */ target?: number | null; + /** Executed By Group */ + executed_by_group?: string | null; /** Flags */ flags: string[]; }; @@ -820,6 +879,8 @@ export interface components { exploit?: string | null; /** Executed By */ executed_by?: string | null; + /** Executed By Group */ + executed_by_group?: string | null; /** Exploit Source */ exploit_source?: string | null; }; @@ -849,6 +910,40 @@ export interface components { * @enum {string} */ FlagStatus: "ok" | "wait" | "timeout" | "invalid"; + /** GroupDTO */ + GroupDTO: { + /** + * Id + * Format: uuid + */ + id: string; + /** Name */ + name: string; + /** + * Members + * @default [] + */ + members: string[]; + /** + * Exploit + * Format: uuid + */ + exploit: string; + /** Last Attack At */ + last_attack_at?: string | null; + /** @default inactive */ + status: components["schemas"]["GroupStatus"]; + /** + * Commit + * @default latest + */ + commit: string | "latest"; + }; + /** + * GroupStatus + * @enum {string} + */ + GroupStatus: "active" | "inactive"; /** * Language * @enum {string} @@ -943,6 +1038,14 @@ export interface components { message?: string | null; response?: components["schemas"]["ExploitDTO"] | null; }; + /** MessageResponse[GroupDTO] */ + MessageResponse_GroupDTO_: { + /** @default ok */ + status: components["schemas"]["ResponseStatus"]; + /** Message */ + message?: string | null; + response?: components["schemas"]["GroupDTO"] | null; + }; /** MessageResponse[List[TeamDTO]] */ MessageResponse_List_TeamDTO__: { /** @default ok */ @@ -2240,6 +2343,134 @@ export interface operations { }; }; }; + group_get_api_groups_get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["GroupDTO"][]; + }; + }; + /** @description Validation error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse_Any_"]; + }; + }; + }; + }; + new_group_api_groups_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["AddGroupForm"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse_GroupDTO_"]; + }; + }; + /** @description Validation error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse_Any_"]; + }; + }; + }; + }; + client_edit_api_groups__group_id__put: { + parameters: { + query?: never; + header?: never; + path: { + group_id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["EditGroupForm"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse_GroupDTO_"]; + }; + }; + /** @description Validation error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse_Any_"]; + }; + }; + }; + }; + delete_group_api_groups__group_id__delete: { + parameters: { + query?: never; + header?: never; + path: { + group_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse_GroupDTO_"]; + }; + }; + /** @description Validation error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MessageResponse_Any_"]; + }; + }; + }; + }; client_get_api_clients_get: { parameters: { query?: never; diff --git a/frontend/src/utils/net.ts b/frontend/src/utils/net.ts index bb96331..0ba6ab1 100644 --- a/frontend/src/utils/net.ts +++ b/frontend/src/utils/net.ts @@ -50,7 +50,7 @@ export const sockIoChannelToQueryKeys = (channel:string):string[][] => { ] case "attack_group": return [ - ["attacks"] + ["groups"], ] case "exploit": return [ diff --git a/frontend/src/utils/queries.tsx b/frontend/src/utils/queries.tsx index 3c7f334..dd1295f 100644 --- a/frontend/src/utils/queries.tsx +++ b/frontend/src/utils/queries.tsx @@ -158,6 +158,18 @@ export const statsQuery = () => useQuery({ queryFn: async () => await getRequest("/flags/stats") as Stats }) +export const groupsQuery = () => useQuery({ + queryKey: ["groups"], + queryFn: async () => await getRequest("/groups") as paths["/api/groups"]["get"]["responses"][200]["content"]["application/json"] +}) + +export const editGroup = async (groupId: string, values:{[k:string]:any}) => { + return await putRequest("/groups/"+groupId, { body: values }) as paths["/api/groups/{group_id}"]["put"]["responses"][200]["content"]["application/json"] +} + +export const deleteGroup = async (groupId: string) => { + return await deleteRequest("/groups/"+groupId) as paths["/api/groups/{group_id}"]["delete"]["responses"][200]["content"]["application/json"] +} export const useServiceMapping = () => { const status = statusQuery() @@ -195,6 +207,15 @@ export const useClientMapping = () => { }, [clients.isFetching]) } +export const useGroupMapping = () => { + const groups = groupsQuery() + return useMemo(() => { + const gr = groups.data?.map((group) => ({[group.id]: group})) + if (gr == null || gr.length == 0) return {} + return gr.reduce((acc, val) => ({...acc, ...val}), {}) + }, [groups.isFetching]) +} + export const useServiceSolver = () => { const services = useServiceMapping() return (id?:string|null) => { @@ -250,6 +271,16 @@ export const useClientSolver = () => { } } +export const useGroupSolver = () => { + const groups = useGroupMapping() + return (id?:string|null) => { + if (id == null) return "Unknown" + const group = groups[id] + if (group == null) return `Group ${id}` + return group.name + } +} + export const useExtendedExploitSolver = () => { const exploits = useExploitMapping() const services = useServiceMapping() diff --git a/frontend/src/utils/types.ts b/frontend/src/utils/types.ts index 1d62d43..1963295 100644 --- a/frontend/src/utils/types.ts +++ b/frontend/src/utils/types.ts @@ -11,6 +11,7 @@ export type AttackExecutionRestricted = components["schemas"]["FlagDTOAttackDeta export type Clinet = components["schemas"]["ClientDTO"] export type Service = components["schemas"]["ServiceDTO"] export type Exploit = components["schemas"]["ExploitDTO"] +export type AttackGroup = components["schemas"]["GroupDTO"] export type Language = components["schemas"]["Language"] export type ExploitSource = components["schemas"]["ExploitSourceDTO"] export const LanguageList = ["python", "java", "javascript", "typescript", "c#", "c++", "php", "r", "kotlin", "go", "ruby", "rust", "lua", "dart", "perl", "haskell", "other"]