Files
Aegis/backend/app/routers/osint.py
T
kitos 0ddd17047d 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.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 12:37:15 +02:00

289 lines
8.9 KiB
Python

"""OSINT enrichment endpoints — view, review, and trigger enrichment of OSINT items linked to techniques."""
# Import UUID from uuid
from uuid import UUID
# Import APIRouter, Depends, HTTPException, Query, status from fastapi
from fastapi import APIRouter, Depends, HTTPException, Query, status
# Import BaseModel from pydantic
from pydantic import BaseModel
# Import Session from sqlalchemy.orm
from sqlalchemy.orm import Session
# Import get_db from app.database
from app.database import get_db
# Import get_current_user, require_any_role from app.dependencies.auth
from app.dependencies.auth import get_current_user, require_any_role
# Import UnitOfWork from app.domain.unit_of_work
from app.domain.unit_of_work import UnitOfWork
# Import User from app.models.user
from app.models.user import User
# Import from app.services.osint_enrichment_service
from app.services.osint_enrichment_service import (
enrich_technique_with_cves,
get_osint_items_for_technique,
get_osint_summary,
get_technique_or_raise,
mark_osint_reviewed,
)
# Import from app.services.osint_enrichment_service
from app.services.osint_enrichment_service import (
list_osint_items as service_list_osint_items,
)
# Assign router = APIRouter(prefix="/osint", tags=["osint"])
router = APIRouter(prefix="/osint", tags=["osint"])
# ── Schemas ──────────────────────────────────────────────────────────
class OsintItemOut(BaseModel):
"""Serialized OSINT item returned by the API."""
# id: str
id: str
# technique_id: str
technique_id: str
# source_type: str
source_type: str
# source_url: str
source_url: str
# title: str
title: str
# description: str | None
description: str | None
# severity: str | None
severity: str | None
# discovered_at: str | None
discovered_at: str | None
# reviewed: bool
reviewed: bool
# Assign metadata_ = None
metadata_: dict | None = None
# Define class Config
class Config:
"""ORM mode configuration for SQLAlchemy model mapping."""
# Assign from_attributes = True
from_attributes = True
# ── Endpoints ────────────────────────────────────────────────────────
@router.get("/items")
# Define function list_osint_items
def list_osint_items(
# Entry: technique_id
technique_id: UUID | None = Query(None),
# Entry: source_type
source_type: str | None = Query(None),
# Entry: reviewed
reviewed: bool | None = Query(None),
# Entry: offset
offset: int = Query(0, ge=0),
# Entry: limit
limit: int = Query(50, ge=1, le=200),
# Entry: db
db: Session = Depends(get_db),
# Entry: user
user: User = Depends(get_current_user),
) -> list:
"""List OSINT items with optional filters.
Args:
technique_id (UUID | None): Filter by the technique's UUID.
source_type (str | None): Filter by source type (e.g. ``nvd_cve``, ``advisory``).
reviewed (bool | None): Filter by review status; ``None`` returns all.
offset (int): Number of records to skip for pagination.
limit (int): Maximum number of records to return.
db (Session): SQLAlchemy database session.
user (User): Authenticated user making the request.
Returns:
list: Serialised list of OSINT item dicts matching the filters.
"""
# Return service_list_osint_items(
return service_list_osint_items(
db,
# Keyword argument: technique_id
technique_id=technique_id,
# Keyword argument: source_type
source_type=source_type,
# Keyword argument: reviewed
reviewed=reviewed,
# Keyword argument: offset
offset=offset,
# Keyword argument: limit
limit=limit,
)
# Apply the @router.get decorator
@router.get("/summary")
# Define function osint_summary
def osint_summary(
# Entry: db
db: Session = Depends(get_db),
# Entry: user
user: User = Depends(get_current_user),
) -> dict:
"""Return summary statistics for OSINT items.
Args:
db (Session): SQLAlchemy database session.
user (User): Authenticated user making the request.
Returns:
dict: Counts of total, reviewed, and unreviewed items broken down by source type.
"""
# Return get_osint_summary(db)
return get_osint_summary(db)
# Apply the @router.post decorator
@router.post("/items/{item_id}/review")
# Define function review_osint_item
def review_osint_item(
# Entry: item_id
item_id: UUID,
# Entry: db
db: Session = Depends(get_db),
# Entry: user
user: User = Depends(get_current_user),
) -> dict:
"""Mark an OSINT item as reviewed.
Args:
item_id (UUID): Primary key of the OSINT item to mark reviewed.
db (Session): SQLAlchemy database session.
user (User): Authenticated user performing the review.
Returns:
dict: Contains ``id`` (str) and ``reviewed`` (bool ``True``).
"""
# Open context manager
with UnitOfWork(db) as uow:
# Assign item = mark_osint_reviewed(db, str(item_id))
item = mark_osint_reviewed(db, str(item_id))
# Check: not item
if not item:
# Raise HTTPException
raise HTTPException(
# Keyword argument: status_code
status_code=status.HTTP_404_NOT_FOUND,
# Keyword argument: detail
detail="OSINT item not found",
)
# Call uow.commit()
uow.commit()
# Return {"id": str(item.id), "reviewed": True}
return {"id": str(item.id), "reviewed": True}
# Apply the @router.post decorator
@router.post("/enrich/{technique_id}")
# Define function trigger_technique_enrichment
def trigger_technique_enrichment(
# Entry: technique_id
technique_id: UUID,
# Entry: db
db: Session = Depends(get_db),
# Entry: user
user: User = Depends(require_any_role("red_lead", "blue_lead")),
) -> dict:
"""Manually trigger OSINT enrichment for a single technique.
Args:
technique_id (UUID): Primary key of the technique to enrich.
db (Session): SQLAlchemy database session.
user (User): Authenticated red_lead or blue_lead requesting enrichment.
Returns:
dict: Contains ``technique_id`` (str), ``mitre_id`` (str), and ``new_items`` (int).
"""
# Assign technique = get_technique_or_raise(db, technique_id)
technique = get_technique_or_raise(db, technique_id)
# Assign count = enrich_technique_with_cves(db, technique)
count = enrich_technique_with_cves(db, technique)
# Return {
return {
# Literal argument value
"technique_id": str(technique.id),
# Literal argument value
"mitre_id": technique.mitre_id,
# Literal argument value
"new_items": count,
}
# Apply the @router.get decorator
@router.get("/technique/{technique_id}")
# Define function get_technique_osint
def get_technique_osint(
# Entry: technique_id
technique_id: UUID,
# Entry: source_type
source_type: str | None = Query(None),
# Entry: reviewed
reviewed: bool | None = Query(None),
# Entry: db
db: Session = Depends(get_db),
# Entry: user
user: User = Depends(get_current_user),
) -> list:
"""Get all OSINT items for a specific technique.
Args:
technique_id (UUID): Primary key of the technique.
source_type (str | None): Filter by source type (e.g. ``nvd_cve``).
reviewed (bool | None): Filter by review status; ``None`` returns all.
db (Session): SQLAlchemy database session.
user (User): Authenticated user making the request.
Returns:
list: Dicts with OSINT item fields including source URL, severity, and review status.
"""
# Assign items = get_osint_items_for_technique(
items = get_osint_items_for_technique(
db,
str(technique_id),
# Keyword argument: source_type
source_type=source_type,
# Keyword argument: reviewed
reviewed=reviewed,
)
# Return [
return [
{
# Literal argument value
"id": str(item.id),
# Literal argument value
"source_type": item.source_type,
# Literal argument value
"source_url": item.source_url,
# Literal argument value
"title": item.title,
# Literal argument value
"description": item.description,
# Literal argument value
"severity": item.severity,
# Literal argument value
"discovered_at": item.discovered_at.isoformat() if item.discovered_at else None,
# Literal argument value
"reviewed": item.reviewed,
# Literal argument value
"metadata": item.metadata_,
}
for item in items
]