mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-01-25 02:08:11 +00:00
Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: César Arroba <19954079+cesararroba@users.noreply.github.com> Co-authored-by: Alan Buscaglia <gentlemanprogramming@gmail.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Andoni Alonso <14891798+andoniaf@users.noreply.github.com> Co-authored-by: Rubén De la Torre Vico <ruben@prowler.com> Co-authored-by: HugoPBrito <hugopbrit@gmail.com> Co-authored-by: Hugo Pereira Brito <101209179+HugoPBrito@users.noreply.github.com> Co-authored-by: Pepe Fagoaga <pepe@prowler.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: Chandrapal Badshah <Chan9390@users.noreply.github.com> Co-authored-by: Chandrapal Badshah <12944530+Chan9390@users.noreply.github.com> Co-authored-by: Adrián Peña <adrianjpr@gmail.com> Co-authored-by: Pedro Martín <pedromarting3@gmail.com> Co-authored-by: KonstGolfi <73020281+KonstGolfi@users.noreply.github.com> Co-authored-by: lydiavilchez <114735608+lydiavilchez@users.noreply.github.com> Co-authored-by: Prowler Bot <bot@prowler.com> Co-authored-by: prowler-bot <179230569+prowler-bot@users.noreply.github.com> Co-authored-by: StylusFrost <43682773+StylusFrost@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: alejandrobailo <alejandrobailo94@gmail.com> Co-authored-by: Alejandro Bailo <59607668+alejandrobailo@users.noreply.github.com> Co-authored-by: Víctor Fernández Poyatos <victor@prowler.com> Co-authored-by: bota4go <108249054+bota4go@users.noreply.github.com> Co-authored-by: Daniel Barranquero <74871504+danibarranqueroo@users.noreply.github.com> Co-authored-by: Daniel Barranquero <danielbo2001@gmail.com> Co-authored-by: mchennai <50082780+mchennai@users.noreply.github.com> Co-authored-by: Ryan Nolette <sonofagl1tch@users.noreply.github.com> Co-authored-by: Ulissis Correa <123517149+ulissisc@users.noreply.github.com> Co-authored-by: Sergio Garcia <hello@mistercloudsec.com> Co-authored-by: Lee Trout <ltrout@watchpointlabs.com> Co-authored-by: Sergio Garcia <sergargar1@gmail.com> Co-authored-by: Alan-TheGentleman <alan@thegentleman.dev>
165 lines
5.0 KiB
TypeScript
165 lines
5.0 KiB
TypeScript
import {
|
|
AttackPathGraphData,
|
|
GraphEdge,
|
|
GraphNodeProperties,
|
|
GraphNodePropertyValue,
|
|
GraphRelationship,
|
|
} from "@/types/attack-paths";
|
|
|
|
/**
|
|
* Normalizes property values to ensure they are primitives
|
|
* Arrays are converted to comma-separated strings
|
|
*
|
|
* @param value - The property value to normalize
|
|
* @returns Normalized primitive value
|
|
*/
|
|
function normalizePropertyValue(
|
|
value:
|
|
| GraphNodePropertyValue
|
|
| GraphNodePropertyValue[]
|
|
| Record<string, unknown>,
|
|
): string | number | boolean | null | undefined {
|
|
if (value === null || value === undefined) {
|
|
return value;
|
|
}
|
|
|
|
if (Array.isArray(value)) {
|
|
// Convert arrays to comma-separated strings
|
|
return value.join(", ");
|
|
}
|
|
|
|
if (
|
|
typeof value === "string" ||
|
|
typeof value === "number" ||
|
|
typeof value === "boolean"
|
|
) {
|
|
return value;
|
|
}
|
|
|
|
// For any other type, convert to string
|
|
return String(value);
|
|
}
|
|
|
|
/**
|
|
* Normalizes all properties in an object to ensure they are primitives
|
|
*
|
|
* @param properties - The properties object to normalize
|
|
* @returns Normalized properties object
|
|
*/
|
|
function normalizeProperties(
|
|
properties: Record<
|
|
string,
|
|
GraphNodePropertyValue | GraphNodePropertyValue[] | Record<string, unknown>
|
|
>,
|
|
): GraphNodeProperties {
|
|
const normalized: GraphNodeProperties = {};
|
|
|
|
for (const [key, value] of Object.entries(properties)) {
|
|
normalized[key] = normalizePropertyValue(value);
|
|
}
|
|
|
|
return normalized;
|
|
}
|
|
|
|
/**
|
|
* Adapts graph query result data for D3 visualization
|
|
* Transforms relationships array into edges array for D3 force-directed graph
|
|
*
|
|
* The adapter handles:
|
|
* - Converting relationship objects to edge objects compatible with D3
|
|
* - Mapping relationship labels to edge types for graph styling
|
|
* - Normalizing array properties to strings (e.g., anonymous_actions: ["s3:GetObject"] -> "s3:GetObject")
|
|
* - Preserving node and relationship data structure
|
|
* - Adding findings array to each node based on HAS_FINDING edges
|
|
* - Adding resources array to finding nodes based on HAS_FINDING edges (reverse relationship)
|
|
*
|
|
* @param graphData - Raw graph data with nodes and relationships from API
|
|
* @returns Graph data with edges array formatted for D3 visualization and findings/resources on nodes
|
|
*/
|
|
export function adaptQueryResultToGraphData(
|
|
graphData: AttackPathGraphData,
|
|
): AttackPathGraphData {
|
|
// Normalize node properties to ensure all values are primitives
|
|
const normalizedNodes = graphData.nodes.map((node) => ({
|
|
...node,
|
|
properties: normalizeProperties(
|
|
node.properties as Record<
|
|
string,
|
|
GraphNodePropertyValue | GraphNodePropertyValue[]
|
|
>,
|
|
),
|
|
findings: [] as string[], // Will be populated below
|
|
resources: [] as string[], // Will be populated below for finding nodes
|
|
}));
|
|
|
|
// Transform relationships into D3-compatible edges if relationships exist
|
|
// Also handle case where edges are already provided (e.g., from mock data)
|
|
let edges: GraphEdge[] = [];
|
|
|
|
if (graphData.relationships) {
|
|
edges = (graphData.relationships as GraphRelationship[]).map(
|
|
(relationship) => ({
|
|
id: relationship.id,
|
|
source: relationship.source,
|
|
target: relationship.target,
|
|
type: relationship.label, // D3 uses 'type' for styling edge appearance
|
|
properties: relationship.properties
|
|
? normalizeProperties(
|
|
relationship.properties as Record<
|
|
string,
|
|
GraphNodePropertyValue | GraphNodePropertyValue[]
|
|
>,
|
|
)
|
|
: undefined,
|
|
}),
|
|
);
|
|
} else if (graphData.edges) {
|
|
// If edges are already provided, just normalize their properties
|
|
edges = (graphData.edges as GraphEdge[]).map((edge) => ({
|
|
...edge,
|
|
properties: edge.properties
|
|
? normalizeProperties(
|
|
edge.properties as Record<
|
|
string,
|
|
GraphNodePropertyValue | GraphNodePropertyValue[]
|
|
>,
|
|
)
|
|
: undefined,
|
|
}));
|
|
}
|
|
|
|
// Populate findings and resources based on HAS_FINDING edges
|
|
edges.forEach((edge) => {
|
|
if (edge.type === "HAS_FINDING") {
|
|
const sourceId =
|
|
typeof edge.source === "string"
|
|
? edge.source
|
|
: (edge.source as { id?: string })?.id;
|
|
const targetId =
|
|
typeof edge.target === "string"
|
|
? edge.target
|
|
: (edge.target as { id?: string })?.id;
|
|
|
|
if (sourceId && targetId) {
|
|
// Add finding to source node (resource -> finding)
|
|
const sourceNode = normalizedNodes.find((n) => n.id === sourceId);
|
|
if (sourceNode) {
|
|
sourceNode.findings.push(targetId);
|
|
}
|
|
|
|
// Add resource to target node (finding <- resource)
|
|
const targetNode = normalizedNodes.find((n) => n.id === targetId);
|
|
if (targetNode) {
|
|
targetNode.resources.push(sourceId);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
return {
|
|
nodes: normalizedNodes,
|
|
edges,
|
|
relationships: graphData.relationships, // Preserve original relationships data
|
|
};
|
|
}
|