Assessor_2.0/src/features/dashboard/environment-result-evidence.ts

117 lines
6.5 KiB
TypeScript

import type { EnvironmentObservation, EnvironmentSnapshot, ScannerReportSection, ScannerReportTable } from '../../domain/environment-context';
export type EnvironmentEvidenceGroup = 'all' | 'directory' | 'resilience' | 'inventory'
| 'password-policy' | 'certificates' | 'source-metrics' | 'operating-systems';
export interface EnvironmentEvidence {
sections: ScannerReportSection[];
observations: EnvironmentObservation[];
}
interface SubsectionLabels {
explicit: Set<string>;
rowCount: Set<string>;
}
const subsectionCache = new WeakMap<EnvironmentSnapshot, Map<string, SubsectionLabels>>();
const subsectionKey = (runId: string, fileId: string, sectionKey: string, tableIndex: number) =>
`${runId}\u0000${fileId}\u0000${sectionKey}\u0000${tableIndex}`;
function indexedSubsections(snapshot: EnvironmentSnapshot): Map<string, SubsectionLabels> {
const cached = subsectionCache.get(snapshot);
if (cached) return cached;
const index = new Map<string, SubsectionLabels>();
for (const observation of snapshot.observations) {
const tableIndex = observation.attributes.sourceTableIndex;
if (typeof tableIndex !== 'number' || !observation.sectionKey) continue;
const key = subsectionKey(observation.scanRunId, observation.importFileId, observation.sectionKey, tableIndex);
let labels = index.get(key);
if (!labels) { labels = { explicit: new Set(), rowCount: new Set() }; index.set(key, labels); }
const explicit = observation.attributes.subsectionLabel;
if (typeof explicit === 'string' && explicit.trim()) labels.explicit.add(explicit.trim());
if (/\.row_count$/.test(observation.property) && observation.subject.label?.trim())
labels.rowCount.add(observation.subject.label.trim());
}
subsectionCache.set(snapshot, index);
return index;
}
/** Retained native subsection headings are keyed by source table within one exact report section. */
export function environmentTableSubsection(
snapshot: EnvironmentSnapshot, section: ScannerReportSection, table: ScannerReportTable
): string | undefined {
if (table.sourceTableIndex === undefined) return undefined;
const labels = indexedSubsections(snapshot).get(subsectionKey(
section.scanRunId, section.importFileId, section.sectionKey, table.sourceTableIndex));
if (!labels) return undefined;
if (labels.explicit.size) return labels.explicit.size === 1 ? [...labels.explicit][0] : undefined;
return labels.rowCount.size === 1 ? [...labels.rowCount][0] : undefined;
}
/** A section labelled with its first subsection must not appear to own all later siblings. */
export function environmentSectionTitle(snapshot: EnvironmentSnapshot, section: ScannerReportSection): string {
const subsectionLabels = new Set(section.tables
.map(table => environmentTableSubsection(snapshot, section, table)).filter((label): label is string => Boolean(label)));
if (subsectionLabels.size > 1 && subsectionLabels.has(section.label)) {
const fromKey = section.sectionKey.replace(/^section/i, '').replace(/([a-z0-9])([A-Z])/g, '$1 $2').trim();
if (fromKey && fromKey.toLocaleLowerCase('en-US') !== section.label.toLocaleLowerCase('en-US')) return fromKey;
}
return section.label || section.sectionKey;
}
const osTable = (table: ScannerReportTable) => /operating\s+systems?/i.test(table.name);
const resilienceProperty = /^(?:backup\.|laps\.|kerberos\.krbtgt\.|logging\.wef\.|domain\.recycle_bin_enabled$)/i;
const inventorySection = /^section(?:UserInformation|ComputerInformation|AdminGroups|Trustsdetails|ControlPathsAnalysis|neverexpiresuser|enabledtrusteddelegationcomputer)$/i;
function observationInGroup(observation: EnvironmentObservation, group: EnvironmentEvidenceGroup): boolean {
const property = observation.property;
const section = observation.sectionKey ?? '';
if (group === 'operating-systems') return /^operating_system\./i.test(property)
|| /operating\s+systems?/i.test(String(observation.attributes.tableName ?? ''));
if (group === 'resilience') return resilienceProperty.test(property);
if (group === 'password-policy') return /^password_policy\./i.test(property) || /^sectionPasswordPolicies$/i.test(section);
if (group === 'certificates') return /^pki\./i.test(property) || /^sectionPKI$/i.test(section);
if (group === 'directory') return /^(?:domain\.|forest\.|entra\.)/i.test(property)
|| /^section(?:DomainInformation|Infrastructure)$/i.test(section);
if (group === 'inventory') return /^(?:host\.|trust\.|user\.|computer\.|domain\.controller_count$)/i.test(property)
|| inventorySection.test(section);
if (group === 'source-metrics') return /^(?:report\.|risk\.|purple_knight\.|benchmark\.|maester\.|entra\.tenant_(?:id|name)$)/i.test(property)
|| /^(?:report-overview|purple-knight-assessment-summary)$/i.test(section);
return true;
}
function sectionForGroup(section: ScannerReportSection, group: EnvironmentEvidenceGroup): ScannerReportSection | undefined {
if (group === 'all') return section;
const key = section.sectionKey;
if (group === 'directory' && /^section(?:DomainInformation|Infrastructure)$/i.test(key)) return section;
if (group === 'inventory' && inventorySection.test(key)) return section;
if (group === 'password-policy' && /^sectionPasswordPolicies$/i.test(key)) return section;
if (group === 'certificates' && /^sectionPKI$/i.test(key)) return section;
if (group === 'source-metrics' && /^(?:report-overview|purple-knight-assessment-summary)$/i.test(key)) return section;
if (group === 'operating-systems' && /^sectionComputerInformation$/i.test(key)) {
const tables = section.tables.filter(osTable);
return tables.length ? { ...section, tables, prose: undefined } : undefined;
}
if (group === 'resilience' && /^sectionAnomalies$/i.test(key)) {
const tables = section.tables.filter(table => /backup|laps|krbtgt|event\s+forwarding|\bwef\b|recycle/i.test(table.name));
return tables.length ? { ...section, tables, prose: undefined } : undefined;
}
return undefined;
}
/** Read-only, source-backed projection. Every retained row remains available in `all`. */
export function environmentEvidenceForGroup(
snapshot: EnvironmentSnapshot | undefined,
group: EnvironmentEvidenceGroup
): EnvironmentEvidence {
if (!snapshot) return { sections: [], observations: [] };
return {
sections: snapshot.sections.flatMap(section => {
const selected = sectionForGroup(section, group);
return selected ? [selected] : [];
}),
observations: group === 'all' ? snapshot.observations
: snapshot.observations.filter(observation => observationInGroup(observation, group))
};
}