mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-01-25 02:08:11 +00:00
232 lines
8.0 KiB
TypeScript
232 lines
8.0 KiB
TypeScript
import { ClientAccordionContent } from "@/components/compliance/compliance-accordion/client-accordion-content";
|
|
import { ComplianceAccordionRequirementTitle } from "@/components/compliance/compliance-accordion/compliance-accordion-requeriment-title";
|
|
import { ComplianceAccordionTitle } from "@/components/compliance/compliance-accordion/compliance-accordion-title";
|
|
import { AccordionItemProps } from "@/components/ui/accordion/Accordion";
|
|
import { FindingStatus } from "@/components/ui/table/status-finding-badge";
|
|
import {
|
|
AttributesData,
|
|
Framework,
|
|
Requirement,
|
|
REQUIREMENT_STATUS,
|
|
RequirementsData,
|
|
RequirementStatus,
|
|
ThreatAttributesMetadata,
|
|
} from "@/types/compliance";
|
|
|
|
import {
|
|
createRequirementsMap,
|
|
findOrCreateCategory,
|
|
findOrCreateControl,
|
|
findOrCreateFramework,
|
|
updateCounters,
|
|
} from "./commons";
|
|
|
|
export const mapComplianceData = (
|
|
attributesData: AttributesData,
|
|
requirementsData: RequirementsData,
|
|
): Framework[] => {
|
|
const attributes = attributesData?.data || [];
|
|
const requirementsMap = createRequirementsMap(requirementsData);
|
|
const frameworks: Framework[] = [];
|
|
|
|
// Process attributes and merge with requirements data
|
|
for (const attributeItem of attributes) {
|
|
const id = attributeItem.id;
|
|
const metadataArray = attributeItem.attributes?.attributes
|
|
?.metadata as unknown as ThreatAttributesMetadata[];
|
|
const attrs = metadataArray?.[0];
|
|
if (!attrs) continue;
|
|
|
|
// Get corresponding requirement data
|
|
const requirementData = requirementsMap.get(id);
|
|
if (!requirementData) continue;
|
|
|
|
const frameworkName = attributeItem.attributes.framework;
|
|
const sectionName = attrs.Section;
|
|
const subSectionName = attrs.SubSection;
|
|
const title = attrs.Title;
|
|
const description = attributeItem.attributes.description;
|
|
const status = requirementData.attributes.status || "";
|
|
const checks = attributeItem.attributes.attributes.check_ids || [];
|
|
const requirementName = id;
|
|
const levelOfRisk = attrs.LevelOfRisk;
|
|
const weight = attrs.Weight;
|
|
const attributeDescription = attrs.AttributeDescription;
|
|
const additionalInformation = attrs.AdditionalInformation;
|
|
const passedFindings = requirementData.attributes.passed_findings || 0;
|
|
const totalFindings = requirementData.attributes.total_findings || 0;
|
|
|
|
// Calculate score: if PASS = levelOfRisk * weight, if FAIL = 0
|
|
const score = status === REQUIREMENT_STATUS.PASS ? levelOfRisk * weight : 0;
|
|
|
|
// Find or create framework using common helper
|
|
const framework = findOrCreateFramework(frameworks, frameworkName);
|
|
|
|
// Find or create category (Section) using common helper
|
|
const category = findOrCreateCategory(framework.categories, sectionName);
|
|
|
|
// Find or create control (SubSection) using common helper
|
|
const control = findOrCreateControl(category.controls, subSectionName);
|
|
|
|
// Create requirement
|
|
const finalStatus: RequirementStatus = status as RequirementStatus;
|
|
const requirement: Requirement = {
|
|
name: requirementName,
|
|
description: description,
|
|
status: finalStatus,
|
|
check_ids: checks,
|
|
pass: finalStatus === REQUIREMENT_STATUS.PASS ? 1 : 0,
|
|
fail: finalStatus === REQUIREMENT_STATUS.FAIL ? 1 : 0,
|
|
manual: finalStatus === REQUIREMENT_STATUS.MANUAL ? 1 : 0,
|
|
title: title,
|
|
levelOfRisk: levelOfRisk,
|
|
weight: weight,
|
|
score: score,
|
|
attributeDescription: attributeDescription,
|
|
additionalInformation: additionalInformation,
|
|
passedFindings: passedFindings,
|
|
totalFindings: totalFindings,
|
|
};
|
|
|
|
control.requirements.push(requirement);
|
|
}
|
|
|
|
// Calculate counters and percentualScore (Threat-specific logic)
|
|
frameworks.forEach((framework) => {
|
|
framework.pass = 0;
|
|
framework.fail = 0;
|
|
framework.manual = 0;
|
|
|
|
framework.categories.forEach((category) => {
|
|
category.pass = 0;
|
|
category.fail = 0;
|
|
category.manual = 0;
|
|
|
|
// Calculate ThreatScore using the new formula
|
|
let numerator = 0;
|
|
let denominator = 0;
|
|
let hasFindings = false;
|
|
|
|
category.controls.forEach((control) => {
|
|
control.pass = 0;
|
|
control.fail = 0;
|
|
control.manual = 0;
|
|
|
|
control.requirements.forEach((requirement) => {
|
|
updateCounters(control, requirement.status);
|
|
|
|
// Extract values for ThreatScore calculation
|
|
const pass_i = (requirement.passedFindings as number) || 0;
|
|
const total_i = (requirement.totalFindings as number) || 0;
|
|
|
|
// Skip if no findings (avoid division by zero)
|
|
if (total_i === 0) return;
|
|
|
|
hasFindings = true;
|
|
const rate_i = pass_i / total_i;
|
|
const weight_i = (requirement.weight as number) || 1;
|
|
const levelOfRisk = (requirement.levelOfRisk as number) || 0;
|
|
|
|
// Map levelOfRisk to risk factor (rfac_i)
|
|
// Formula: rfac_i = 1 + (k * levelOfRisk) where k = 0.25
|
|
const rfac_i = 1 + 0.25 * levelOfRisk;
|
|
|
|
// Accumulate weighted values
|
|
numerator += rate_i * total_i * weight_i * rfac_i;
|
|
denominator += total_i * weight_i * rfac_i;
|
|
});
|
|
|
|
category.pass += control.pass;
|
|
category.fail += control.fail;
|
|
category.manual += control.manual;
|
|
});
|
|
|
|
// Calculate ThreatScore (percentualScore) for this section
|
|
// If no findings exist, consider it 100% (PASS)
|
|
const percentualScore = !hasFindings
|
|
? 100
|
|
: denominator > 0
|
|
? (numerator / denominator) * 100
|
|
: 0;
|
|
|
|
// Add percentualScore to category (we can extend the type or use a custom property)
|
|
(category as any).percentualScore =
|
|
Math.round(percentualScore * 100) / 100; // Round to 2 decimal places
|
|
|
|
framework.pass += category.pass;
|
|
framework.fail += category.fail;
|
|
framework.manual += category.manual;
|
|
});
|
|
});
|
|
|
|
return frameworks;
|
|
};
|
|
|
|
export const toAccordionItems = (
|
|
data: Framework[],
|
|
scanId: string | undefined,
|
|
): AccordionItemProps[] => {
|
|
return data.flatMap((framework) =>
|
|
framework.categories.map((category) => {
|
|
const percentualScore = (category as any).percentualScore || 0;
|
|
|
|
return {
|
|
key: `${framework.name}-${category.name}`,
|
|
title: (
|
|
<ComplianceAccordionTitle
|
|
label={`${category.name} - ${percentualScore}%`}
|
|
pass={category.pass}
|
|
fail={category.fail}
|
|
manual={category.manual}
|
|
isParentLevel={true}
|
|
/>
|
|
),
|
|
content: "",
|
|
items: category.controls.map((control, i: number) => {
|
|
return {
|
|
key: `${framework.name}-${category.name}-control-${i}`,
|
|
title: (
|
|
<ComplianceAccordionTitle
|
|
label={control.label}
|
|
pass={control.pass}
|
|
fail={control.fail}
|
|
manual={control.manual}
|
|
/>
|
|
),
|
|
content: "",
|
|
items: control.requirements.map((requirement, j: number) => {
|
|
const itemKey = `${framework.name}-${category.name}-control-${i}-req-${j}`;
|
|
|
|
return {
|
|
key: itemKey,
|
|
title: (
|
|
<ComplianceAccordionRequirementTitle
|
|
type=""
|
|
name={`${requirement.name} - ${requirement.title || requirement.description}`}
|
|
status={requirement.status as FindingStatus}
|
|
/>
|
|
),
|
|
content: (
|
|
<ClientAccordionContent
|
|
key={`content-${itemKey}`}
|
|
requirement={requirement}
|
|
scanId={scanId || ""}
|
|
framework={framework.name}
|
|
disableFindings={
|
|
requirement.check_ids.length === 0 &&
|
|
requirement.manual === 0
|
|
}
|
|
/>
|
|
),
|
|
items: [],
|
|
};
|
|
}),
|
|
isDisabled:
|
|
control.pass === 0 && control.fail === 0 && control.manual === 0,
|
|
};
|
|
}),
|
|
};
|
|
}),
|
|
);
|
|
};
|