feat: extract advanced_metrics, analytics, test_templates, and auth to services (Tier 1 complete)
This commit is contained in:
@@ -25,13 +25,12 @@ Filters (GET /test-templates)
|
||||
import uuid
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
from sqlalchemy import func, or_
|
||||
from fastapi import APIRouter, Depends, Query, status
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.database import get_db
|
||||
from app.dependencies.auth import get_current_user, require_role, require_any_role
|
||||
from app.models.test_template import TestTemplate
|
||||
from app.dependencies.auth import get_current_user, require_any_role
|
||||
from app.domain.unit_of_work import UnitOfWork
|
||||
from app.models.user import User
|
||||
from app.schemas.test_template import (
|
||||
TestTemplateCreate,
|
||||
@@ -39,6 +38,17 @@ from app.schemas.test_template import (
|
||||
TestTemplateSummary,
|
||||
)
|
||||
from app.services.audit_service import log_action
|
||||
from app.services.test_template_service import (
|
||||
bulk_activate,
|
||||
create_template as create_template_svc,
|
||||
get_template_or_raise,
|
||||
get_template_stats,
|
||||
get_templates_by_technique as templates_by_technique,
|
||||
list_templates,
|
||||
soft_delete_template,
|
||||
toggle_template_active as toggle_template_active_svc,
|
||||
update_template as update_template_svc,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/test-templates", tags=["test-templates"])
|
||||
|
||||
@@ -49,7 +59,7 @@ router = APIRouter(prefix="/test-templates", tags=["test-templates"])
|
||||
|
||||
|
||||
@router.get("", response_model=list[TestTemplateSummary])
|
||||
def list_templates(
|
||||
def _list_templates_handler(
|
||||
source: Optional[str] = Query(None, description="Filter by source (atomic_red_team, mitre, custom)"),
|
||||
platform: Optional[str] = Query(None, description="Filter by platform (windows, linux, macos)"),
|
||||
severity: Optional[str] = Query(None, description="Filter by severity (low, medium, high, critical)"),
|
||||
@@ -62,37 +72,17 @@ def list_templates(
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""Return a paginated, filterable list of test templates."""
|
||||
query = db.query(TestTemplate)
|
||||
if is_active is not None:
|
||||
query = query.filter(TestTemplate.is_active == is_active) # noqa: E712
|
||||
|
||||
if source:
|
||||
query = query.filter(TestTemplate.source == source)
|
||||
if platform:
|
||||
from app.utils import escape_like
|
||||
query = query.filter(TestTemplate.platform.ilike(f"%{escape_like(platform)}%"))
|
||||
if severity:
|
||||
query = query.filter(TestTemplate.severity == severity)
|
||||
if mitre_technique_id:
|
||||
query = query.filter(TestTemplate.mitre_technique_id == mitre_technique_id)
|
||||
if search:
|
||||
from app.utils import escape_like
|
||||
pattern = f"%{escape_like(search)}%"
|
||||
query = query.filter(
|
||||
or_(
|
||||
TestTemplate.name.ilike(pattern),
|
||||
TestTemplate.description.ilike(pattern),
|
||||
)
|
||||
)
|
||||
|
||||
templates = (
|
||||
query
|
||||
.order_by(TestTemplate.mitre_technique_id, TestTemplate.name)
|
||||
.offset(offset)
|
||||
.limit(limit)
|
||||
.all()
|
||||
return list_templates(
|
||||
db,
|
||||
source=source,
|
||||
platform=platform,
|
||||
severity=severity,
|
||||
mitre_technique_id=mitre_technique_id,
|
||||
search=search,
|
||||
is_active=is_active,
|
||||
offset=offset,
|
||||
limit=limit,
|
||||
)
|
||||
return templates
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -105,41 +95,8 @@ def template_stats(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_any_role("red_lead", "blue_lead")),
|
||||
):
|
||||
"""Return catalog statistics: totals by source, platform, active/inactive."""
|
||||
|
||||
total = db.query(func.count(TestTemplate.id)).scalar() or 0
|
||||
active = (
|
||||
db.query(func.count(TestTemplate.id))
|
||||
.filter(TestTemplate.is_active == True) # noqa: E712
|
||||
.scalar()
|
||||
) or 0
|
||||
inactive = total - active
|
||||
|
||||
# By source
|
||||
source_rows = (
|
||||
db.query(TestTemplate.source, func.count(TestTemplate.id))
|
||||
.filter(TestTemplate.is_active == True) # noqa: E712
|
||||
.group_by(TestTemplate.source)
|
||||
.all()
|
||||
)
|
||||
by_source = {source: cnt for source, cnt in source_rows}
|
||||
|
||||
# By platform
|
||||
platform_rows = (
|
||||
db.query(TestTemplate.platform, func.count(TestTemplate.id))
|
||||
.filter(TestTemplate.is_active == True) # noqa: E712
|
||||
.group_by(TestTemplate.platform)
|
||||
.all()
|
||||
)
|
||||
by_platform = {(platform or "unspecified"): cnt for platform, cnt in platform_rows}
|
||||
|
||||
return {
|
||||
"total": total,
|
||||
"active": active,
|
||||
"inactive": inactive,
|
||||
"by_source": by_source,
|
||||
"by_platform": by_platform,
|
||||
}
|
||||
"""Return catalog statistics: active, by_source, by_platform."""
|
||||
return get_template_stats(db)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -154,21 +111,17 @@ def bulk_activate_templates(
|
||||
current_user: User = Depends(require_any_role("red_lead", "blue_lead")),
|
||||
):
|
||||
"""Set all templates to active or inactive."""
|
||||
count = (
|
||||
db.query(TestTemplate)
|
||||
.filter(TestTemplate.is_active != activate)
|
||||
.update({TestTemplate.is_active: activate})
|
||||
)
|
||||
db.commit()
|
||||
|
||||
log_action(
|
||||
db,
|
||||
user_id=current_user.id,
|
||||
action="bulk_activate_templates" if activate else "bulk_deactivate_templates",
|
||||
entity_type="test_template",
|
||||
entity_id=None,
|
||||
details={"affected": count, "is_active": activate},
|
||||
)
|
||||
count = bulk_activate(db, activate=activate)
|
||||
with UnitOfWork(db) as uow:
|
||||
log_action(
|
||||
db,
|
||||
user_id=current_user.id,
|
||||
action="bulk_activate_templates" if activate else "bulk_deactivate_templates",
|
||||
entity_type="test_template",
|
||||
entity_id=None,
|
||||
details={"affected": count, "is_active": activate},
|
||||
)
|
||||
uow.commit()
|
||||
|
||||
return {
|
||||
"detail": f"{'Activated' if activate else 'Deactivated'} {count} templates",
|
||||
@@ -183,22 +136,13 @@ def bulk_activate_templates(
|
||||
|
||||
|
||||
@router.get("/by-technique/{mitre_id}", response_model=list[TestTemplateSummary])
|
||||
def templates_by_technique(
|
||||
def _templates_by_technique_handler(
|
||||
mitre_id: str,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""Return all active templates mapped to a specific MITRE technique."""
|
||||
templates = (
|
||||
db.query(TestTemplate)
|
||||
.filter(
|
||||
TestTemplate.mitre_technique_id == mitre_id,
|
||||
TestTemplate.is_active == True, # noqa: E712
|
||||
)
|
||||
.order_by(TestTemplate.name)
|
||||
.all()
|
||||
)
|
||||
return templates
|
||||
return templates_by_technique(db, mitre_id)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -213,13 +157,7 @@ def get_template(
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""Return full details for a single test template."""
|
||||
template = db.query(TestTemplate).filter(TestTemplate.id == template_id).first()
|
||||
if template is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Test template not found",
|
||||
)
|
||||
return template
|
||||
return get_template_or_raise(db, template_id)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -238,24 +176,23 @@ def create_template(
|
||||
current_user: User = Depends(require_any_role("red_lead", "blue_lead")),
|
||||
):
|
||||
"""Create a custom test template."""
|
||||
template = TestTemplate(**payload.model_dump())
|
||||
db.add(template)
|
||||
db.commit()
|
||||
template = create_template_svc(db, **payload.model_dump())
|
||||
with UnitOfWork(db) as uow:
|
||||
log_action(
|
||||
db,
|
||||
user_id=current_user.id,
|
||||
action="create_test_template",
|
||||
entity_type="test_template",
|
||||
entity_id=template.id,
|
||||
details={
|
||||
"name": template.name,
|
||||
"source": template.source,
|
||||
"mitre_technique_id": template.mitre_technique_id,
|
||||
},
|
||||
)
|
||||
uow.commit()
|
||||
db.refresh(template)
|
||||
|
||||
log_action(
|
||||
db,
|
||||
user_id=current_user.id,
|
||||
action="create_test_template",
|
||||
entity_type="test_template",
|
||||
entity_id=template.id,
|
||||
details={
|
||||
"name": template.name,
|
||||
"source": template.source,
|
||||
"mitre_technique_id": template.mitre_technique_id,
|
||||
},
|
||||
)
|
||||
|
||||
return template
|
||||
|
||||
|
||||
@@ -272,29 +209,19 @@ def update_template(
|
||||
current_user: User = Depends(require_any_role("red_lead", "blue_lead")),
|
||||
):
|
||||
"""Update fields of an existing test template."""
|
||||
template = db.query(TestTemplate).filter(TestTemplate.id == template_id).first()
|
||||
if template is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Test template not found",
|
||||
template = update_template_svc(db, template_id, **payload.model_dump(exclude_unset=True))
|
||||
with UnitOfWork(db) as uow:
|
||||
log_action(
|
||||
db,
|
||||
user_id=current_user.id,
|
||||
action="update_test_template",
|
||||
entity_type="test_template",
|
||||
entity_id=template.id,
|
||||
details={"updated_fields": list(payload.model_dump(exclude_unset=True).keys())},
|
||||
)
|
||||
|
||||
update_data = payload.model_dump(exclude_unset=True)
|
||||
for field, value in update_data.items():
|
||||
setattr(template, field, value)
|
||||
|
||||
db.commit()
|
||||
uow.commit()
|
||||
db.refresh(template)
|
||||
|
||||
log_action(
|
||||
db,
|
||||
user_id=current_user.id,
|
||||
action="update_test_template",
|
||||
entity_type="test_template",
|
||||
entity_id=template.id,
|
||||
details={"updated_fields": list(update_data.keys())},
|
||||
)
|
||||
|
||||
return template
|
||||
|
||||
|
||||
@@ -309,27 +236,20 @@ def toggle_template_active(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_any_role("red_lead", "blue_lead")),
|
||||
):
|
||||
"""Toggle a template between active and inactive."""
|
||||
template = db.query(TestTemplate).filter(TestTemplate.id == template_id).first()
|
||||
if template is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Test template not found",
|
||||
"""Toggle a template between active and inactive (is_active = not is_active)."""
|
||||
template = toggle_template_active_svc(db, template_id)
|
||||
with UnitOfWork(db) as uow:
|
||||
log_action(
|
||||
db,
|
||||
user_id=current_user.id,
|
||||
action="toggle_test_template",
|
||||
entity_type="test_template",
|
||||
entity_id=template.id,
|
||||
details={"name": template.name, "is_active": template.is_active},
|
||||
)
|
||||
|
||||
template.is_active = not template.is_active
|
||||
db.commit()
|
||||
uow.commit()
|
||||
db.refresh(template)
|
||||
|
||||
log_action(
|
||||
db,
|
||||
user_id=current_user.id,
|
||||
action="toggle_test_template",
|
||||
entity_type="test_template",
|
||||
entity_id=template.id,
|
||||
details={"name": template.name, "is_active": template.is_active},
|
||||
)
|
||||
|
||||
return template
|
||||
|
||||
|
||||
@@ -345,23 +265,17 @@ def delete_template(
|
||||
current_user: User = Depends(require_any_role("red_lead", "blue_lead")),
|
||||
):
|
||||
"""Soft-delete a test template by setting ``is_active=False``."""
|
||||
template = db.query(TestTemplate).filter(TestTemplate.id == template_id).first()
|
||||
if template is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Test template not found",
|
||||
template = get_template_or_raise(db, template_id)
|
||||
soft_delete_template(db, template_id)
|
||||
with UnitOfWork(db) as uow:
|
||||
log_action(
|
||||
db,
|
||||
user_id=current_user.id,
|
||||
action="delete_test_template",
|
||||
entity_type="test_template",
|
||||
entity_id=template.id,
|
||||
details={"name": template.name},
|
||||
)
|
||||
|
||||
template.is_active = False
|
||||
db.commit()
|
||||
|
||||
log_action(
|
||||
db,
|
||||
user_id=current_user.id,
|
||||
action="delete_test_template",
|
||||
entity_type="test_template",
|
||||
entity_id=template.id,
|
||||
details={"name": template.name},
|
||||
)
|
||||
uow.commit()
|
||||
|
||||
return {"detail": "Test template deactivated"}
|
||||
|
||||
Reference in New Issue
Block a user