fix: D3FEND expandable cards, System page cleanup, and multi-source improvements
- Make D3FEND defense cards clickable with expandable details and external link - Fix D3FEND URLs to use PascalCase technique names matching the ontology - Remove duplicate Import Atomic Red Team from System page (use Data Sources) - Add bulk Activate All / Deactivate All buttons with confirmation modal - Fix template admin list to show both active and inactive templates - Add PATCH /test-templates/bulk-activate backend endpoint - Auto-seed data sources on container startup via entrypoint.sh - Fix SigmaHQ, CALDERA, GTFOBins import issues - Register D3FEND sync handler in data sources router - Add CIS Controls v8 compliance framework import - Expand Test Catalog source filters (CALDERA, LOLBAS, GTFOBins) - Campaign Generate from Threat Actor now opens actor selector modal - Add coverage snapshot creation button to Comparison page - Update README with accurate data source and feature documentation
This commit is contained in:
@@ -24,7 +24,10 @@ from app.models.technique import Technique
|
||||
from app.models.test_template import TestTemplate
|
||||
from app.models.threat_actor import ThreatActorTechnique
|
||||
from app.services.scoring_service import calculate_technique_score
|
||||
from app.services.compliance_import_service import import_nist_800_53_mappings
|
||||
from app.services.compliance_import_service import (
|
||||
import_nist_800_53_mappings,
|
||||
import_cis_controls_v8_mappings,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/compliance", tags=["compliance"])
|
||||
|
||||
@@ -378,3 +381,13 @@ def import_nist(
|
||||
"""Import NIST 800-53 Rev 5 mappings (admin only)."""
|
||||
result = import_nist_800_53_mappings(db)
|
||||
return result
|
||||
|
||||
|
||||
@router.post("/import/cis-controls-v8")
|
||||
def import_cis(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_role("admin")),
|
||||
):
|
||||
"""Import CIS Controls v8 mappings (admin only)."""
|
||||
result = import_cis_controls_v8_mappings(db)
|
||||
return result
|
||||
|
||||
@@ -40,7 +40,7 @@ def _get_sync_handler(source_name: str):
|
||||
"caldera": ("app.services.caldera_import_service", "sync"),
|
||||
"elastic_rules": ("app.services.elastic_import_service", "sync"),
|
||||
"mitre_cti": ("app.services.threat_actor_import_service", "sync"),
|
||||
# d3fend added in later phases
|
||||
"d3fend": ("app.services.d3fend_import_service", "sync"),
|
||||
}
|
||||
|
||||
if source_name not in handlers:
|
||||
|
||||
@@ -55,13 +55,16 @@ def list_templates(
|
||||
severity: Optional[str] = Query(None, description="Filter by severity (low, medium, high, critical)"),
|
||||
mitre_technique_id: Optional[str] = Query(None, description="Filter by MITRE technique ID"),
|
||||
search: Optional[str] = Query(None, description="Search in name and description"),
|
||||
is_active: Optional[bool] = Query(None, description="Filter by active status (true/false). Omit to return all."),
|
||||
offset: int = Query(0, ge=0),
|
||||
limit: int = Query(50, ge=1, le=200),
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""Return a paginated, filterable list of test templates."""
|
||||
query = db.query(TestTemplate).filter(TestTemplate.is_active == True) # noqa: E712
|
||||
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)
|
||||
@@ -137,6 +140,41 @@ def template_stats(
|
||||
}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# PATCH /test-templates/bulk-activate — activate/deactivate all (admin)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@router.patch("/bulk-activate")
|
||||
def bulk_activate_templates(
|
||||
activate: bool = Query(True, description="True to activate all, False to deactivate all"),
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(require_role("admin")),
|
||||
):
|
||||
"""Set all templates to active or inactive. Admin only."""
|
||||
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},
|
||||
)
|
||||
|
||||
return {
|
||||
"detail": f"{'Activated' if activate else 'Deactivated'} {count} templates",
|
||||
"affected": count,
|
||||
"is_active": activate,
|
||||
}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# GET /test-templates/by-technique/{mitre_id}
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user