refactor(docs+comments): add Google-style docstrings and inline comments across backend

Task D — Google-style docstrings (Args/Returns) on every public function,
method, and class across all 158 Python files in the backend. Zero ruff D
violations (pydocstyle Google convention).

Task E — Explanatory one-line comment before every code line (~11600 new
comments). ruff check passes clean after isort re-sort.
This commit is contained in:
kitos
2026-06-10 12:37:15 +02:00
parent 9ff0f04ba3
commit d2a46feba8
158 changed files with 14861 additions and 248 deletions
+323 -9
View File
@@ -1,26 +1,41 @@
"""D3FEND import service — fetches MITRE D3FEND data and creates
DefensiveTechnique records plus ATT&CK → D3FEND mappings.
"""D3FEND import service — fetches MITRE D3FEND data and creates DefensiveTechnique records plus ATT&CK → D3FEND mappings.
Uses the D3FEND public API:
- https://d3fend.mitre.org/api/technique/api-all.json (all defensive techniques)
- https://d3fend.mitre.org/api/offensive-technique/{attack_id}.json (mappings per ATT&CK technique)
"""
# Import logging
import logging
# Import Any from typing
from typing import Any
# Import UUID from uuid
from uuid import UUID
# Import httpx
import httpx
# Import Session from sqlalchemy.orm
from sqlalchemy.orm import Session
# Import DefensiveTechnique, DefensiveTechniqueMapping from app.models.defensive_technique
from app.models.defensive_technique import DefensiveTechnique, DefensiveTechniqueMapping
# Import Technique from app.models.technique
from app.models.technique import Technique
# Assign logger = logging.getLogger(__name__)
logger = logging.getLogger(__name__)
# Assign D3FEND_TACTIC_URL = "https://d3fend.mitre.org/api/tactic/d3f:{tactic}.json"
D3FEND_TACTIC_URL = "https://d3fend.mitre.org/api/tactic/d3f:{tactic}.json"
# Assign D3FEND_MAPPING_URL = "https://d3fend.mitre.org/api/offensive-technique/{attack_id}.json"
D3FEND_MAPPING_URL = "https://d3fend.mitre.org/api/offensive-technique/{attack_id}.json"
# Assign D3FEND_BASE_URL = "https://d3fend.mitre.org/technique/d3f:{iri}"
D3FEND_BASE_URL = "https://d3fend.mitre.org/technique/d3f:{iri}"
# Assign D3FEND_TACTICS = ["Detect", "Harden", "Isolate", "Deceive", "Evict", "Model"]
D3FEND_TACTICS = ["Detect", "Harden", "Isolate", "Deceive", "Evict", "Model"]
@@ -28,121 +43,228 @@ D3FEND_TACTICS = ["Detect", "Harden", "Isolate", "Deceive", "Evict", "Model"]
def _to_str(v: Any) -> str: # noqa: ANN401
"""Coerce an RDF value (str, dict with @value, or list) to a plain string."""
"""Coerce an RDF value (str, dict with @value, or list) to a plain string.
Args:
v (Any): RDF node value — may be a plain string, a dict containing
a ``@value`` key, or a list of such values.
Returns:
str: Plain string representation; ``"; "``-joined for list inputs.
"""
# Check: isinstance(v, dict)
if isinstance(v, dict):
# Return v.get("@value", str(v))
return v.get("@value", str(v))
# Check: isinstance(v, list)
if isinstance(v, list):
# Return "; ".join(_to_str(x) for x in v)
return "; ".join(_to_str(x) for x in v)
# Return str(v) if v else ""
return str(v) if v else ""
# Define function _fetch_techniques_from_tactic_apis
def _fetch_techniques_from_tactic_apis() -> list[dict[str, Any]]:
"""Fetch all defensive techniques via D3FEND tactic APIs.
Uses ``/api/tactic/d3f:{tactic}.json`` which is reliable and returns
full metadata including the ontology IRI for each technique.
Returns:
list[dict[str, Any]]: Deduplicated list of technique dicts, each
containing ``d3fend_id``, ``iri``, ``name``, ``description``,
and ``tactic``.
"""
# Assign all_techniques = []
all_techniques: list[dict[str, Any]] = []
# Assign seen = set()
seen: set[str] = set()
# Open context manager
with httpx.Client(timeout=60.0) as client:
# Iterate over D3FEND_TACTICS
for tactic in D3FEND_TACTICS:
# Assign url = D3FEND_TACTIC_URL.format(tactic=tactic)
url = D3FEND_TACTIC_URL.format(tactic=tactic)
# Attempt the following; catch errors below
try:
# Assign resp = client.get(url)
resp = client.get(url)
# Call resp.raise_for_status()
resp.raise_for_status()
# Assign data = resp.json()
data = resp.json()
# Handle Exception
except Exception as e:
# Log warning: "Failed to fetch D3FEND tactic %s: %s", tactic, e
logger.warning("Failed to fetch D3FEND tactic %s: %s", tactic, e)
# Skip to the next loop iteration
continue
# Assign graph = data.get("techniques", {}).get("@graph", [])
graph = data.get("techniques", {}).get("@graph", [])
# Iterate over graph
for node in graph:
# Assign nid = node.get("@id", "")
nid = node.get("@id", "")
# Assign d3id = _to_str(node.get("d3f:d3fend-id", ""))
d3id = _to_str(node.get("d3f:d3fend-id", ""))
# Assign label = _to_str(node.get("rdfs:label", ""))
label = _to_str(node.get("rdfs:label", ""))
# Assign defn = _to_str(node.get("d3f:definition", ""))
defn = _to_str(node.get("d3f:definition", ""))
# Check: not defn
if not defn:
# Assign defn = _to_str(node.get("rdfs:comment", ""))
defn = _to_str(node.get("rdfs:comment", ""))
# Assign iri = nid.replace("d3f:", "") if nid.startswith("d3f:") else nid
iri = nid.replace("d3f:", "") if nid.startswith("d3f:") else nid
# Check: d3id and label and d3id not in seen
if d3id and label and d3id not in seen:
# Call seen.add()
seen.add(d3id)
# Call all_techniques.append()
all_techniques.append({
# Literal argument value
"d3fend_id": d3id,
# Literal argument value
"iri": iri,
# Literal argument value
"name": label,
# Literal argument value
"description": defn[:500] if defn else None,
# Literal argument value
"tactic": tactic,
})
# Log info: "D3FEND tactic %s: %d techniques", tactic, len(gra
logger.info("D3FEND tactic %s: %d techniques", tactic, len(graph))
# Return all_techniques
return all_techniques
# Define function _upsert_techniques
def _upsert_techniques(db: Session, techniques: list[dict[str, Any]]) -> dict[str, int]:
"""Upsert a list of technique dicts into the DefensiveTechnique table."""
"""Upsert a list of technique dicts into the DefensiveTechnique table.
Args:
db (Session): Active SQLAlchemy database session.
techniques (list[dict[str, Any]]): List of technique data dicts, each
containing ``d3fend_id``, ``name``, and optionally ``description``,
``tactic``, and ``iri``.
Returns:
dict[str, int]: Contains ``created``, ``updated``, and ``total``
counts after the upsert.
"""
# Assign created = 0
created = 0
# Assign updated = 0
updated = 0
# Iterate over techniques
for tech_data in techniques:
# Assign existing = (
existing = (
db.query(DefensiveTechnique)
# Chain .filter() call
.filter(DefensiveTechnique.d3fend_id == tech_data["d3fend_id"])
# Chain .first() call
.first()
)
# Assign iri = tech_data.get("iri") or tech_data["name"].replace(" ", "")
iri = tech_data.get("iri") or tech_data["name"].replace(" ", "")
# Assign d3fend_url = D3FEND_BASE_URL.format(iri=iri)
d3fend_url = D3FEND_BASE_URL.format(iri=iri)
# Check: existing
if existing:
# Assign existing.name = tech_data["name"]
existing.name = tech_data["name"]
# Assign existing.description = tech_data.get("description")
existing.description = tech_data.get("description")
# Assign existing.tactic = tech_data.get("tactic")
existing.tactic = tech_data.get("tactic")
# Assign existing.d3fend_url = d3fend_url
existing.d3fend_url = d3fend_url
# Assign updated = 1
updated += 1
# Fallback: handle remaining cases
else:
# Assign new_tech = DefensiveTechnique(
new_tech = DefensiveTechnique(
# Keyword argument: d3fend_id
d3fend_id=tech_data["d3fend_id"],
# Keyword argument: name
name=tech_data["name"],
# Keyword argument: description
description=tech_data.get("description"),
# Keyword argument: tactic
tactic=tech_data.get("tactic"),
# Keyword argument: d3fend_url
d3fend_url=d3fend_url,
)
# Stage new record(s) for database insertion
db.add(new_tech)
# Assign created = 1
created += 1
# Commit all pending changes to the database
db.commit()
# Assign total = db.query(DefensiveTechnique).count()
total = db.query(DefensiveTechnique).count()
# Return {"created": created, "updated": updated, "total": total}
return {"created": created, "updated": updated, "total": total}
# Define function import_d3fend_techniques
def import_d3fend_techniques(db: Session) -> dict[str, int]:
"""Fetch all D3FEND defensive techniques and upsert into DB.
Uses the tactic-level APIs which are reliable and provide full metadata
including ontology IRIs for correct URL generation.
Returns a dict with counts: {created, updated, total}.
Args:
db (Session): Active SQLAlchemy database session.
Returns:
dict[str, int]: Contains ``created``, ``updated``, and ``total``
counts; falls back to curated list when the API returns fewer
than 50 techniques.
"""
# Log info: "Fetching D3FEND techniques from tactic APIs"
logger.info("Fetching D3FEND techniques from tactic APIs")
# Attempt the following; catch errors below
try:
# Assign techniques = _fetch_techniques_from_tactic_apis()
techniques = _fetch_techniques_from_tactic_apis()
# Handle Exception
except Exception as e:
# Log error: "Failed to fetch D3FEND techniques from tactic API
logger.error("Failed to fetch D3FEND techniques from tactic APIs: %s", e)
# Assign techniques = []
techniques = []
# Check: len(techniques) >= 50
if len(techniques) >= 50:
# Log info: "Fetched %d D3FEND techniques from tactic APIs", l
logger.info("Fetched %d D3FEND techniques from tactic APIs", len(techniques))
# Assign result = _upsert_techniques(db, techniques)
result = _upsert_techniques(db, techniques)
# Log info: "D3FEND import done: %d created, %d updated, %d to
logger.info("D3FEND import done: %d created, %d updated, %d total",
result["created"], result["updated"], result["total"])
# Return result
return result
# Fallback: use a curated list of well-known D3FEND techniques
logger.warning("Tactic APIs returned too few techniques (%d), using fallback", len(techniques))
# Return _import_d3fend_fallback(db)
return _import_d3fend_fallback(db)
@@ -228,9 +350,20 @@ _FALLBACK_TECHNIQUES: list[dict[str, str | None]] = [
]
# Define function _import_d3fend_fallback
def _import_d3fend_fallback(db: Session) -> dict[str, int]:
"""Import curated D3FEND techniques when the tactic APIs are unreachable."""
"""Import curated D3FEND techniques when the tactic APIs are unreachable.
Args:
db (Session): Active SQLAlchemy database session.
Returns:
dict[str, int]: Contains ``created``, ``updated``, and ``total``
counts from upserting the fallback technique list.
"""
# Log info: "Using fallback D3FEND technique list (%d entries
logger.info("Using fallback D3FEND technique list (%d entries)", len(_FALLBACK_TECHNIQUES))
# Return _upsert_techniques(db, _FALLBACK_TECHNIQUES) # type: ignore[arg-type]
return _upsert_techniques(db, _FALLBACK_TECHNIQUES) # type: ignore[arg-type]
@@ -239,218 +372,399 @@ def _import_d3fend_fallback(db: Session) -> dict[str, int]:
# Curated ATT&CK → D3FEND mapping for common techniques
_ATTACK_TO_D3FEND: dict[str, list[str]] = {
# Literal argument value
"T1059": ["D3-PSA", "D3-SCA", "D3-PA", "D3-EAW", "D3-EDL", "D3-PLA"],
# Literal argument value
"T1059.001": ["D3-PSA", "D3-SCA", "D3-PA", "D3-EAW", "D3-EDL"],
# Literal argument value
"T1059.003": ["D3-PSA", "D3-SCA", "D3-PA", "D3-EAW"],
# Literal argument value
"T1059.005": ["D3-PSA", "D3-SCA", "D3-EAW"],
# Literal argument value
"T1059.007": ["D3-PSA", "D3-SCA", "D3-EAW"],
# Literal argument value
"T1055": ["D3-PA", "D3-PSA", "D3-HBPI", "D3-PMAD", "D3-PLA"],
# Literal argument value
"T1055.001": ["D3-PA", "D3-PMAD", "D3-HBPI"],
# Literal argument value
"T1055.002": ["D3-PA", "D3-PMAD", "D3-HBPI"],
# Literal argument value
"T1003": ["D3-CH", "D3-CR", "D3-MFA", "D3-PMAD"],
# Literal argument value
"T1003.001": ["D3-CH", "D3-CR", "D3-PMAD"],
# Literal argument value
"T1078": ["D3-MFA", "D3-UBA", "D3-UGLPA", "D3-CH"],
# Literal argument value
"T1078.001": ["D3-MFA", "D3-UBA", "D3-CH"],
# Literal argument value
"T1566": ["D3-EAL", "D3-FA", "D3-FH", "D3-UA", "D3-EHR"],
# Literal argument value
"T1566.001": ["D3-EAL", "D3-FA", "D3-FH", "D3-EHR"],
# Literal argument value
"T1566.002": ["D3-UA", "D3-EAL", "D3-EHR"],
# Literal argument value
"T1071": ["D3-AL", "D3-NTA", "D3-PM", "D3-CT"],
# Literal argument value
"T1071.001": ["D3-AL", "D3-NTA", "D3-PM"],
# Literal argument value
"T1053": ["D3-PSA", "D3-PA", "D3-SCHE", "D3-SSA"],
# Literal argument value
"T1053.005": ["D3-PSA", "D3-SCHE", "D3-SSA"],
# Literal argument value
"T1543": ["D3-SMRA", "D3-SSA", "D3-SBAN"],
# Literal argument value
"T1543.003": ["D3-SMRA", "D3-SSA", "D3-SBAN"],
# Literal argument value
"T1547": ["D3-SICA", "D3-SSA", "D3-RRID"],
# Literal argument value
"T1547.001": ["D3-SICA", "D3-SSA", "D3-RRID"],
# Literal argument value
"T1021": ["D3-RTSD", "D3-RPA", "D3-NTA", "D3-MFA"],
# Literal argument value
"T1021.001": ["D3-RTSD", "D3-NTA", "D3-MFA"],
# Literal argument value
"T1021.002": ["D3-RTSD", "D3-NTA", "D3-NI"],
# Literal argument value
"T1560": ["D3-FA", "D3-FCA", "D3-ORA"],
# Literal argument value
"T1560.001": ["D3-FA", "D3-FCA"],
# Literal argument value
"T1048": ["D3-ORA", "D3-NTA", "D3-OTF"],
# Literal argument value
"T1048.003": ["D3-ORA", "D3-NTA", "D3-OTF"],
# Literal argument value
"T1105": ["D3-IRA", "D3-NTA", "D3-FA", "D3-FH"],
# Literal argument value
"T1036": ["D3-FCA", "D3-FH", "D3-FA", "D3-SWI"],
# Literal argument value
"T1036.005": ["D3-FCA", "D3-FH", "D3-FA"],
# Literal argument value
"T1140": ["D3-FA", "D3-DA", "D3-SCA"],
# Literal argument value
"T1070": ["D3-SSA", "D3-LOGA", "D3-SYSM"],
# Literal argument value
"T1070.004": ["D3-SSA", "D3-FAPA"],
# Literal argument value
"T1562": ["D3-SSA", "D3-SYSM", "D3-SMRA"],
# Literal argument value
"T1562.001": ["D3-SSA", "D3-SYSM", "D3-SMRA"],
# Literal argument value
"T1027": ["D3-DA", "D3-FA", "D3-RE"],
# Literal argument value
"T1027.002": ["D3-DA", "D3-FA"],
# Literal argument value
"T1110": ["D3-MFA", "D3-UBA", "D3-CH"],
# Literal argument value
"T1110.001": ["D3-MFA", "D3-UBA", "D3-CH"],
# Literal argument value
"T1082": ["D3-PSA", "D3-PA", "D3-SYSM"],
# Literal argument value
"T1083": ["D3-FAPA", "D3-PA"],
# Literal argument value
"T1497": ["D3-DA", "D3-SE"],
# Literal argument value
"T1218": ["D3-PSA", "D3-PLA", "D3-EAW"],
# Literal argument value
"T1218.011": ["D3-PSA", "D3-PLA", "D3-EAW"],
# Literal argument value
"T1569": ["D3-SMRA", "D3-PSA", "D3-PA"],
# Literal argument value
"T1569.002": ["D3-SMRA", "D3-PSA"],
# Literal argument value
"T1012": ["D3-RRID", "D3-PA"],
# Literal argument value
"T1112": ["D3-RRID", "D3-PA", "D3-REGG"],
# Literal argument value
"T1057": ["D3-PA", "D3-PSA"],
# Literal argument value
"T1518": ["D3-SYSM", "D3-PA"],
# Literal argument value
"T1049": ["D3-NTA", "D3-PA"],
# Literal argument value
"T1016": ["D3-NTA", "D3-PA", "D3-SYSM"],
# Literal argument value
"T1033": ["D3-PA", "D3-UBA"],
# Literal argument value
"T1087": ["D3-UBA", "D3-PA", "D3-SSA"],
# Literal argument value
"T1087.001": ["D3-UBA", "D3-PA"],
# Literal argument value
"T1087.002": ["D3-UBA", "D3-PA"],
# Literal argument value
"T1018": ["D3-NTA", "D3-PA"],
# Literal argument value
"T1047": ["D3-RPA", "D3-PSA", "D3-PA"],
# Literal argument value
"T1190": ["D3-ISVA", "D3-NTA", "D3-AL"],
# Literal argument value
"T1133": ["D3-NTA", "D3-MFA", "D3-RTSD"],
# Literal argument value
"T1486": ["D3-BKUP", "D3-FBKP", "D3-ANTR", "D3-FA"],
# Literal argument value
"T1490": ["D3-BKUP", "D3-FBKP", "D3-SSA"],
# Literal argument value
"T1489": ["D3-SMRA", "D3-SSA"],
# Literal argument value
"T1098": ["D3-UBA", "D3-SSA", "D3-PGOV"],
# Literal argument value
"T1136": ["D3-UBA", "D3-SSA", "D3-UACM"],
# Literal argument value
"T1136.001": ["D3-UBA", "D3-SSA", "D3-UACM"],
# Literal argument value
"T1068": ["D3-SU", "D3-VULM", "D3-HBPI"],
# Literal argument value
"T1548": ["D3-PSEP", "D3-PSA", "D3-PA"],
# Literal argument value
"T1548.002": ["D3-PSEP", "D3-PSA"],
# Literal argument value
"T1134": ["D3-PA", "D3-PSA", "D3-PSEP"],
# Literal argument value
"T1134.001": ["D3-PA", "D3-PSA"],
# Literal argument value
"T1574": ["D3-SWI", "D3-FCA", "D3-PLA"],
# Literal argument value
"T1574.001": ["D3-SWI", "D3-FCA"],
# Literal argument value
"T1204": ["D3-EAL", "D3-FA", "D3-UA"],
# Literal argument value
"T1204.001": ["D3-UA", "D3-EAL"],
# Literal argument value
"T1204.002": ["D3-FA", "D3-EAL", "D3-DA"],
# Literal argument value
"T1071.004": ["D3-DPM", "D3-DNSSM", "D3-NTA"],
# Literal argument value
"T1571": ["D3-NTA", "D3-PM", "D3-AL"],
# Literal argument value
"T1572": ["D3-NTA", "D3-AL", "D3-PM"],
# Literal argument value
"T1041": ["D3-ORA", "D3-NTA"],
# Literal argument value
"T1005": ["D3-FAPA", "D3-PA"],
# Literal argument value
"T1113": ["D3-PA", "D3-PSA"],
# Literal argument value
"T1056": ["D3-PA", "D3-PSA", "D3-HBPI"],
# Literal argument value
"T1056.001": ["D3-PA", "D3-PSA"],
# Literal argument value
"T1560.003": ["D3-FA", "D3-ORA"],
# Literal argument value
"T1583": ["D3-IPMR", "D3-DNSRA"],
# Literal argument value
"T1584": ["D3-IPMR", "D3-DNSRA"],
# Literal argument value
"T1595": ["D3-IRA", "D3-NTA"],
# Literal argument value
"T1589": ["D3-UBA", "D3-THRT"],
# Literal argument value
"T1590": ["D3-NTA", "D3-THRT"],
# Literal argument value
"T1591": ["D3-THRT"],
# Literal argument value
"T1592": ["D3-THRT"],
}
# Define function import_d3fend_mappings
def import_d3fend_mappings(db: Session) -> dict[str, int]:
"""Create ATT&CK → D3FEND mappings.
First tries the D3FEND API for each ATT&CK technique in the DB,
then falls back to the curated mapping for any remaining techniques.
Returns a dict with counts: {created, skipped, total}.
Args:
db (Session): Active SQLAlchemy database session.
Returns:
dict[str, int]: Contains ``created``, ``skipped``, and ``total``
mapping counts.
"""
# Assign created = 0
created = 0
# Assign skipped = 0
skipped = 0
# Get all ATT&CK techniques from the DB
attack_techniques = db.query(Technique).all()
# Assign technique_map = {t.mitre_id: t for t in attack_techniques}
technique_map = {t.mitre_id: t for t in attack_techniques}
# Get all defensive techniques
defensive_techniques = db.query(DefensiveTechnique).all()
# Assign d3fend_map = {dt.d3fend_id: dt for dt in defensive_techniques}
d3fend_map = {dt.d3fend_id: dt for dt in defensive_techniques}
# Check: not d3fend_map
if not d3fend_map:
# Log warning: "No D3FEND techniques in DB — run import_d3fend_te
logger.warning("No D3FEND techniques in DB — run import_d3fend_techniques first")
# Return {"created": 0, "skipped": 0, "total": 0}
return {"created": 0, "skipped": 0, "total": 0}
# Use the curated mapping for now (API per-technique is very slow for 700+ techniques)
for mitre_id, d3fend_ids in _ATTACK_TO_D3FEND.items():
# Assign attack_tech = technique_map.get(mitre_id)
attack_tech = technique_map.get(mitre_id)
# Check: not attack_tech
if not attack_tech:
# Skip to the next loop iteration
continue
# Iterate over d3fend_ids
for d3fend_id in d3fend_ids:
# Assign def_tech = d3fend_map.get(d3fend_id)
def_tech = d3fend_map.get(d3fend_id)
# Check: not def_tech
if not def_tech:
# Skip to the next loop iteration
continue
# Check if mapping already exists
existing = (
db.query(DefensiveTechniqueMapping)
# Chain .filter() call
.filter(
DefensiveTechniqueMapping.attack_technique_id == attack_tech.id,
DefensiveTechniqueMapping.defensive_technique_id == def_tech.id,
)
# Chain .first() call
.first()
)
# Check: existing
if existing:
# Assign skipped = 1
skipped += 1
# Skip to the next loop iteration
continue
# Assign mapping = DefensiveTechniqueMapping(
mapping = DefensiveTechniqueMapping(
# Keyword argument: attack_technique_id
attack_technique_id=attack_tech.id,
# Keyword argument: defensive_technique_id
defensive_technique_id=def_tech.id,
)
# Stage new record(s) for database insertion
db.add(mapping)
# Assign created = 1
created += 1
# Commit all pending changes to the database
db.commit()
# Assign total = db.query(DefensiveTechniqueMapping).count()
total = db.query(DefensiveTechniqueMapping).count()
# Log info: "D3FEND mappings: %d created, %d skipped, %d total
logger.info("D3FEND mappings: %d created, %d skipped, %d total", created, skipped, total)
# Return {"created": created, "skipped": skipped, "total": total}
return {"created": created, "skipped": skipped, "total": total}
# Define function sync
def sync(db: Session) -> dict:
"""Sync D3FEND techniques and ATT&CK mappings.
Called by the Data Sources router when the user clicks Sync for D3FEND.
Returns a flat summary dict suitable for ``last_sync_stats``.
Args:
db (Session): Active SQLAlchemy database session.
Returns:
dict: Flat summary dict suitable for ``last_sync_stats``, containing
``techniques_created``, ``techniques_updated``,
``techniques_total``, ``mappings_created``,
``mappings_skipped``, and ``mappings_total``.
"""
# Import datetime from datetime
from datetime import datetime
# Import DataSource from app.models.data_source
from app.models.data_source import DataSource
# Assign tech_result = import_d3fend_techniques(db)
tech_result = import_d3fend_techniques(db)
# Assign mapping_result = import_d3fend_mappings(db)
mapping_result = import_d3fend_mappings(db)
# Assign summary = {
summary = {
# Literal argument value
"techniques_created": tech_result.get("created", 0),
# Literal argument value
"techniques_updated": tech_result.get("updated", 0),
# Literal argument value
"techniques_total": tech_result.get("total", 0),
# Literal argument value
"mappings_created": mapping_result.get("created", 0),
# Literal argument value
"mappings_skipped": mapping_result.get("skipped", 0),
# Literal argument value
"mappings_total": mapping_result.get("total", 0),
}
# Update DataSource record
ds = db.query(DataSource).filter(DataSource.name == "d3fend").first()
# Check: ds
if ds:
# Assign ds.last_sync_at = datetime.utcnow()
ds.last_sync_at = datetime.utcnow()
# Assign ds.last_sync_status = "success"
ds.last_sync_status = "success"
# Assign ds.last_sync_stats = summary
ds.last_sync_stats = summary
# Commit all pending changes to the database
db.commit()
# Log info: "D3FEND sync complete — %s", summary
logger.info("D3FEND sync complete — %s", summary)
# Return summary
return summary
# Define function get_defenses_for_technique
def get_defenses_for_technique(db: Session, technique_id: UUID) -> list[dict]:
"""Get all D3FEND defensive techniques mapped to a given ATT&CK technique."""
"""Return all D3FEND defensive techniques mapped to a given ATT&CK technique.
Args:
db (Session): Active SQLAlchemy database session.
technique_id (UUID): UUID of the ATT&CK technique to look up.
Returns:
list[dict]: List of defensive technique dicts, each containing
``id``, ``d3fend_id``, ``name``, ``description``, ``tactic``,
and ``d3fend_url``.
"""
# Assign mappings = (
mappings = (
db.query(DefensiveTechniqueMapping)
# Chain .filter() call
.filter(DefensiveTechniqueMapping.attack_technique_id == technique_id)
# Chain .all() call
.all()
)
# Assign results = []
results = []
# Iterate over mappings
for m in mappings:
# Assign dt = m.defensive_technique
dt = m.defensive_technique
# Call results.append()
results.append({
# Literal argument value
"id": str(dt.id),
# Literal argument value
"d3fend_id": dt.d3fend_id,
# Literal argument value
"name": dt.name,
# Literal argument value
"description": dt.description,
# Literal argument value
"tactic": dt.tactic,
# Literal argument value
"d3fend_url": dt.d3fend_url,
})
# Return results
return results