Files
security-alert-center/frontend/src/composables/useRdgQwinsta.ts
T

189 lines
5.8 KiB
TypeScript

import { onUnmounted, ref } from "vue";
import {
fetchEventAction,
postEventLogoff,
postEventQwinsta,
type AgentCommandResponse,
type EventSummary,
} from "../api";
import { rdgQwinstaEventId } from "../utils/rdgFlap";
export type QwinstaSessionRow = {
sessionName: string;
userName: string;
id: number;
state: string;
};
function parseQwinstaSessions(stdout: string, actorUser: string): QwinstaSessionRow[] {
const lines = stdout.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
const rows: QwinstaSessionRow[] = [];
for (const line of lines) {
if (/^SESSION/i.test(line) || /^---/.test(line)) continue;
const parts = line.split(/\s+/);
if (parts.length < 4) continue;
const sessionName = parts[0].replace(/^>/, "");
const userName = parts[1];
const id = Number.parseInt(parts[2], 10);
const state = parts.slice(3).join(" ");
if (!Number.isFinite(id)) continue;
const norm = (s: string) => s.replace(/^B26\\/i, "").toLowerCase();
const matchUser = actorUser && norm(userName).includes(norm(actorUser));
if (actorUser && !matchUser) continue;
rows.push({ sessionName, userName, id, state });
}
if (rows.length === 0 && stdout.trim()) {
for (const line of lines) {
if (/^SESSION/i.test(line) || /^---/.test(line)) continue;
const parts = line.split(/\s+/);
if (parts.length < 4) continue;
const id = Number.parseInt(parts[2], 10);
if (!Number.isFinite(id)) continue;
rows.push({
sessionName: parts[0].replace(/^>/, ""),
userName: parts[1],
id,
state: parts.slice(3).join(" "),
});
}
}
return rows;
}
export function useRdgQwinsta() {
let qwinstaPollTimer: ReturnType<typeof setInterval> | null = null;
const qwinstaLoadingId = ref<number | null>(null);
const qwinstaModal = ref({
open: false,
eventId: 0,
actorUser: "",
commandUuid: "",
target: "",
clientHostname: "",
internalIp: "",
loading: false,
error: "",
stdout: "",
sessions: [] as QwinstaSessionRow[],
logoffId: null as number | null,
});
function stopQwinstaPoll() {
if (qwinstaPollTimer) {
clearInterval(qwinstaPollTimer);
qwinstaPollTimer = null;
}
}
function closeQwinstaModal() {
stopQwinstaPoll();
qwinstaModal.value.open = false;
}
function applyCommandResult(eventId: number, actorUser: string, cmd: AgentCommandResponse) {
if (cmd.status === "pending") return false;
qwinstaModal.value.loading = false;
if (cmd.status === "failed") {
qwinstaModal.value.error = cmd.result_stderr || cmd.result_stdout || "Команда завершилась с ошибкой";
return true;
}
qwinstaModal.value.stdout = cmd.result_stdout || "";
qwinstaModal.value.sessions = parseQwinstaSessions(qwinstaModal.value.stdout, actorUser);
return true;
}
function applyCommandMeta(modal: typeof qwinstaModal.value, cmd: AgentCommandResponse) {
modal.target = cmd.target || "";
modal.clientHostname = cmd.client_hostname || "";
modal.internalIp = cmd.internal_ip || "";
}
function pollQwinstaCommand(eventId: number, commandUuid: string, actorUser: string) {
stopQwinstaPoll();
qwinstaPollTimer = setInterval(async () => {
try {
const cmd = await fetchEventAction(eventId, commandUuid);
if (applyCommandResult(eventId, actorUser, cmd)) {
stopQwinstaPoll();
}
} catch (e) {
qwinstaModal.value.loading = false;
qwinstaModal.value.error = e instanceof Error ? e.message : "Ошибка опроса команды";
stopQwinstaPoll();
}
}, 2000);
}
async function runQwinsta(event: EventSummary) {
const targetId = rdgQwinstaEventId(event) ?? event.id;
qwinstaLoadingId.value = event.id;
qwinstaModal.value = {
open: true,
eventId: targetId,
actorUser: event.actor_user || "",
commandUuid: "",
target: "",
clientHostname: "",
internalIp: "",
loading: true,
error: "",
stdout: "",
sessions: [],
logoffId: null,
};
try {
const cmd = await postEventQwinsta(targetId);
applyCommandMeta(qwinstaModal.value, cmd);
qwinstaModal.value.commandUuid = cmd.command_uuid;
if (applyCommandResult(targetId, event.actor_user || "", cmd)) {
return;
}
pollQwinstaCommand(targetId, cmd.command_uuid, event.actor_user || "");
} catch (e) {
qwinstaModal.value.loading = false;
qwinstaModal.value.error = e instanceof Error ? e.message : "Не удалось отправить qwinsta";
} finally {
qwinstaLoadingId.value = null;
}
}
async function runLogoff(sessionId: number) {
const modal = qwinstaModal.value;
if (!modal.eventId) return;
if (!window.confirm(`Завершить сеанс ID ${sessionId}?`)) return;
modal.logoffId = sessionId;
modal.error = "";
try {
const cmd = await postEventLogoff(modal.eventId, sessionId);
applyCommandMeta(modal, cmd);
if (cmd.status === "failed") {
modal.error = cmd.result_stderr || cmd.result_stdout || "logoff failed";
return;
}
modal.loading = true;
modal.stdout = "";
modal.sessions = [];
const again = await postEventQwinsta(modal.eventId);
applyCommandMeta(modal, again);
if (applyCommandResult(modal.eventId, modal.actorUser, again)) {
return;
}
pollQwinstaCommand(modal.eventId, again.command_uuid, modal.actorUser);
} catch (e) {
modal.error = e instanceof Error ? e.message : "logoff error";
} finally {
modal.logoffId = null;
}
}
onUnmounted(() => stopQwinstaPoll());
return {
qwinstaLoadingId,
qwinstaModal,
closeQwinstaModal,
runQwinsta,
runLogoff,
};
}