"""D3FEND query service — framework-agnostic queries for defensive techniques.""" from __future__ import annotations from typing import Optional from sqlalchemy import func from sqlalchemy.orm import Session from app.domain.errors import EntityNotFoundError from app.models.defensive_technique import DefensiveTechnique from app.models.technique import Technique from app.services.d3fend_import_service import get_defenses_for_technique from app.utils import escape_like def list_defensive_techniques( db: Session, *, tactic: Optional[str] = None, search: Optional[str] = None, offset: int = 0, limit: int = 50, ) -> dict: """List D3FEND defensive techniques with optional filters.""" query = db.query(DefensiveTechnique) if tactic: query = query.filter(DefensiveTechnique.tactic == tactic) if search: pattern = f"%{escape_like(search)}%" query = query.filter( DefensiveTechnique.name.ilike(pattern) | DefensiveTechnique.d3fend_id.ilike(pattern) ) total = query.count() items = query.order_by(DefensiveTechnique.d3fend_id).offset(offset).limit(limit).all() return { "total": total, "offset": offset, "limit": limit, "items": [ { "id": str(dt.id), "d3fend_id": dt.d3fend_id, "name": dt.name, "description": dt.description, "tactic": dt.tactic, "d3fend_url": dt.d3fend_url, } for dt in items ], } def list_d3fend_tactics(db: Session) -> list[dict]: """Return a list of all D3FEND tactics with counts.""" rows = ( db.query(DefensiveTechnique.tactic, func.count(DefensiveTechnique.id)) .group_by(DefensiveTechnique.tactic) .order_by(DefensiveTechnique.tactic) .all() ) return [{"tactic": tactic or "Unknown", "count": count} for tactic, count in rows] def get_defenses_for_attack_technique(db: Session, mitre_id: str) -> dict: """Get all D3FEND defensive techniques mapped to a given ATT&CK technique.""" technique = db.query(Technique).filter(Technique.mitre_id == mitre_id).first() if technique is None: raise EntityNotFoundError("Technique", mitre_id) defenses = get_defenses_for_technique(db, technique.id) return { "mitre_id": mitre_id, "technique_name": technique.name, "defenses": defenses, "total": len(defenses), }