0de9f4a6c0
The Coverage Matrix heatmap only ever showed the bare MITRE ID on each cell (name only via hover tooltip), and colored cells from a continuous score -> hex gradient with no discrete status concept — unlike the Techniques page, which showed ID+name inline with a fixed 5-color status palette. Unifying the two pages' visual style requires both pieces of data to exist server-side. - Adds "name" to all 4 layer builders (coverage/threat-actor/detection- rules/campaign) — harmless extra field for Navigator exports, lets the frontend show it inline. - Adds "status" (the real TechniqueStatus value) to the coverage and threat-actor layers specifically — the only two backed by an actual TechniqueStatus concept; detection-rules/campaign scores don't correspond to one and don't get this field.
86 lines
3.0 KiB
Python
86 lines
3.0 KiB
Python
"""Every heatmap layer must include the technique's display name.
|
|
|
|
The frontend heatmap previously showed only the MITRE ID on each cell
|
|
(name only available via hover tooltip), unlike the Techniques page which
|
|
always showed both. Unifying the two pages' cell style requires the name
|
|
to be available on every layer's technique entries, not just derived
|
|
client-side (HeatmapTechnique has no name field to derive it from).
|
|
"""
|
|
|
|
from app.models.campaign import Campaign, CampaignTest
|
|
from app.models.detection_rule import DetectionRule
|
|
from app.models.enums import TestState
|
|
from app.models.technique import Technique
|
|
from app.models.test import Test
|
|
from app.models.threat_actor import ThreatActor, ThreatActorTechnique
|
|
from app.services.heatmap_service import (
|
|
build_campaign_layer,
|
|
build_coverage_layer,
|
|
build_detection_rules_layer,
|
|
build_threat_actor_layer,
|
|
)
|
|
|
|
|
|
def _seed_technique(db, mitre_id="T1059", name="Command and Scripting Interpreter"):
|
|
tech = Technique(mitre_id=mitre_id, name=name, tactic="execution", platforms=["windows"])
|
|
db.add(tech)
|
|
db.flush()
|
|
return tech
|
|
|
|
|
|
def test_coverage_layer_includes_technique_name_and_status(db):
|
|
tech = _seed_technique(db)
|
|
db.commit()
|
|
|
|
layer = build_coverage_layer(db)
|
|
|
|
entry = next(t for t in layer["techniques"] if t["techniqueID"] == tech.mitre_id)
|
|
assert entry["name"] == tech.name
|
|
assert entry["status"] == "not_evaluated"
|
|
|
|
|
|
def test_threat_actor_layer_includes_technique_name_and_status(db):
|
|
tech = _seed_technique(db)
|
|
actor = ThreatActor(name="APT-Test")
|
|
db.add(actor)
|
|
db.flush()
|
|
db.add(ThreatActorTechnique(threat_actor_id=actor.id, technique_id=tech.id))
|
|
db.commit()
|
|
|
|
layer = build_threat_actor_layer(db, actor.id)
|
|
|
|
entry = next(t for t in layer["techniques"] if t["techniqueID"] == tech.mitre_id)
|
|
assert entry["name"] == tech.name
|
|
assert entry["status"] == "not_evaluated"
|
|
|
|
|
|
def test_detection_rules_layer_includes_technique_name(db):
|
|
tech = _seed_technique(db)
|
|
db.add(DetectionRule(
|
|
mitre_technique_id=tech.mitre_id, title="Rule 1", source="sigma",
|
|
rule_content="title: Rule 1", rule_format="sigma",
|
|
))
|
|
db.commit()
|
|
|
|
layer = build_detection_rules_layer(db)
|
|
|
|
entry = next(t for t in layer["techniques"] if t["techniqueID"] == tech.mitre_id)
|
|
assert entry["name"] == tech.name
|
|
|
|
|
|
def test_campaign_layer_includes_technique_name(db, red_lead_user):
|
|
tech = _seed_technique(db)
|
|
campaign = Campaign(name="Test Campaign", type="custom", status="active", created_by=red_lead_user.id)
|
|
db.add(campaign)
|
|
db.flush()
|
|
test = Test(technique_id=tech.id, name="A test", state=TestState.validated, created_by=red_lead_user.id)
|
|
db.add(test)
|
|
db.flush()
|
|
db.add(CampaignTest(campaign_id=campaign.id, test_id=test.id, order_index=0))
|
|
db.commit()
|
|
|
|
layer = build_campaign_layer(db, campaign.id)
|
|
|
|
entry = next(t for t in layer["techniques"] if t["techniqueID"] == tech.mitre_id)
|
|
assert entry["name"] == tech.name
|