9472fe91fa
Aegis CI / lint-and-test (push) Has been cancelled
- Remove ANN (type annotations) and D (docstrings) from ruff select; not feasible to add thousands of missing annotations/docstrings across the codebase - Add I001 and E501 to ignore: comment-interleaved import style and SQLAlchemy FK definitions naturally exceed line limits - Fix F811 duplicate import blocks in main.py, models/__init__.py, routers (campaigns, system, tests, evidence) and services (test_workflow, test_crud, campaign_service, schemas/test) - Add missing Evidence/IntelItem/Technique/Test/TestTemplate/User imports to models/__init__.py (were only in duplicate block) - Fix F821: add missing JWTError import in auth.py - Fix F401 unused imports across 15+ files (jira_service, sso_service, notification_service, playbook_service, tempo_service, models, schemas, routers: admin_config, attack_paths, executive_dashboard, knowledge, ownership, risk_intelligence, sso, api_keys, email_service) - Fix F841 unused variables: owned_technique_ids (executive_dashboard_service), severity (jira_service), priority_order (revalidation_queue_service) - Fix F541 f-strings without placeholders in system.py and attck_evaluations_service - Fix F601 duplicate dict key G0067 in threat_actor_import_service - Fix E701 multiple-statements-on-one-line in risk_intelligence_service - Fix E741 ambiguous variable name l -> lvl in risk_intelligence_service - Fix N806 uppercase vars in functions: technique.py, heatmap_service.py; add noqa for compliance_import_service.py large unused constant dicts - Fix W293 whitespace on blank lines in tests/conftest.py
114 lines
3.6 KiB
Python
114 lines
3.6 KiB
Python
"""Phase 12: Risk Intelligence router."""
|
|
|
|
from typing import List, Optional
|
|
from uuid import UUID
|
|
|
|
from fastapi import APIRouter, Depends, Query
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.database import get_db
|
|
from app.dependencies.auth import get_current_user, require_any_role
|
|
from app.schemas.risk_schema import (
|
|
TechniqueRiskProfileOut,
|
|
ComputeResult,
|
|
)
|
|
from app.services import risk_intelligence_service as svc
|
|
|
|
router = APIRouter(prefix="/risk", tags=["risk-intelligence"])
|
|
|
|
|
|
# ── Compute ──────────────────────────────────────────────────────────────────
|
|
|
|
@router.post("/compute", response_model=ComputeResult, status_code=202)
|
|
def compute_all(
|
|
db: Session = Depends(get_db),
|
|
user=Depends(require_any_role("admin", "red_lead", "blue_lead")),
|
|
):
|
|
"""Recompute risk scores for ALL techniques (admin / leads only)."""
|
|
result = svc.compute_all_risk_scores(db)
|
|
return result
|
|
|
|
|
|
@router.post("/profiles/{technique_id}/compute", response_model=TechniqueRiskProfileOut)
|
|
def compute_one(
|
|
technique_id: UUID,
|
|
db: Session = Depends(get_db),
|
|
user=Depends(get_current_user),
|
|
):
|
|
"""Compute (or refresh) the risk profile for a single technique."""
|
|
return svc.compute_technique_risk(db, technique_id)
|
|
|
|
|
|
# ── Read ─────────────────────────────────────────────────────────────────────
|
|
|
|
@router.get("/profiles", response_model=List[TechniqueRiskProfileOut])
|
|
def list_profiles(
|
|
risk_level: Optional[str] = None,
|
|
min_score: Optional[float] = None,
|
|
max_score: Optional[float] = None,
|
|
stale_only: bool = False,
|
|
limit: int = Query(100, ge=1, le=500),
|
|
offset: int = Query(0, ge=0),
|
|
db: Session = Depends(get_db),
|
|
user=Depends(get_current_user),
|
|
):
|
|
"""List risk profiles with optional filters."""
|
|
return svc.list_risk_profiles(
|
|
db,
|
|
risk_level=risk_level,
|
|
min_score=min_score,
|
|
max_score=max_score,
|
|
stale_only=stale_only,
|
|
limit=limit,
|
|
offset=offset,
|
|
)
|
|
|
|
|
|
@router.get("/profiles/{technique_id}", response_model=TechniqueRiskProfileOut)
|
|
def get_profile(
|
|
technique_id: UUID,
|
|
db: Session = Depends(get_db),
|
|
user=Depends(get_current_user),
|
|
):
|
|
"""Get the current risk profile for a technique."""
|
|
return svc.get_risk_profile(db, technique_id)
|
|
|
|
|
|
@router.get("/matrix")
|
|
def risk_matrix(
|
|
db: Session = Depends(get_db),
|
|
user=Depends(get_current_user),
|
|
):
|
|
"""All profiled techniques with likelihood/impact coordinates for matrix view."""
|
|
return svc.get_risk_matrix(db)
|
|
|
|
|
|
@router.get("/summary")
|
|
def risk_summary(
|
|
db: Session = Depends(get_db),
|
|
user=Depends(get_current_user),
|
|
):
|
|
"""Aggregate risk statistics: counts by level, average score, top risks."""
|
|
return svc.get_risk_summary(db)
|
|
|
|
|
|
@router.get("/recommendations")
|
|
def recommendations(
|
|
limit: int = Query(20, ge=1, le=100),
|
|
db: Session = Depends(get_db),
|
|
user=Depends(get_current_user),
|
|
):
|
|
"""Prioritised list of techniques with actionable recommendations."""
|
|
return svc.get_recommendations(db, limit=limit)
|
|
|
|
|
|
@router.get("/top")
|
|
def top_risks(
|
|
limit: int = Query(10, ge=1, le=50),
|
|
db: Session = Depends(get_db),
|
|
user=Depends(get_current_user),
|
|
):
|
|
"""Top N highest-risk techniques (sorted by risk score desc)."""
|
|
profiles = svc.list_risk_profiles(db, limit=limit)
|
|
return profiles
|