refactor(pep8): enforce full PEP8 compliance across backend Python codebase
- ruff.toml: select E/W/F/I/N rules, line-length=120, drop legacy ignores - Auto-fix: sort 82 import blocks (isort), remove 29 unused imports, strip 6 trailing-whitespace blank lines in docstrings - main.py: move setup_logging and settings imports to top (E402) - errors.py: noqa N818 on DDD exception names (96 call sites, safe) - intel_service.py: noqa N817 for universal ET alias - atomic/elastic/sigma import services: move _MAX_UNCOMPRESSED_SIZE and _MAX_ENTRIES to module level (N806) - compliance_import_service.py: move SAMPLE_CONTROLS / CIS_CONTROLS to module level; wrap long description strings (N806 + E501) - snapshot_service.py: move STATUS_ORDER dict to module level (N806) - sigma_import_service.py: remove dead dedup_key expression (F841) - threat_actor_import_service.py: remove dead stix_to_actor expression (F841) - data_source.py, seed_demo.py, campaign_scheduler_service.py, lolbas_import_service.py: wrap lines exceeding 120 chars (E501) - d3fend_import_service.py: per-file E501 ignore (data file with long strings) All 439 unit tests pass. ruff check app/ → All checks passed! Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker, declarative_base
|
||||
from sqlalchemy.orm import declarative_base, sessionmaker
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ class DuplicateEntityError(DomainError):
|
||||
# ── State machine ────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class InvalidStateTransition(DomainError):
|
||||
class InvalidStateTransition(DomainError): # noqa: N818 — DDD term, renaming would break 96 call sites
|
||||
"""A state-machine transition is not allowed."""
|
||||
|
||||
def __init__(
|
||||
@@ -67,7 +67,7 @@ class InvalidStateTransition(DomainError):
|
||||
# ── Business rules ────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class BusinessRuleViolation(DomainError):
|
||||
class BusinessRuleViolation(DomainError): # noqa: N818 — DDD term, renaming would break 96 call sites
|
||||
"""An operation violates a business invariant."""
|
||||
|
||||
def __init__(self, message: str) -> None:
|
||||
@@ -89,7 +89,7 @@ class InvalidOperationError(BusinessRuleViolation):
|
||||
# ── Authorization ────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class PermissionViolation(DomainError):
|
||||
class PermissionViolation(DomainError): # noqa: N818 — DDD term, renaming would break 96 call sites
|
||||
"""The user lacks permissions for an action."""
|
||||
|
||||
def __init__(self, message: str = "Insufficient permissions") -> None:
|
||||
|
||||
@@ -6,7 +6,7 @@ This is a domain contract — implementations live in infrastructure/.
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
from typing import Protocol, runtime_checkable
|
||||
from typing import Protocol
|
||||
|
||||
from app.domain.enums import TestState
|
||||
|
||||
|
||||
@@ -34,7 +34,6 @@ from app.domain.errors import (
|
||||
InvalidStateTransition,
|
||||
)
|
||||
|
||||
|
||||
# ── Value objects ────────────────────────────────────────────────────
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from app.domain.entities.technique import TechniqueEntity
|
||||
from app.domain.enums import TechniqueStatus
|
||||
|
||||
|
||||
class TechniqueMapper:
|
||||
|
||||
@@ -15,15 +15,15 @@ import logging
|
||||
from apscheduler.schedulers.background import BackgroundScheduler
|
||||
|
||||
from app.database import SessionLocal
|
||||
from app.services.mitre_sync_service import sync_mitre
|
||||
from app.services.intel_service import scan_intel
|
||||
from app.services.notification_service import cleanup_old_notifications
|
||||
from app.services.snapshot_service import create_snapshot, cleanup_old_snapshots
|
||||
from app.services.campaign_scheduler_service import check_and_run_recurring_campaigns
|
||||
from app.jobs.jira_sync_job import sync_all_jira_links
|
||||
from app.services.osint_enrichment_service import enrich_all_techniques
|
||||
from app.services.stale_detection_service import detect_stale_coverage
|
||||
from app.jobs.retention_job import run_retention_job
|
||||
from app.services.campaign_scheduler_service import check_and_run_recurring_campaigns
|
||||
from app.services.intel_service import scan_intel
|
||||
from app.services.mitre_sync_service import sync_mitre
|
||||
from app.services.notification_service import cleanup_old_notifications
|
||||
from app.services.osint_enrichment_service import enrich_all_techniques
|
||||
from app.services.snapshot_service import cleanup_old_snapshots, create_snapshot
|
||||
from app.services.stale_detection_service import detect_stale_coverage
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
+35
-37
@@ -3,55 +3,55 @@ import os
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
from fastapi import FastAPI, Request, status
|
||||
from fastapi.exceptions import RequestValidationError
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import JSONResponse
|
||||
from fastapi.exceptions import RequestValidationError
|
||||
from slowapi import _rate_limit_exceeded_handler
|
||||
from slowapi.errors import RateLimitExceeded
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
|
||||
from app.routers import auth as auth_router
|
||||
from app.routers import techniques as techniques_router
|
||||
from app.routers import tests as tests_router
|
||||
from app.routers import evidence as evidence_router
|
||||
from app.routers import test_templates as test_templates_router
|
||||
from app.routers import system as system_router
|
||||
from app.routers import metrics as metrics_router
|
||||
from app.routers import users as users_router
|
||||
from app.routers import audit as audit_router
|
||||
from app.routers import notifications as notifications_router
|
||||
from app.routers import reports as reports_router
|
||||
from app.routers import data_sources as data_sources_router
|
||||
from app.routers import threat_actors as threat_actors_router
|
||||
from app.routers import d3fend as d3fend_router
|
||||
from app.routers import detection_rules as detection_rules_router
|
||||
from app.routers import campaigns as campaigns_router
|
||||
from app.routers import heatmap as heatmap_router
|
||||
from app.routers import scores as scores_router
|
||||
from app.routers import operational_metrics as operational_metrics_router
|
||||
from app.routers import compliance as compliance_router
|
||||
from app.routers import snapshots as snapshots_router
|
||||
from app.routers import jira as jira_router
|
||||
from app.routers import worklogs as worklogs_router
|
||||
from app.routers import professional_reports as professional_reports_router
|
||||
from app.routers import analytics as analytics_router
|
||||
from app.routers import advanced_metrics as advanced_metrics_router
|
||||
from app.routers import osint as osint_router
|
||||
from app.config import settings as _settings
|
||||
from app.domain.errors import DomainError
|
||||
from app.jobs.mitre_sync_job import scheduler, start_scheduler
|
||||
from app.limiter import limiter
|
||||
from app.logging_config import setup_logging
|
||||
from app.middleware.error_handler import domain_exception_handler
|
||||
from app.middleware.request_context import RequestContextMiddleware
|
||||
from app.limiter import limiter
|
||||
from app.routers import advanced_metrics as advanced_metrics_router
|
||||
from app.routers import analytics as analytics_router
|
||||
from app.routers import audit as audit_router
|
||||
from app.routers import auth as auth_router
|
||||
from app.routers import campaigns as campaigns_router
|
||||
from app.routers import compliance as compliance_router
|
||||
from app.routers import d3fend as d3fend_router
|
||||
from app.routers import data_sources as data_sources_router
|
||||
from app.routers import detection_rules as detection_rules_router
|
||||
from app.routers import evidence as evidence_router
|
||||
from app.routers import heatmap as heatmap_router
|
||||
from app.routers import jira as jira_router
|
||||
from app.routers import metrics as metrics_router
|
||||
from app.routers import notifications as notifications_router
|
||||
from app.routers import operational_metrics as operational_metrics_router
|
||||
from app.routers import osint as osint_router
|
||||
from app.routers import professional_reports as professional_reports_router
|
||||
from app.routers import reports as reports_router
|
||||
from app.routers import scores as scores_router
|
||||
from app.routers import snapshots as snapshots_router
|
||||
from app.routers import system as system_router
|
||||
from app.routers import techniques as techniques_router
|
||||
from app.routers import test_templates as test_templates_router
|
||||
from app.routers import tests as tests_router
|
||||
from app.routers import threat_actors as threat_actors_router
|
||||
from app.routers import users as users_router
|
||||
from app.routers import worklogs as worklogs_router
|
||||
from app.storage import ensure_bucket_exists
|
||||
from app.jobs.mitre_sync_job import start_scheduler, scheduler
|
||||
|
||||
# Configure structured logging before any module initialises its own logger
|
||||
setup_logging()
|
||||
|
||||
# ── Environment detection ─────────────────────────────────────────────────
|
||||
_IS_PRODUCTION = os.environ.get("AEGIS_ENV", "").lower() == "production"
|
||||
|
||||
# ── Logging ───────────────────────────────────────────────────────────────
|
||||
from app.logging_config import setup_logging
|
||||
|
||||
setup_logging()
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
"""Startup / shutdown logic."""
|
||||
@@ -81,8 +81,6 @@ app.add_middleware(RequestContextMiddleware)
|
||||
app.add_exception_handler(DomainError, domain_exception_handler)
|
||||
|
||||
# ── CORS ──────────────────────────────────────────────────────────────────
|
||||
from app.config import settings as _settings
|
||||
|
||||
_cors_origins: list[str] = [
|
||||
o.strip() for o in _settings.CORS_ORIGINS.split(",") if o.strip()
|
||||
]
|
||||
|
||||
@@ -1,26 +1,30 @@
|
||||
# Import all models here so Alembic can detect them
|
||||
from app.models.user import User
|
||||
from app.models.technique import Technique
|
||||
from app.models.test import Test
|
||||
from app.models.test_template import TestTemplate
|
||||
from app.models.audit import AuditLog
|
||||
from app.models.campaign import Campaign, CampaignTest
|
||||
from app.models.compliance import (
|
||||
ComplianceControl,
|
||||
ComplianceControlMapping,
|
||||
ComplianceFramework,
|
||||
)
|
||||
from app.models.coverage_snapshot import CoverageSnapshot, SnapshotTechniqueState
|
||||
from app.models.data_source import DataSource
|
||||
from app.models.defensive_technique import DefensiveTechnique, DefensiveTechniqueMapping
|
||||
from app.models.detection_rule import DetectionRule
|
||||
from app.models.enums import TeamSide, TechniqueStatus, TestResult, TestState
|
||||
from app.models.evidence import Evidence
|
||||
from app.models.intel import IntelItem
|
||||
from app.models.audit import AuditLog
|
||||
from app.models.notification import Notification
|
||||
from app.models.data_source import DataSource
|
||||
from app.models.detection_rule import DetectionRule
|
||||
from app.models.threat_actor import ThreatActor, ThreatActorTechnique
|
||||
from app.models.defensive_technique import DefensiveTechnique, DefensiveTechniqueMapping
|
||||
from app.models.test_template_detection_rule import TestTemplateDetectionRule
|
||||
from app.models.test_detection_result import TestDetectionResult
|
||||
from app.models.campaign import Campaign, CampaignTest
|
||||
from app.models.compliance import ComplianceFramework, ComplianceControl, ComplianceControlMapping
|
||||
from app.models.coverage_snapshot import CoverageSnapshot, SnapshotTechniqueState
|
||||
from app.models.jira_link import JiraLink, JiraLinkEntityType, JiraSyncDirection
|
||||
from app.models.worklog import Worklog
|
||||
from app.models.notification import Notification
|
||||
from app.models.osint_item import OsintItem
|
||||
from app.models.scoring_config import ScoringConfig
|
||||
from app.models.enums import TechniqueStatus, TestState, TestResult, TeamSide
|
||||
from app.models.technique import Technique
|
||||
from app.models.test import Test
|
||||
from app.models.test_detection_result import TestDetectionResult
|
||||
from app.models.test_template import TestTemplate
|
||||
from app.models.test_template_detection_rule import TestTemplateDetectionRule
|
||||
from app.models.threat_actor import ThreatActor, ThreatActorTechnique
|
||||
from app.models.user import User
|
||||
from app.models.worklog import Worklog
|
||||
|
||||
__all__ = [
|
||||
"User", "Technique", "Test", "TestTemplate", "Evidence",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import uuid
|
||||
from sqlalchemy import Column, String, DateTime, ForeignKey, Index, func
|
||||
from sqlalchemy.dialects.postgresql import UUID, JSONB
|
||||
|
||||
from sqlalchemy import Column, DateTime, ForeignKey, Index, String, func
|
||||
from sqlalchemy.dialects.postgresql import JSONB, UUID
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from app.database import Base
|
||||
@@ -9,7 +10,7 @@ from app.database import Base
|
||||
class AuditLog(Base):
|
||||
"""
|
||||
Audit log model for tracking all system actions.
|
||||
|
||||
|
||||
Records user actions, entity changes, and system events
|
||||
for security auditing and compliance purposes.
|
||||
"""
|
||||
|
||||
@@ -5,11 +5,19 @@ enabling simulation of complete attack chains and APT emulations.
|
||||
"""
|
||||
|
||||
import uuid
|
||||
|
||||
from sqlalchemy import (
|
||||
Column, String, Text, Integer, Boolean, DateTime,
|
||||
ForeignKey, Index, func,
|
||||
Boolean,
|
||||
Column,
|
||||
DateTime,
|
||||
ForeignKey,
|
||||
Index,
|
||||
Integer,
|
||||
String,
|
||||
Text,
|
||||
func,
|
||||
)
|
||||
from sqlalchemy.dialects.postgresql import UUID, JSONB
|
||||
from sqlalchemy.dialects.postgresql import JSONB, UUID
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from app.database import Base
|
||||
|
||||
@@ -5,9 +5,17 @@ MITRE ATT&CK techniques, enabling compliance gap analysis.
|
||||
"""
|
||||
|
||||
import uuid
|
||||
|
||||
from sqlalchemy import (
|
||||
Column, String, Text, Boolean, DateTime,
|
||||
ForeignKey, Index, UniqueConstraint, func,
|
||||
Boolean,
|
||||
Column,
|
||||
DateTime,
|
||||
ForeignKey,
|
||||
Index,
|
||||
String,
|
||||
Text,
|
||||
UniqueConstraint,
|
||||
func,
|
||||
)
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
@@ -6,9 +6,16 @@ per technique per snapshot) to avoid bloated JSONB fields.
|
||||
"""
|
||||
|
||||
import uuid
|
||||
|
||||
from sqlalchemy import (
|
||||
Column, String, Float, Integer, DateTime,
|
||||
ForeignKey, Index, func,
|
||||
Column,
|
||||
DateTime,
|
||||
Float,
|
||||
ForeignKey,
|
||||
Index,
|
||||
Integer,
|
||||
String,
|
||||
func,
|
||||
)
|
||||
from sqlalchemy.dialects.postgresql import JSONB, UUID
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
"""DataSource model — registry of external data sources for import."""
|
||||
|
||||
import uuid
|
||||
from sqlalchemy import Column, String, Text, Boolean, DateTime, Index, func
|
||||
from sqlalchemy.dialects.postgresql import UUID, JSONB
|
||||
|
||||
from sqlalchemy import Boolean, Column, DateTime, Index, String, Text, func
|
||||
from sqlalchemy.dialects.postgresql import JSONB, UUID
|
||||
|
||||
from app.database import Base
|
||||
|
||||
@@ -20,7 +21,8 @@ class DataSource(Base):
|
||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
name = Column(String, unique=True, nullable=False) # e.g. "atomic_red_team"
|
||||
display_name = Column(String, nullable=False) # e.g. "Atomic Red Team"
|
||||
type = Column(String, nullable=False) # attack_procedure / detection_rule / threat_intel / defensive_technique
|
||||
# Values: attack_procedure / detection_rule / threat_intel / defensive_technique
|
||||
type = Column(String, nullable=False)
|
||||
url = Column(String, nullable=True) # URL base of repo/API
|
||||
description = Column(Text, nullable=True)
|
||||
is_enabled = Column(Boolean, default=True)
|
||||
|
||||
@@ -5,9 +5,16 @@ ATT&CK techniques, enabling recommended countermeasure lookups.
|
||||
"""
|
||||
|
||||
import uuid
|
||||
|
||||
from sqlalchemy import (
|
||||
Column, String, Text, DateTime,
|
||||
ForeignKey, Index, UniqueConstraint, func,
|
||||
Column,
|
||||
DateTime,
|
||||
ForeignKey,
|
||||
Index,
|
||||
String,
|
||||
Text,
|
||||
UniqueConstraint,
|
||||
func,
|
||||
)
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
"""DetectionRule model — detection rules from multiple sources."""
|
||||
|
||||
import uuid
|
||||
from sqlalchemy import Column, String, Text, Boolean, DateTime, Index, func
|
||||
from sqlalchemy.dialects.postgresql import UUID, JSONB
|
||||
|
||||
from sqlalchemy import Boolean, Column, DateTime, Index, String, Text, func
|
||||
from sqlalchemy.dialects.postgresql import JSONB, UUID
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import uuid
|
||||
from sqlalchemy import Column, String, Text, DateTime, ForeignKey, Enum, func
|
||||
|
||||
from sqlalchemy import Column, DateTime, Enum, ForeignKey, String, Text, func
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
@@ -10,10 +11,10 @@ from app.models.enums import TeamSide
|
||||
class Evidence(Base):
|
||||
"""
|
||||
Evidence model for storing file metadata associated with tests.
|
||||
|
||||
|
||||
Files are stored in MinIO, and this model tracks the file location,
|
||||
integrity hash, and upload metadata.
|
||||
|
||||
|
||||
The ``team`` field distinguishes whether this evidence was uploaded by
|
||||
Red Team (attack evidence) or Blue Team (detection evidence).
|
||||
"""
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import uuid
|
||||
from sqlalchemy import Column, String, Boolean, DateTime, ForeignKey, func
|
||||
|
||||
from sqlalchemy import Boolean, Column, DateTime, ForeignKey, String, func
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
@@ -9,7 +10,7 @@ from app.database import Base
|
||||
class IntelItem(Base):
|
||||
"""
|
||||
Intelligence item model for tracking threat intelligence related to techniques.
|
||||
|
||||
|
||||
Stores URLs and metadata from automated intel scans that may indicate
|
||||
new attack variations or detection bypasses for specific techniques.
|
||||
"""
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
|
||||
import enum
|
||||
import uuid
|
||||
from sqlalchemy import Column, String, DateTime, ForeignKey, Enum as SQLEnum, Index, func
|
||||
from sqlalchemy.dialects.postgresql import UUID, JSONB
|
||||
|
||||
from sqlalchemy import Column, DateTime, ForeignKey, Index, String, func
|
||||
from sqlalchemy import Enum as SQLEnum
|
||||
from sqlalchemy.dialects.postgresql import JSONB, UUID
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from app.database import Base
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
"""Notification model — in-app notifications for user actions."""
|
||||
|
||||
import uuid
|
||||
from sqlalchemy import Column, String, Text, Boolean, DateTime, ForeignKey, Index, func
|
||||
|
||||
from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Index, String, Text, func
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
"""OSINT enrichment items — CVEs, blogs, PoCs, and advisories linked to techniques."""
|
||||
|
||||
import uuid
|
||||
from sqlalchemy import Column, String, Text, Boolean, DateTime, ForeignKey, func
|
||||
from sqlalchemy.dialects.postgresql import UUID, JSONB
|
||||
|
||||
from sqlalchemy import Boolean, Column, DateTime, ForeignKey, String, Text, func
|
||||
from sqlalchemy.dialects.postgresql import JSONB, UUID
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from app.database import Base
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import uuid
|
||||
|
||||
from sqlalchemy import Column, Float, DateTime, ForeignKey, func
|
||||
from sqlalchemy import Column, DateTime, Float, ForeignKey, func
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
|
||||
from app.database import Base
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import Column, String, Text, Boolean, DateTime, Enum
|
||||
from sqlalchemy.dialects.postgresql import UUID, JSONB
|
||||
from sqlalchemy import Boolean, Column, DateTime, Enum, String, Text
|
||||
from sqlalchemy.dialects.postgresql import JSONB, UUID
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from app.database import Base
|
||||
@@ -12,7 +11,7 @@ from app.models.enums import TechniqueStatus
|
||||
class Technique(Base):
|
||||
"""
|
||||
MITRE ATT&CK Technique model.
|
||||
|
||||
|
||||
Represents an attack technique from the MITRE ATT&CK framework,
|
||||
including its coverage status and associated tests.
|
||||
"""
|
||||
|
||||
@@ -1,10 +1,22 @@
|
||||
import uuid
|
||||
from sqlalchemy import Column, String, Text, Boolean, Integer, DateTime, ForeignKey, Enum, Index, func
|
||||
|
||||
from sqlalchemy import (
|
||||
Boolean,
|
||||
Column,
|
||||
DateTime,
|
||||
Enum,
|
||||
ForeignKey,
|
||||
Index,
|
||||
Integer,
|
||||
String,
|
||||
Text,
|
||||
func,
|
||||
)
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from app.database import Base
|
||||
from app.models.enums import TestState, TestResult
|
||||
from app.models.enums import TestResult, TestState
|
||||
|
||||
|
||||
class Test(Base):
|
||||
|
||||
@@ -5,9 +5,16 @@ rule as triggered / not triggered / not applicable, along with notes.
|
||||
"""
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import Column, String, Text, Boolean, DateTime, ForeignKey, Index, UniqueConstraint
|
||||
from sqlalchemy import (
|
||||
Boolean,
|
||||
Column,
|
||||
DateTime,
|
||||
ForeignKey,
|
||||
Index,
|
||||
Text,
|
||||
UniqueConstraint,
|
||||
)
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
"""TestTemplate model — predefined test catalog entries."""
|
||||
|
||||
import uuid
|
||||
from sqlalchemy import Column, String, Text, Boolean, DateTime, Index, func
|
||||
|
||||
from sqlalchemy import Boolean, Column, DateTime, Index, String, Text, func
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
|
||||
from app.database import Base
|
||||
|
||||
@@ -5,9 +5,8 @@ for a given test template / attack procedure.
|
||||
"""
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import Column, Boolean, ForeignKey, Index, UniqueConstraint
|
||||
from sqlalchemy import Boolean, Column, ForeignKey, Index, UniqueConstraint
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
|
||||
@@ -5,11 +5,19 @@ techniques, imported from MITRE CTI (STIX 2.0).
|
||||
"""
|
||||
|
||||
import uuid
|
||||
|
||||
from sqlalchemy import (
|
||||
Column, String, Text, Boolean, DateTime,
|
||||
ForeignKey, Index, UniqueConstraint, func,
|
||||
Boolean,
|
||||
Column,
|
||||
DateTime,
|
||||
ForeignKey,
|
||||
Index,
|
||||
String,
|
||||
Text,
|
||||
UniqueConstraint,
|
||||
func,
|
||||
)
|
||||
from sqlalchemy.dialects.postgresql import UUID, JSONB
|
||||
from sqlalchemy.dialects.postgresql import JSONB, UUID
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from app.database import Base
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import uuid
|
||||
from sqlalchemy import Column, String, Boolean, DateTime, func
|
||||
|
||||
from sqlalchemy import Boolean, Column, DateTime, String, func
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
|
||||
from app.database import Base
|
||||
@@ -8,7 +9,7 @@ from app.database import Base
|
||||
class User(Base):
|
||||
"""
|
||||
User model for authentication and authorization.
|
||||
|
||||
|
||||
Possible roles:
|
||||
- admin: Full system access
|
||||
- red_tech: Red team technician - can create and edit tests
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
"""Worklog model — immutable internal time-tracking records."""
|
||||
|
||||
import uuid
|
||||
from sqlalchemy import Column, String, Integer, DateTime, ForeignKey, Text, Index, func
|
||||
from sqlalchemy.dialects.postgresql import UUID, JSONB
|
||||
|
||||
from sqlalchemy import Column, DateTime, ForeignKey, Index, Integer, String, Text, func
|
||||
from sqlalchemy.dialects.postgresql import JSONB, UUID
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from app.database import Base
|
||||
|
||||
@@ -11,11 +11,10 @@ import os
|
||||
|
||||
from fastapi import APIRouter, Cookie, Depends, Request, Response
|
||||
from fastapi.security import OAuth2PasswordRequestForm
|
||||
from jose import JWTError, jwt
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from jose import jwt, JWTError
|
||||
|
||||
from app.auth import create_access_token, blacklist_token, verify_password
|
||||
from app.auth import blacklist_token, create_access_token, verify_password
|
||||
from app.config import settings
|
||||
from app.database import get_db
|
||||
from app.dependencies.auth import get_current_user
|
||||
@@ -24,13 +23,15 @@ from app.domain.unit_of_work import UnitOfWork
|
||||
from app.limiter import limiter
|
||||
from app.middleware.request_context import resolve_client_ip
|
||||
from app.models.user import User
|
||||
from app.services.auth_service import (
|
||||
_DUMMY_HASH,
|
||||
change_password as auth_change_password,
|
||||
)
|
||||
from app.services.audit_service import log_action
|
||||
from app.schemas.auth import TokenResponse, UserOut
|
||||
from app.schemas.user import PasswordChange
|
||||
from app.services.audit_service import log_action
|
||||
from app.services.auth_service import (
|
||||
_DUMMY_HASH,
|
||||
)
|
||||
from app.services.auth_service import (
|
||||
change_password as auth_change_password,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/auth", tags=["auth"])
|
||||
|
||||
|
||||
@@ -9,29 +9,51 @@ import uuid
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from pydantic import BaseModel, Field
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.database import get_db
|
||||
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.services.campaign_service import generate_campaign_from_threat_actor
|
||||
from app.services.audit_service import log_action
|
||||
from app.services.campaign_crud_service import (
|
||||
activate_campaign as crud_activate,
|
||||
)
|
||||
from app.services.campaign_crud_service import (
|
||||
add_test_to_campaign as crud_add_test,
|
||||
activate_campaign as crud_activate,
|
||||
)
|
||||
from app.services.campaign_crud_service import (
|
||||
complete_campaign as crud_complete,
|
||||
)
|
||||
from app.services.campaign_crud_service import (
|
||||
create_campaign as crud_create,
|
||||
)
|
||||
from app.services.campaign_crud_service import (
|
||||
get_campaign_detail as crud_get_detail,
|
||||
)
|
||||
from app.services.campaign_crud_service import (
|
||||
get_campaign_history as crud_get_history,
|
||||
)
|
||||
from app.services.campaign_crud_service import (
|
||||
get_campaign_progress_data as crud_get_progress,
|
||||
)
|
||||
from app.services.campaign_crud_service import (
|
||||
list_campaigns as crud_list,
|
||||
)
|
||||
from app.services.campaign_crud_service import (
|
||||
remove_test_from_campaign as crud_remove_test,
|
||||
)
|
||||
from app.services.campaign_crud_service import (
|
||||
schedule_campaign as crud_schedule,
|
||||
)
|
||||
from app.services.campaign_crud_service import (
|
||||
serialize_campaign,
|
||||
)
|
||||
from app.services.campaign_crud_service import (
|
||||
update_campaign as crud_update,
|
||||
)
|
||||
from app.domain.unit_of_work import UnitOfWork
|
||||
from app.services.audit_service import log_action
|
||||
from app.services.campaign_service import generate_campaign_from_threat_actor
|
||||
from app.services.notification_service import notify_role
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -13,15 +13,15 @@ from sqlalchemy.orm import Session
|
||||
from app.database import get_db
|
||||
from app.dependencies.auth import get_current_user, require_role
|
||||
from app.models.user import User
|
||||
from app.services.compliance_import_service import (
|
||||
import_cis_controls_v8_mappings,
|
||||
import_nist_800_53_mappings,
|
||||
)
|
||||
from app.services.compliance_service import (
|
||||
list_frameworks,
|
||||
get_framework_status,
|
||||
build_framework_report_csv,
|
||||
get_framework_gaps,
|
||||
)
|
||||
from app.services.compliance_import_service import (
|
||||
import_nist_800_53_mappings,
|
||||
import_cis_controls_v8_mappings,
|
||||
get_framework_status,
|
||||
list_frameworks,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/compliance", tags=["compliance"])
|
||||
|
||||
@@ -10,13 +10,15 @@ from app.database import get_db
|
||||
from app.dependencies.auth import get_current_user, require_role
|
||||
from app.models.user import User
|
||||
from app.services.d3fend_import_service import (
|
||||
import_d3fend_techniques,
|
||||
import_d3fend_mappings,
|
||||
import_d3fend_techniques,
|
||||
)
|
||||
from app.services.d3fend_query_service import (
|
||||
get_defenses_for_attack_technique,
|
||||
list_d3fend_tactics,
|
||||
)
|
||||
from app.services.d3fend_query_service import (
|
||||
list_defensive_techniques as list_defensive_techniques_svc,
|
||||
list_d3fend_tactics,
|
||||
get_defenses_for_attack_technique,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -5,10 +5,11 @@ Provides a centralized panel for managing all external data sources
|
||||
including sync triggers, enable/disable toggles, and statistics.
|
||||
"""
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import Optional
|
||||
|
||||
from app.database import get_db
|
||||
from app.dependencies.auth import require_role
|
||||
@@ -23,7 +24,6 @@ from app.services.data_source_service import (
|
||||
update_source,
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Pydantic schemas for request validation
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@@ -14,17 +14,16 @@ from pydantic import BaseModel
|
||||
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.dependencies.auth import get_current_user, require_any_role, require_role
|
||||
from app.models.user import User
|
||||
from app.services.detection_rule_service import (
|
||||
list_rules,
|
||||
get_rules_for_template,
|
||||
auto_associate_rules,
|
||||
get_rules_for_test,
|
||||
evaluate_rule,
|
||||
get_rules_for_template,
|
||||
get_rules_for_test,
|
||||
list_rules,
|
||||
)
|
||||
|
||||
|
||||
# ── Pydantic schemas for request validation ────────────────────────────
|
||||
|
||||
|
||||
|
||||
@@ -28,23 +28,23 @@ from fastapi import APIRouter, Depends, File, Form, Query, Request, UploadFile,
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.database import get_db
|
||||
from app.domain.unit_of_work import UnitOfWork
|
||||
from app.dependencies.auth import get_current_user
|
||||
from app.domain.unit_of_work import UnitOfWork
|
||||
from app.limiter import limiter
|
||||
from app.models.enums import TeamSide
|
||||
from app.models.evidence import Evidence
|
||||
from app.models.user import User
|
||||
from app.schemas.evidence import EvidenceOut
|
||||
from app.services.audit_service import log_action
|
||||
from app.services.evidence_service import (
|
||||
MAX_UPLOAD_SIZE,
|
||||
get_evidence_or_raise,
|
||||
get_test_or_raise,
|
||||
list_evidence_for_test,
|
||||
MAX_UPLOAD_SIZE,
|
||||
validate_delete_permission,
|
||||
validate_file,
|
||||
validate_upload_permission,
|
||||
)
|
||||
from app.limiter import limiter
|
||||
from app.storage import get_presigned_url, upload_file
|
||||
|
||||
router = APIRouter(tags=["evidence"])
|
||||
|
||||
@@ -17,7 +17,7 @@ from app.schemas.jira_schema import (
|
||||
JiraLinkCreate,
|
||||
JiraLinkOut,
|
||||
)
|
||||
from app.services import jira_service, audit_service
|
||||
from app.services import audit_service, jira_service
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -19,10 +19,10 @@ from app.domain.unit_of_work import UnitOfWork
|
||||
from app.models.user import User
|
||||
from app.schemas.notification import NotificationOut, UnreadCountOut
|
||||
from app.services.notification_service import (
|
||||
list_notifications,
|
||||
mark_as_read,
|
||||
mark_all_as_read,
|
||||
get_unread_count,
|
||||
list_notifications,
|
||||
mark_all_as_read,
|
||||
mark_as_read,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/notifications", tags=["notifications"])
|
||||
|
||||
@@ -11,9 +11,8 @@ from app.database import get_db
|
||||
from app.dependencies.auth import get_current_user
|
||||
from app.models.user import User
|
||||
from app.services.operational_metrics_service import (
|
||||
get_all_operational_metrics,
|
||||
get_operational_trend,
|
||||
get_metrics_by_team,
|
||||
get_operational_trend,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/metrics/operational", tags=["operational-metrics"])
|
||||
|
||||
@@ -4,7 +4,7 @@ OSINT items (CVEs, advisories, etc.) linked to techniques.
|
||||
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi import APIRouter, Depends, Query, HTTPException, status
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
@@ -17,9 +17,11 @@ from app.services.osint_enrichment_service import (
|
||||
get_osint_items_for_technique,
|
||||
get_osint_summary,
|
||||
get_technique_or_raise,
|
||||
list_osint_items as service_list_osint_items,
|
||||
mark_osint_reviewed,
|
||||
)
|
||||
from app.services.osint_enrichment_service import (
|
||||
list_osint_items as service_list_osint_items,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/osint", tags=["osint"])
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@ from sqlalchemy.orm import Session
|
||||
|
||||
from app.database import get_db
|
||||
from app.dependencies.auth import get_current_user, require_any_role
|
||||
from app.models.user import User
|
||||
from app.limiter import limiter
|
||||
from app.models.user import User
|
||||
from app.services import report_generation_service
|
||||
|
||||
router = APIRouter(prefix="/reports/generate", tags=["professional-reports"])
|
||||
|
||||
@@ -13,17 +13,16 @@ from app.database import get_db
|
||||
from app.dependencies.auth import get_current_user, require_role
|
||||
from app.domain.unit_of_work import UnitOfWork
|
||||
from app.models.user import User
|
||||
from app.services.scoring_service import (
|
||||
score_technique_by_mitre_id,
|
||||
score_actor_by_id,
|
||||
calculate_tactic_score,
|
||||
calculate_organization_score,
|
||||
get_score_history,
|
||||
)
|
||||
from app.services.scoring_config_service import (
|
||||
get_weights_dict,
|
||||
update_scoring_weights,
|
||||
)
|
||||
from app.services.scoring_service import (
|
||||
calculate_tactic_score,
|
||||
get_score_history,
|
||||
score_actor_by_id,
|
||||
score_technique_by_mitre_id,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/scores", tags=["scores"])
|
||||
|
||||
|
||||
@@ -17,18 +17,19 @@ from app.dependencies.auth import get_current_user, require_any_role, require_ro
|
||||
from app.domain.errors import BusinessRuleViolation
|
||||
from app.domain.unit_of_work import UnitOfWork
|
||||
from app.models.user import User
|
||||
from app.services.snapshot_service import (
|
||||
create_snapshot,
|
||||
compare_snapshots,
|
||||
cleanup_old_snapshots,
|
||||
get_coverage_evolution,
|
||||
serialize_snapshot_summary,
|
||||
list_snapshots as list_snapshots_svc,
|
||||
get_snapshot_or_raise,
|
||||
get_snapshot_detail,
|
||||
delete_snapshot,
|
||||
)
|
||||
from app.services.audit_service import log_action
|
||||
from app.services.snapshot_service import (
|
||||
compare_snapshots,
|
||||
create_snapshot,
|
||||
delete_snapshot,
|
||||
get_coverage_evolution,
|
||||
get_snapshot_detail,
|
||||
get_snapshot_or_raise,
|
||||
serialize_snapshot_summary,
|
||||
)
|
||||
from app.services.snapshot_service import (
|
||||
list_snapshots as list_snapshots_svc,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -12,12 +12,12 @@ from sqlalchemy.orm import Session
|
||||
|
||||
from app.database import get_db
|
||||
from app.dependencies.auth import require_role
|
||||
from app.models.user import User
|
||||
from app.services.mitre_sync_service import sync_mitre
|
||||
from app.services.intel_service import scan_intel
|
||||
from app.services.atomic_import_service import import_atomic_red_team
|
||||
from app.jobs.mitre_sync_job import scheduler
|
||||
from app.limiter import limiter
|
||||
from app.models.user import User
|
||||
from app.services.atomic_import_service import import_atomic_red_team
|
||||
from app.services.intel_service import scan_intel
|
||||
from app.services.mitre_sync_service import sync_mitre
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -9,11 +9,11 @@ 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.dependencies.auth import get_current_user, require_any_role, require_role
|
||||
from app.dependencies.repositories import get_technique_repository
|
||||
from app.domain.entities.technique import TechniqueEntity
|
||||
from app.domain.errors import DuplicateEntityError, EntityNotFoundError
|
||||
from app.domain.enums import TechniqueStatus
|
||||
from app.domain.errors import DuplicateEntityError, EntityNotFoundError
|
||||
from app.domain.unit_of_work import UnitOfWork
|
||||
from app.infrastructure.persistence.repositories.sa_technique_repository import (
|
||||
SATechniqueRepository,
|
||||
|
||||
@@ -40,13 +40,21 @@ from app.schemas.test_template import (
|
||||
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,
|
||||
)
|
||||
from app.services.test_template_service import (
|
||||
create_template as create_template_svc,
|
||||
)
|
||||
from app.services.test_template_service import (
|
||||
get_templates_by_technique as templates_by_technique,
|
||||
)
|
||||
from app.services.test_template_service import (
|
||||
toggle_template_active as toggle_template_active_svc,
|
||||
)
|
||||
from app.services.test_template_service import (
|
||||
update_template as update_template_svc,
|
||||
)
|
||||
|
||||
|
||||
@@ -26,49 +26,84 @@ from sqlalchemy.orm import Session
|
||||
|
||||
from app.database import get_db
|
||||
from app.dependencies.auth import get_current_user, require_any_role, require_role
|
||||
from app.domain.enums import DataClassification
|
||||
from app.domain.unit_of_work import UnitOfWork
|
||||
from app.limiter import limiter
|
||||
from app.models.enums import TestState
|
||||
from app.models.user import User
|
||||
from app.schemas.test import (
|
||||
TestBlueUpdate,
|
||||
TestBlueValidate,
|
||||
TestClassificationUpdate,
|
||||
TestCreate,
|
||||
TestOut,
|
||||
TestUpdate,
|
||||
TestRedUpdate,
|
||||
TestBlueUpdate,
|
||||
TestRedValidate,
|
||||
TestBlueValidate,
|
||||
TestRemediationUpdate,
|
||||
TestClassificationUpdate,
|
||||
TestUpdate,
|
||||
)
|
||||
from app.schemas.test_template import TestTemplateInstantiate
|
||||
from app.domain.unit_of_work import UnitOfWork
|
||||
from app.services.audit_service import log_action
|
||||
from app.services.status_service import recalculate_technique_status
|
||||
from app.services.test_crud_service import (
|
||||
create_test as crud_create_test,
|
||||
)
|
||||
from app.services.test_crud_service import (
|
||||
create_test_from_template as crud_create_from_template,
|
||||
)
|
||||
from app.services.test_crud_service import (
|
||||
get_test_detail as crud_get_test_detail,
|
||||
)
|
||||
from app.services.test_crud_service import (
|
||||
get_test_or_raise as crud_get_test_or_raise,
|
||||
)
|
||||
from app.services.test_crud_service import (
|
||||
get_test_timeline as crud_get_test_timeline,
|
||||
)
|
||||
from app.services.test_crud_service import (
|
||||
get_test_with_technique as crud_get_test_with_technique,
|
||||
)
|
||||
from app.services.test_crud_service import (
|
||||
list_tests as crud_list_tests,
|
||||
)
|
||||
from app.services.test_crud_service import (
|
||||
update_test as crud_update_test,
|
||||
)
|
||||
from app.services.test_crud_service import (
|
||||
update_test_blue as crud_update_test_blue,
|
||||
)
|
||||
from app.services.test_crud_service import (
|
||||
update_test_red as crud_update_test_red,
|
||||
)
|
||||
from app.services.test_workflow_service import (
|
||||
start_execution as wf_start_execution,
|
||||
submit_red_evidence as wf_submit_red,
|
||||
submit_blue_evidence as wf_submit_blue,
|
||||
validate_as_red_lead as wf_validate_red,
|
||||
validate_as_blue_lead as wf_validate_blue,
|
||||
reopen_test as wf_reopen,
|
||||
handle_remediation_completed as wf_handle_remediation,
|
||||
get_retest_chain as wf_get_retest_chain,
|
||||
)
|
||||
from app.services.test_workflow_service import (
|
||||
handle_remediation_completed as wf_handle_remediation,
|
||||
)
|
||||
from app.services.test_workflow_service import (
|
||||
pause_timer as wf_pause_timer,
|
||||
)
|
||||
from app.services.test_workflow_service import (
|
||||
reopen_test as wf_reopen,
|
||||
)
|
||||
from app.services.test_workflow_service import (
|
||||
resume_timer as wf_resume_timer,
|
||||
)
|
||||
from app.services.test_workflow_service import (
|
||||
start_execution as wf_start_execution,
|
||||
)
|
||||
from app.services.test_workflow_service import (
|
||||
submit_blue_evidence as wf_submit_blue,
|
||||
)
|
||||
from app.services.test_workflow_service import (
|
||||
submit_red_evidence as wf_submit_red,
|
||||
)
|
||||
from app.services.test_workflow_service import (
|
||||
validate_as_blue_lead as wf_validate_blue,
|
||||
)
|
||||
from app.services.test_workflow_service import (
|
||||
validate_as_red_lead as wf_validate_red,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/tests", tags=["tests"])
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ from app.database import get_db
|
||||
from app.dependencies.auth import require_role
|
||||
from app.domain.unit_of_work import UnitOfWork
|
||||
from app.models.user import User
|
||||
from app.schemas.user import UserCreate, UserUpdate, UserOut
|
||||
from app.schemas.user import UserCreate, UserOut, UserUpdate
|
||||
from app.services.audit_service import log_action
|
||||
from app.services.user_service import (
|
||||
create_user,
|
||||
|
||||
@@ -4,7 +4,7 @@ from datetime import datetime
|
||||
from typing import Optional
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from fastapi import APIRouter, Depends
|
||||
from pydantic import BaseModel, Field
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
|
||||
@@ -1,32 +1,28 @@
|
||||
"""Pydantic schemas — re-exported for convenient imports."""
|
||||
|
||||
from app.schemas.auth import LoginRequest, TokenResponse, UserOut
|
||||
|
||||
from app.schemas.evidence import EvidenceOut, EvidenceUpload
|
||||
from app.schemas.technique import (
|
||||
TechniqueCreate,
|
||||
TechniqueOut,
|
||||
TechniqueSummary,
|
||||
TechniqueUpdate,
|
||||
)
|
||||
|
||||
from app.schemas.test import (
|
||||
TestBlueUpdate,
|
||||
TestBlueValidate,
|
||||
TestCreate,
|
||||
TestOut,
|
||||
TestRedUpdate,
|
||||
TestRedValidate,
|
||||
TestUpdate,
|
||||
TestValidate,
|
||||
TestRedUpdate,
|
||||
TestBlueUpdate,
|
||||
TestRedValidate,
|
||||
TestBlueValidate,
|
||||
)
|
||||
|
||||
from app.schemas.evidence import EvidenceOut, EvidenceUpload
|
||||
|
||||
from app.schemas.test_template import (
|
||||
TestTemplateOut,
|
||||
TestTemplateCreate,
|
||||
TestTemplateSummary,
|
||||
TestTemplateInstantiate,
|
||||
TestTemplateOut,
|
||||
TestTemplateSummary,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
|
||||
@@ -7,7 +7,6 @@ from pydantic import BaseModel, ConfigDict
|
||||
|
||||
from app.models.enums import TechniqueStatus
|
||||
|
||||
|
||||
# ── Create ──────────────────────────────────────────────────────────
|
||||
|
||||
class TechniqueCreate(BaseModel):
|
||||
|
||||
@@ -8,7 +8,6 @@ from pydantic import BaseModel, ConfigDict
|
||||
from app.domain.enums import DataClassification
|
||||
from app.models.enums import TestResult, TestState
|
||||
|
||||
|
||||
# ── Create ──────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ from datetime import datetime
|
||||
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
|
||||
|
||||
# ── Full output ─────────────────────────────────────────────────────
|
||||
|
||||
|
||||
|
||||
@@ -4,8 +4,7 @@ import re
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, EmailStr, field_validator
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, field_validator
|
||||
|
||||
# ── Username policy ─────────────────────────────────────────────────
|
||||
|
||||
|
||||
@@ -18,14 +18,14 @@ from datetime import datetime, timedelta
|
||||
|
||||
from app.auth import hash_password
|
||||
from app.database import SessionLocal
|
||||
from app.models.user import User
|
||||
from app.models.audit import AuditLog
|
||||
from app.models.enums import TeamSide, TechniqueStatus, TestResult, TestState
|
||||
from app.models.evidence import Evidence
|
||||
from app.models.notification import Notification
|
||||
from app.models.technique import Technique
|
||||
from app.models.test import Test
|
||||
from app.models.test_template import TestTemplate
|
||||
from app.models.evidence import Evidence
|
||||
from app.models.audit import AuditLog
|
||||
from app.models.notification import Notification
|
||||
from app.models.enums import TechniqueStatus, TestState, TestResult, TeamSide
|
||||
from app.models.user import User
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -214,7 +214,11 @@ def _seed_tests(db, users: list[User], techniques: list[Technique], count: int =
|
||||
name=f"Demo Test {i + 1} — {technique.name[:40]}",
|
||||
description=f"Automated demo test #{i + 1} for {technique.mitre_id}.",
|
||||
platform=random.choice(PLATFORMS),
|
||||
procedure_text=f"Step 1: Prepare environment.\nStep 2: Execute {technique.mitre_id} procedure.\nStep 3: Observe results.",
|
||||
procedure_text=(
|
||||
f"Step 1: Prepare environment.\n"
|
||||
f"Step 2: Execute {technique.mitre_id} procedure.\n"
|
||||
f"Step 3: Observe results."
|
||||
),
|
||||
tool_used=random.choice(["powershell", "bash", "cmd", "python", "caldera", "metasploit"]),
|
||||
execution_date=datetime.utcnow() - timedelta(days=random.randint(0, 60)),
|
||||
created_by=creator.id,
|
||||
@@ -353,7 +357,11 @@ def _seed_templates(db, techniques: list[Technique], count: int = 10) -> None:
|
||||
description=f"Demo template: {name}. Targets {technique.mitre_id} ({technique.name}).",
|
||||
source="demo",
|
||||
source_url=None,
|
||||
attack_procedure=f"1. Set up environment for {technique.mitre_id}.\n2. Execute the procedure.\n3. Record observations.",
|
||||
attack_procedure=(
|
||||
f"1. Set up environment for {technique.mitre_id}.\n"
|
||||
"2. Execute the procedure.\n"
|
||||
"3. Record observations."
|
||||
),
|
||||
expected_detection=f"SIEM should alert on {technique.mitre_id} indicators.",
|
||||
platform=random.choice(PLATFORMS),
|
||||
tool_suggested=random.choice(["powershell", "cmd", "bash", "python"]),
|
||||
|
||||
@@ -7,9 +7,9 @@ from datetime import datetime, timedelta
|
||||
from sqlalchemy import case, func
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.models.enums import TestResult
|
||||
from app.models.technique import Technique
|
||||
from app.models.test import Test
|
||||
from app.models.enums import TestResult
|
||||
|
||||
|
||||
def get_coverage_by_tactic(db: Session) -> list[dict]:
|
||||
|
||||
@@ -24,7 +24,6 @@ templates are identified by their ``atomic_test_id`` and simply skipped.
|
||||
|
||||
import io
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
import zipfile
|
||||
@@ -54,6 +53,10 @@ _DOWNLOAD_TIMEOUT = 300
|
||||
# Top-level directory name inside the ZIP
|
||||
_ZIP_ROOT_PREFIX = "atomic-red-team-master"
|
||||
|
||||
# Safety limits for ZIP extraction — prevent zip-bomb DoS
|
||||
_MAX_UNCOMPRESSED_SIZE = 500 * 1024 * 1024 # 500 MB
|
||||
_MAX_ENTRIES = 50_000
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Internal helpers
|
||||
@@ -77,11 +80,6 @@ def _safe_extract_zip(zip_bytes: bytes, dest: str) -> None:
|
||||
directory (path traversal / Zip Slip) or if the archive exceeds the
|
||||
safety limits.
|
||||
"""
|
||||
# Maximum uncompressed size: 500 MB — prevents zip-bomb DoS
|
||||
_MAX_UNCOMPRESSED_SIZE = 500 * 1024 * 1024
|
||||
# Maximum number of entries
|
||||
_MAX_ENTRIES = 50_000
|
||||
|
||||
dest_path = Path(dest).resolve()
|
||||
|
||||
with zipfile.ZipFile(io.BytesIO(zip_bytes)) as zf:
|
||||
|
||||
@@ -33,8 +33,8 @@ import requests as _requests
|
||||
import yaml
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.models.test_template import TestTemplate
|
||||
from app.models.data_source import DataSource
|
||||
from app.models.test_template import TestTemplate
|
||||
from app.services.audit_service import log_action
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -16,16 +16,15 @@ from app.domain.errors import (
|
||||
PermissionViolation,
|
||||
)
|
||||
from app.models.campaign import Campaign, CampaignTest
|
||||
from app.models.test import Test
|
||||
from app.models.technique import Technique
|
||||
from app.utils import escape_like
|
||||
from app.models.test import Test
|
||||
from app.services.campaign_scheduler_service import calculate_next_run
|
||||
from app.services.campaign_service import (
|
||||
TACTIC_TO_PHASE,
|
||||
get_campaign_progress,
|
||||
validate_no_circular_dependency,
|
||||
TACTIC_TO_PHASE,
|
||||
)
|
||||
from app.services.campaign_scheduler_service import calculate_next_run
|
||||
|
||||
from app.utils import escape_like
|
||||
|
||||
# ── Serialization helpers ────────────────────────────────────────────────
|
||||
|
||||
|
||||
@@ -5,17 +5,16 @@ fresh tests, and computing the next run date.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import uuid
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.models.campaign import Campaign, CampaignTest
|
||||
from app.models.test import Test
|
||||
from app.models.enums import TestState
|
||||
from app.services.notification_service import create_notification
|
||||
from app.services.audit_service import log_action
|
||||
from app.models.test import Test
|
||||
from app.models.user import User
|
||||
from app.services.audit_service import log_action
|
||||
from app.services.notification_service import create_notification
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -166,7 +165,10 @@ def check_and_run_recurring_campaigns(db: Session) -> int:
|
||||
user_id=campaign.created_by,
|
||||
type="recurring_campaign_run",
|
||||
title="Recurring campaign executed",
|
||||
message=f'Campaign "{child.name}" was automatically created from recurring template "{campaign.name}".',
|
||||
message=(
|
||||
f'Campaign "{child.name}" was automatically created '
|
||||
f'from recurring template "{campaign.name}".'
|
||||
),
|
||||
entity_type="campaign",
|
||||
entity_id=child.id,
|
||||
)
|
||||
|
||||
@@ -6,18 +6,16 @@ threat actors, and progress calculation.
|
||||
|
||||
import logging
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.domain.exceptions import EntityNotFoundError, InvalidOperationError
|
||||
from app.models.campaign import Campaign, CampaignTest, KILL_CHAIN_PHASES
|
||||
from app.models.campaign import Campaign, CampaignTest
|
||||
from app.models.enums import TechniqueStatus, TestState
|
||||
from app.models.technique import Technique
|
||||
from app.models.test import Test
|
||||
from app.models.test_template import TestTemplate
|
||||
from app.models.technique import Technique
|
||||
from app.models.threat_actor import ThreatActor, ThreatActorTechnique
|
||||
from app.models.enums import TechniqueStatus, TestState
|
||||
from app.services.notification_service import create_notification
|
||||
from app.models.user import User
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -6,22 +6,256 @@ ComplianceControl, and ComplianceControlMapping records.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import json
|
||||
import re
|
||||
from typing import Optional
|
||||
|
||||
import requests
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.models.compliance import (
|
||||
ComplianceFramework,
|
||||
ComplianceControl,
|
||||
ComplianceControlMapping,
|
||||
ComplianceFramework,
|
||||
)
|
||||
from app.models.technique import Technique
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# ── Module-level control definitions (avoids N806 / uppercase-in-function) ────
|
||||
|
||||
_NIST_SAMPLE_CONTROLS = [
|
||||
{
|
||||
"control_id": "AC-2",
|
||||
"title": "Account Management",
|
||||
"category": "Access Control",
|
||||
"techniques": ["T1078", "T1136", "T1098", "T1087", "T1069"],
|
||||
},
|
||||
{
|
||||
"control_id": "AC-3",
|
||||
"title": "Access Enforcement",
|
||||
"category": "Access Control",
|
||||
"techniques": ["T1078", "T1548", "T1134"],
|
||||
},
|
||||
{
|
||||
"control_id": "AC-4",
|
||||
"title": "Information Flow Enforcement",
|
||||
"category": "Access Control",
|
||||
"techniques": ["T1048", "T1041", "T1572"],
|
||||
},
|
||||
{
|
||||
"control_id": "AC-6",
|
||||
"title": "Least Privilege",
|
||||
"category": "Access Control",
|
||||
"techniques": ["T1078", "T1548", "T1134"],
|
||||
},
|
||||
{
|
||||
"control_id": "AU-2",
|
||||
"title": "Event Logging",
|
||||
"category": "Audit and Accountability",
|
||||
"techniques": ["T1562", "T1070"],
|
||||
},
|
||||
{
|
||||
"control_id": "AU-6",
|
||||
"title": "Audit Record Review",
|
||||
"category": "Audit and Accountability",
|
||||
"techniques": ["T1562", "T1070", "T1027"],
|
||||
},
|
||||
{
|
||||
"control_id": "CA-7",
|
||||
"title": "Continuous Monitoring",
|
||||
"category": "Assessment, Authorization, and Monitoring",
|
||||
"techniques": ["T1059", "T1053"],
|
||||
},
|
||||
{
|
||||
"control_id": "CM-2",
|
||||
"title": "Baseline Configuration",
|
||||
"category": "Configuration Management",
|
||||
"techniques": ["T1574", "T1546"],
|
||||
},
|
||||
{
|
||||
"control_id": "CM-6",
|
||||
"title": "Configuration Settings",
|
||||
"category": "Configuration Management",
|
||||
"techniques": ["T1574", "T1546", "T1112"],
|
||||
},
|
||||
{
|
||||
"control_id": "CM-7",
|
||||
"title": "Least Functionality",
|
||||
"category": "Configuration Management",
|
||||
"techniques": ["T1059", "T1218"],
|
||||
},
|
||||
{
|
||||
"control_id": "IA-2",
|
||||
"title": "Identification and Authentication",
|
||||
"category": "Identification and Authentication",
|
||||
"techniques": ["T1078", "T1110"],
|
||||
},
|
||||
{
|
||||
"control_id": "IA-5",
|
||||
"title": "Authenticator Management",
|
||||
"category": "Identification and Authentication",
|
||||
"techniques": ["T1078", "T1110", "T1003"],
|
||||
},
|
||||
{
|
||||
"control_id": "IR-4",
|
||||
"title": "Incident Handling",
|
||||
"category": "Incident Response",
|
||||
"techniques": ["T1059", "T1547"],
|
||||
},
|
||||
{
|
||||
"control_id": "RA-5",
|
||||
"title": "Vulnerability Monitoring and Scanning",
|
||||
"category": "Risk Assessment",
|
||||
"techniques": ["T1190", "T1203"],
|
||||
},
|
||||
{
|
||||
"control_id": "SC-7",
|
||||
"title": "Boundary Protection",
|
||||
"category": "System and Communications Protection",
|
||||
"techniques": ["T1048", "T1041", "T1071"],
|
||||
},
|
||||
{
|
||||
"control_id": "SC-28",
|
||||
"title": "Protection of Information at Rest",
|
||||
"category": "System and Communications Protection",
|
||||
"techniques": ["T1005", "T1114"],
|
||||
},
|
||||
{
|
||||
"control_id": "SI-3",
|
||||
"title": "Malicious Code Protection",
|
||||
"category": "System and Information Integrity",
|
||||
"techniques": ["T1059", "T1204", "T1566"],
|
||||
},
|
||||
{
|
||||
"control_id": "SI-4",
|
||||
"title": "System Monitoring",
|
||||
"category": "System and Information Integrity",
|
||||
"techniques": ["T1059", "T1053", "T1547"],
|
||||
},
|
||||
{
|
||||
"control_id": "SI-7",
|
||||
"title": "Software, Firmware, and Information Integrity",
|
||||
"category": "System and Information Integrity",
|
||||
"techniques": ["T1195", "T1553"],
|
||||
},
|
||||
{
|
||||
"control_id": "PM-16",
|
||||
"title": "Threat Awareness Program",
|
||||
"category": "Program Management",
|
||||
"techniques": ["T1566", "T1204"],
|
||||
},
|
||||
]
|
||||
|
||||
_CIS_CONTROLS = [
|
||||
{
|
||||
"control_id": "CIS-1",
|
||||
"title": "Inventory and Control of Enterprise Assets",
|
||||
"category": "IG1 — Basic",
|
||||
"techniques": ["T1595", "T1590", "T1018", "T1082"],
|
||||
},
|
||||
{
|
||||
"control_id": "CIS-2",
|
||||
"title": "Inventory and Control of Software Assets",
|
||||
"category": "IG1 — Basic",
|
||||
"techniques": ["T1518", "T1072", "T1195"],
|
||||
},
|
||||
{
|
||||
"control_id": "CIS-3",
|
||||
"title": "Data Protection",
|
||||
"category": "IG1 — Basic",
|
||||
"techniques": ["T1005", "T1114", "T1560", "T1048", "T1041"],
|
||||
},
|
||||
{
|
||||
"control_id": "CIS-4",
|
||||
"title": "Secure Configuration of Enterprise Assets and Software",
|
||||
"category": "IG1 — Basic",
|
||||
"techniques": ["T1574", "T1546", "T1112", "T1543"],
|
||||
},
|
||||
{
|
||||
"control_id": "CIS-5",
|
||||
"title": "Account Management",
|
||||
"category": "IG1 — Basic",
|
||||
"techniques": ["T1078", "T1136", "T1098", "T1087"],
|
||||
},
|
||||
{
|
||||
"control_id": "CIS-6",
|
||||
"title": "Access Control Management",
|
||||
"category": "IG1 — Basic",
|
||||
"techniques": ["T1078", "T1548", "T1134", "T1021"],
|
||||
},
|
||||
{
|
||||
"control_id": "CIS-7",
|
||||
"title": "Continuous Vulnerability Management",
|
||||
"category": "IG2 — Foundational",
|
||||
"techniques": ["T1190", "T1203", "T1068", "T1210"],
|
||||
},
|
||||
{
|
||||
"control_id": "CIS-8",
|
||||
"title": "Audit Log Management",
|
||||
"category": "IG2 — Foundational",
|
||||
"techniques": ["T1562", "T1070", "T1059"],
|
||||
},
|
||||
{
|
||||
"control_id": "CIS-9",
|
||||
"title": "Email and Web Browser Protections",
|
||||
"category": "IG2 — Foundational",
|
||||
"techniques": ["T1566", "T1204", "T1189", "T1598"],
|
||||
},
|
||||
{
|
||||
"control_id": "CIS-10",
|
||||
"title": "Malware Defenses",
|
||||
"category": "IG2 — Foundational",
|
||||
"techniques": ["T1059", "T1204", "T1027", "T1140", "T1497"],
|
||||
},
|
||||
{
|
||||
"control_id": "CIS-11",
|
||||
"title": "Data Recovery",
|
||||
"category": "IG1 — Basic",
|
||||
"techniques": ["T1486", "T1490", "T1561"],
|
||||
},
|
||||
{
|
||||
"control_id": "CIS-12",
|
||||
"title": "Network Infrastructure Management",
|
||||
"category": "IG2 — Foundational",
|
||||
"techniques": ["T1557", "T1071", "T1572", "T1571"],
|
||||
},
|
||||
{
|
||||
"control_id": "CIS-13",
|
||||
"title": "Network Monitoring and Defense",
|
||||
"category": "IG2 — Foundational",
|
||||
"techniques": ["T1071", "T1048", "T1041", "T1105", "T1572"],
|
||||
},
|
||||
{
|
||||
"control_id": "CIS-14",
|
||||
"title": "Security Awareness and Skills Training",
|
||||
"category": "IG1 — Basic",
|
||||
"techniques": ["T1566", "T1204", "T1598"],
|
||||
},
|
||||
{
|
||||
"control_id": "CIS-15",
|
||||
"title": "Service Provider Management",
|
||||
"category": "IG2 — Foundational",
|
||||
"techniques": ["T1199", "T1195"],
|
||||
},
|
||||
{
|
||||
"control_id": "CIS-16",
|
||||
"title": "Application Software Security",
|
||||
"category": "IG2 — Foundational",
|
||||
"techniques": ["T1190", "T1059", "T1203"],
|
||||
},
|
||||
{
|
||||
"control_id": "CIS-17",
|
||||
"title": "Incident Response Management",
|
||||
"category": "IG2 — Foundational",
|
||||
"techniques": ["T1059", "T1547", "T1053"],
|
||||
},
|
||||
{
|
||||
"control_id": "CIS-18",
|
||||
"title": "Penetration Testing",
|
||||
"category": "IG3 — Organizational",
|
||||
"techniques": ["T1595", "T1046", "T1190", "T1059"],
|
||||
},
|
||||
]
|
||||
|
||||
# URL for the NIST 800-53 Rev 5 to ATT&CK mapping
|
||||
# This is the JSON STIX bundle that contains the relationships
|
||||
NIST_MAPPING_URL = (
|
||||
@@ -53,7 +287,11 @@ def import_nist_800_53_mappings(db: Session) -> dict:
|
||||
framework = ComplianceFramework(
|
||||
name="NIST 800-53 Rev 5",
|
||||
version="5",
|
||||
description="National Institute of Standards and Technology Special Publication 800-53 Revision 5 — Security and Privacy Controls for Information Systems and Organizations",
|
||||
description=(
|
||||
"National Institute of Standards and Technology "
|
||||
"Special Publication 800-53 Revision 5 — "
|
||||
"Security and Privacy Controls for Information Systems and Organizations"
|
||||
),
|
||||
url="https://csrc.nist.gov/publications/detail/sp/800-53/rev-5/final",
|
||||
is_active=True,
|
||||
)
|
||||
@@ -216,49 +454,6 @@ def _import_sample_nist_mappings(db: Session, framework: ComplianceFramework) ->
|
||||
|
||||
This ensures the feature works even without network access.
|
||||
"""
|
||||
SAMPLE_CONTROLS = [
|
||||
{"control_id": "AC-2", "title": "Account Management", "category": "Access Control",
|
||||
"techniques": ["T1078", "T1136", "T1098", "T1087", "T1069"]},
|
||||
{"control_id": "AC-3", "title": "Access Enforcement", "category": "Access Control",
|
||||
"techniques": ["T1078", "T1548", "T1134"]},
|
||||
{"control_id": "AC-4", "title": "Information Flow Enforcement", "category": "Access Control",
|
||||
"techniques": ["T1048", "T1041", "T1572"]},
|
||||
{"control_id": "AC-6", "title": "Least Privilege", "category": "Access Control",
|
||||
"techniques": ["T1078", "T1548", "T1134"]},
|
||||
{"control_id": "AU-2", "title": "Event Logging", "category": "Audit and Accountability",
|
||||
"techniques": ["T1562", "T1070"]},
|
||||
{"control_id": "AU-6", "title": "Audit Record Review", "category": "Audit and Accountability",
|
||||
"techniques": ["T1562", "T1070", "T1027"]},
|
||||
{"control_id": "CA-7", "title": "Continuous Monitoring", "category": "Assessment, Authorization, and Monitoring",
|
||||
"techniques": ["T1059", "T1053"]},
|
||||
{"control_id": "CM-2", "title": "Baseline Configuration", "category": "Configuration Management",
|
||||
"techniques": ["T1574", "T1546"]},
|
||||
{"control_id": "CM-6", "title": "Configuration Settings", "category": "Configuration Management",
|
||||
"techniques": ["T1574", "T1546", "T1112"]},
|
||||
{"control_id": "CM-7", "title": "Least Functionality", "category": "Configuration Management",
|
||||
"techniques": ["T1059", "T1218"]},
|
||||
{"control_id": "IA-2", "title": "Identification and Authentication", "category": "Identification and Authentication",
|
||||
"techniques": ["T1078", "T1110"]},
|
||||
{"control_id": "IA-5", "title": "Authenticator Management", "category": "Identification and Authentication",
|
||||
"techniques": ["T1078", "T1110", "T1003"]},
|
||||
{"control_id": "IR-4", "title": "Incident Handling", "category": "Incident Response",
|
||||
"techniques": ["T1059", "T1547"]},
|
||||
{"control_id": "RA-5", "title": "Vulnerability Monitoring and Scanning", "category": "Risk Assessment",
|
||||
"techniques": ["T1190", "T1203"]},
|
||||
{"control_id": "SC-7", "title": "Boundary Protection", "category": "System and Communications Protection",
|
||||
"techniques": ["T1048", "T1041", "T1071"]},
|
||||
{"control_id": "SC-28", "title": "Protection of Information at Rest", "category": "System and Communications Protection",
|
||||
"techniques": ["T1005", "T1114"]},
|
||||
{"control_id": "SI-3", "title": "Malicious Code Protection", "category": "System and Information Integrity",
|
||||
"techniques": ["T1059", "T1204", "T1566"]},
|
||||
{"control_id": "SI-4", "title": "System Monitoring", "category": "System and Information Integrity",
|
||||
"techniques": ["T1059", "T1053", "T1547"]},
|
||||
{"control_id": "SI-7", "title": "Software, Firmware, and Information Integrity", "category": "System and Information Integrity",
|
||||
"techniques": ["T1195", "T1553"]},
|
||||
{"control_id": "PM-16", "title": "Threat Awareness Program", "category": "Program Management",
|
||||
"techniques": ["T1566", "T1204"]},
|
||||
]
|
||||
|
||||
# Build technique lookup
|
||||
all_techniques = {t.mitre_id: t for t in db.query(Technique).all()}
|
||||
|
||||
@@ -276,7 +471,7 @@ def _import_sample_nist_mappings(db: Session, framework: ComplianceFramework) ->
|
||||
controls_created = 0
|
||||
mappings_created = 0
|
||||
|
||||
for sample in SAMPLE_CONTROLS:
|
||||
for sample in _NIST_SAMPLE_CONTROLS:
|
||||
# Create or get control
|
||||
if sample["control_id"] in existing_controls:
|
||||
control = existing_controls[sample["control_id"]]
|
||||
@@ -348,8 +543,11 @@ def import_cis_controls_v8_mappings(db: Session) -> dict:
|
||||
framework = ComplianceFramework(
|
||||
name="CIS Controls v8",
|
||||
version="8",
|
||||
description="Center for Internet Security Critical Security Controls Version 8 — "
|
||||
"a prioritized set of 18 security safeguards organized by Implementation Groups (IG1, IG2, IG3).",
|
||||
description=(
|
||||
"Center for Internet Security Critical Security Controls Version 8 — "
|
||||
"a prioritized set of 18 security safeguards "
|
||||
"organized by Implementation Groups (IG1, IG2, IG3)."
|
||||
),
|
||||
url="https://www.cisecurity.org/controls/v8",
|
||||
is_active=True,
|
||||
)
|
||||
@@ -360,62 +558,7 @@ def import_cis_controls_v8_mappings(db: Session) -> dict:
|
||||
logger.info("CIS Controls v8 framework already exists")
|
||||
|
||||
# ── 2. Control definitions with ATT&CK mappings ───────────────
|
||||
CIS_CONTROLS = [
|
||||
{"control_id": "CIS-1", "title": "Inventory and Control of Enterprise Assets",
|
||||
"category": "IG1 — Basic",
|
||||
"techniques": ["T1595", "T1590", "T1018", "T1082"]},
|
||||
{"control_id": "CIS-2", "title": "Inventory and Control of Software Assets",
|
||||
"category": "IG1 — Basic",
|
||||
"techniques": ["T1518", "T1072", "T1195"]},
|
||||
{"control_id": "CIS-3", "title": "Data Protection",
|
||||
"category": "IG1 — Basic",
|
||||
"techniques": ["T1005", "T1114", "T1560", "T1048", "T1041"]},
|
||||
{"control_id": "CIS-4", "title": "Secure Configuration of Enterprise Assets and Software",
|
||||
"category": "IG1 — Basic",
|
||||
"techniques": ["T1574", "T1546", "T1112", "T1543"]},
|
||||
{"control_id": "CIS-5", "title": "Account Management",
|
||||
"category": "IG1 — Basic",
|
||||
"techniques": ["T1078", "T1136", "T1098", "T1087"]},
|
||||
{"control_id": "CIS-6", "title": "Access Control Management",
|
||||
"category": "IG1 — Basic",
|
||||
"techniques": ["T1078", "T1548", "T1134", "T1021"]},
|
||||
{"control_id": "CIS-7", "title": "Continuous Vulnerability Management",
|
||||
"category": "IG2 — Foundational",
|
||||
"techniques": ["T1190", "T1203", "T1068", "T1210"]},
|
||||
{"control_id": "CIS-8", "title": "Audit Log Management",
|
||||
"category": "IG2 — Foundational",
|
||||
"techniques": ["T1562", "T1070", "T1059"]},
|
||||
{"control_id": "CIS-9", "title": "Email and Web Browser Protections",
|
||||
"category": "IG2 — Foundational",
|
||||
"techniques": ["T1566", "T1204", "T1189", "T1598"]},
|
||||
{"control_id": "CIS-10", "title": "Malware Defenses",
|
||||
"category": "IG2 — Foundational",
|
||||
"techniques": ["T1059", "T1204", "T1027", "T1140", "T1497"]},
|
||||
{"control_id": "CIS-11", "title": "Data Recovery",
|
||||
"category": "IG1 — Basic",
|
||||
"techniques": ["T1486", "T1490", "T1561"]},
|
||||
{"control_id": "CIS-12", "title": "Network Infrastructure Management",
|
||||
"category": "IG2 — Foundational",
|
||||
"techniques": ["T1557", "T1071", "T1572", "T1571"]},
|
||||
{"control_id": "CIS-13", "title": "Network Monitoring and Defense",
|
||||
"category": "IG2 — Foundational",
|
||||
"techniques": ["T1071", "T1048", "T1041", "T1105", "T1572"]},
|
||||
{"control_id": "CIS-14", "title": "Security Awareness and Skills Training",
|
||||
"category": "IG1 — Basic",
|
||||
"techniques": ["T1566", "T1204", "T1598"]},
|
||||
{"control_id": "CIS-15", "title": "Service Provider Management",
|
||||
"category": "IG2 — Foundational",
|
||||
"techniques": ["T1199", "T1195"]},
|
||||
{"control_id": "CIS-16", "title": "Application Software Security",
|
||||
"category": "IG2 — Foundational",
|
||||
"techniques": ["T1190", "T1059", "T1203"]},
|
||||
{"control_id": "CIS-17", "title": "Incident Response Management",
|
||||
"category": "IG2 — Foundational",
|
||||
"techniques": ["T1059", "T1547", "T1053"]},
|
||||
{"control_id": "CIS-18", "title": "Penetration Testing",
|
||||
"category": "IG3 — Organizational",
|
||||
"techniques": ["T1595", "T1046", "T1190", "T1059"]},
|
||||
]
|
||||
# (defined at module level as _CIS_CONTROLS)
|
||||
|
||||
# Build technique lookup
|
||||
all_techniques = {t.mitre_id: t for t in db.query(Technique).all()}
|
||||
@@ -439,7 +582,7 @@ def import_cis_controls_v8_mappings(db: Session) -> dict:
|
||||
controls_created = 0
|
||||
mappings_created = 0
|
||||
|
||||
for item in CIS_CONTROLS:
|
||||
for item in _CIS_CONTROLS:
|
||||
if item["control_id"] in existing_controls:
|
||||
control = existing_controls[item["control_id"]]
|
||||
else:
|
||||
|
||||
@@ -16,16 +16,15 @@ from sqlalchemy.orm import Session
|
||||
|
||||
from app.domain.errors import EntityNotFoundError
|
||||
from app.models.compliance import (
|
||||
ComplianceFramework,
|
||||
ComplianceControl,
|
||||
ComplianceControlMapping,
|
||||
ComplianceFramework,
|
||||
)
|
||||
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
|
||||
|
||||
|
||||
# ── Helpers ───────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
|
||||
@@ -7,14 +7,13 @@ Uses the D3FEND public API:
|
||||
"""
|
||||
|
||||
import logging
|
||||
import uuid
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.models.technique import Technique
|
||||
from app.models.defensive_technique import DefensiveTechnique, DefensiveTechniqueMapping
|
||||
from app.models.technique import Technique
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -405,9 +404,10 @@ def sync(db: Session) -> dict:
|
||||
Called by the Data Sources router when the user clicks Sync for D3FEND.
|
||||
Returns a flat summary dict suitable for ``last_sync_stats``.
|
||||
"""
|
||||
from app.models.data_source import DataSource
|
||||
from datetime import datetime
|
||||
|
||||
from app.models.data_source import DataSource
|
||||
|
||||
tech_result = import_d3fend_techniques(db)
|
||||
mapping_result = import_d3fend_mappings(db)
|
||||
|
||||
|
||||
@@ -164,8 +164,8 @@ def get_source_stats(db: Session, source_id: str) -> dict:
|
||||
if not ds:
|
||||
raise EntityNotFoundError("Data source", source_id)
|
||||
|
||||
from app.models.test_template import TestTemplate
|
||||
from app.models.detection_rule import DetectionRule
|
||||
from app.models.test_template import TestTemplate
|
||||
|
||||
template_count = 0
|
||||
rule_count = 0
|
||||
|
||||
@@ -15,14 +15,13 @@ from sqlalchemy.orm import Session
|
||||
|
||||
from app.domain.errors import EntityNotFoundError
|
||||
from app.models.detection_rule import DetectionRule
|
||||
from app.models.technique import Technique
|
||||
from app.models.test import Test
|
||||
from app.models.test_detection_result import TestDetectionResult
|
||||
from app.models.test_template import TestTemplate
|
||||
from app.models.test_template_detection_rule import TestTemplateDetectionRule
|
||||
from app.models.test_detection_result import TestDetectionResult
|
||||
from app.models.technique import Technique
|
||||
from app.utils import escape_like
|
||||
|
||||
|
||||
# ── Public service functions ──────────────────────────────────────────
|
||||
|
||||
|
||||
|
||||
@@ -32,8 +32,8 @@ from pathlib import Path
|
||||
import requests as _requests
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.models.detection_rule import DetectionRule
|
||||
from app.models.data_source import DataSource
|
||||
from app.models.detection_rule import DetectionRule
|
||||
from app.services.audit_service import log_action
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -50,6 +50,10 @@ ELASTIC_ZIP_URL = (
|
||||
_DOWNLOAD_TIMEOUT = 300
|
||||
_ZIP_ROOT_PREFIX = "detection-rules-main"
|
||||
|
||||
# Safety limits for ZIP extraction — prevent zip-bomb DoS
|
||||
_MAX_UNCOMPRESSED_SIZE = 500 * 1024 * 1024 # 500 MB
|
||||
_MAX_ENTRIES = 50_000
|
||||
|
||||
# Severity normalisation
|
||||
_SEVERITY_MAP = {
|
||||
"informational": "informational",
|
||||
@@ -82,11 +86,6 @@ def _safe_extract_zip(zip_bytes: bytes, dest: str) -> None:
|
||||
directory (path traversal / Zip Slip) or if the archive exceeds the
|
||||
safety limits.
|
||||
"""
|
||||
# Maximum uncompressed size: 500 MB — prevents zip-bomb DoS
|
||||
_MAX_UNCOMPRESSED_SIZE = 500 * 1024 * 1024
|
||||
# Maximum number of entries
|
||||
_MAX_ENTRIES = 50_000
|
||||
|
||||
dest_path = Path(dest).resolve()
|
||||
|
||||
with zipfile.ZipFile(io.BytesIO(zip_bytes)) as zf:
|
||||
|
||||
@@ -10,7 +10,6 @@ no ``db.commit()``.
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from typing import Optional
|
||||
|
||||
from sqlalchemy import func, or_
|
||||
from sqlalchemy.orm import Session
|
||||
@@ -18,7 +17,6 @@ from sqlalchemy.orm import Session
|
||||
from app.domain.errors import BusinessRuleViolation, EntityNotFoundError
|
||||
from app.models.campaign import Campaign, CampaignTest
|
||||
from app.models.detection_rule import DetectionRule
|
||||
from app.models.defensive_technique import DefensiveTechniqueMapping
|
||||
from app.models.enums import TechniqueStatus, TestState
|
||||
from app.models.technique import Technique
|
||||
from app.models.test import Test
|
||||
|
||||
@@ -11,9 +11,9 @@ parser. No LLMs or paid APIs are used.
|
||||
|
||||
import logging
|
||||
import re
|
||||
import defusedxml.ElementTree as ET
|
||||
from datetime import datetime
|
||||
|
||||
import defusedxml.ElementTree as ET # noqa: N817 — ET is the universal stdlib alias for ElementTree
|
||||
import requests as _requests
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
|
||||
@@ -37,8 +37,8 @@ import requests as _requests
|
||||
import yaml
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.models.test_template import TestTemplate
|
||||
from app.models.data_source import DataSource
|
||||
from app.models.test_template import TestTemplate
|
||||
from app.services.audit_service import log_action
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -176,7 +176,11 @@ def _parse_lolbas(root_dir: Path) -> list[dict]:
|
||||
results.append({
|
||||
"mitre_technique_id": mitre_id,
|
||||
"name": f"LOLBAS: {binary_name} — {usecase or cmd_description or mitre_id}"[:500],
|
||||
"description": f"{description}\n\n{cmd_description}".strip()[:2000] if description else cmd_description[:2000] if cmd_description else None,
|
||||
"description": (
|
||||
f"{description}\n\n{cmd_description}".strip()[:2000]
|
||||
if description
|
||||
else cmd_description[:2000] if cmd_description else None
|
||||
),
|
||||
"source": "lolbas",
|
||||
"platform": "windows",
|
||||
"tool_suggested": binary_name,
|
||||
|
||||
@@ -13,8 +13,8 @@ import requests as _requests
|
||||
from sqlalchemy.orm import Session
|
||||
from taxii2client.v20 import Server as TaxiiServer
|
||||
|
||||
from app.models.technique import Technique
|
||||
from app.models.enums import TechniqueStatus
|
||||
from app.models.technique import Technique
|
||||
from app.services.audit_service import log_action
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -10,14 +10,13 @@ but do **not** commit. The caller is responsible for committing.
|
||||
import uuid
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import func
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.domain.errors import EntityNotFoundError
|
||||
from app.models.notification import Notification
|
||||
from app.models.user import User
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Core CRUD
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@@ -6,14 +6,14 @@ Calculates security operations KPIs from test data and audit logs.
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Optional
|
||||
|
||||
from sqlalchemy import func, case, and_, or_, extract
|
||||
from sqlalchemy import func
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.models.test import Test
|
||||
from app.models.technique import Technique
|
||||
from app.models.test_detection_result import TestDetectionResult
|
||||
from app.models.audit import AuditLog
|
||||
from app.models.enums import TestState, TestResult
|
||||
from app.models.enums import TestResult, TestState
|
||||
from app.models.technique import Technique
|
||||
from app.models.test import Test
|
||||
from app.models.test_detection_result import TestDetectionResult
|
||||
|
||||
|
||||
def _safe_stats(values: list[float]) -> dict:
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
Uses WeasyPrint for PDF generation and docxtpl for DOCX.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import uuid
|
||||
import logging
|
||||
from datetime import datetime
|
||||
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
@@ -34,7 +34,7 @@ class ReportEngine:
|
||||
|
||||
def generate_pdf(self, template_name: str, context: dict) -> str:
|
||||
"""Render HTML and convert to PDF with WeasyPrint."""
|
||||
from weasyprint import HTML, CSS
|
||||
from weasyprint import CSS, HTML
|
||||
|
||||
html_content = self.render_html(template_name, context)
|
||||
css_path = os.path.join(settings.REPORT_TEMPLATES_DIR, "styles", "report.css")
|
||||
|
||||
@@ -98,7 +98,7 @@ def generate_coverage_report(
|
||||
output_format: str = "pdf",
|
||||
) -> str:
|
||||
"""Generate an organization-wide MITRE ATT&CK coverage report."""
|
||||
from sqlalchemy import func, case
|
||||
from sqlalchemy import case, func
|
||||
|
||||
org_score = _safe_org_score(db)
|
||||
|
||||
@@ -234,7 +234,8 @@ def generate_quarterly_summary(
|
||||
output_format: str = "pdf",
|
||||
) -> str:
|
||||
"""Quarterly summary — reuses executive metrics plus snapshot trend rows."""
|
||||
from sqlalchemy import case as sql_case, func
|
||||
from sqlalchemy import case as sql_case
|
||||
from sqlalchemy import func
|
||||
|
||||
org_score = _safe_org_score(db)
|
||||
quarter_ago = datetime.utcnow() - timedelta(days=90)
|
||||
|
||||
@@ -58,13 +58,13 @@ def get_organization_score_cached(db):
|
||||
def get_operational_metrics_cached(db):
|
||||
"""Cached wrapper around operational metrics (MTTD, MTTR, efficacy)."""
|
||||
from app.services.operational_metrics_service import (
|
||||
calculate_mttd,
|
||||
calculate_mttr,
|
||||
calculate_detection_efficacy,
|
||||
calculate_alert_fidelity,
|
||||
calculate_coverage_velocity,
|
||||
calculate_validation_throughput,
|
||||
calculate_detection_efficacy,
|
||||
calculate_mttd,
|
||||
calculate_mttr,
|
||||
calculate_rejection_rate,
|
||||
calculate_validation_throughput,
|
||||
)
|
||||
|
||||
cached = get("op_metrics")
|
||||
|
||||
@@ -10,19 +10,18 @@ never produce N+1 traffic.
|
||||
"""
|
||||
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import Optional
|
||||
|
||||
from sqlalchemy import case, func
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.domain.errors import EntityNotFoundError
|
||||
from app.models.defensive_technique import DefensiveTechniqueMapping
|
||||
from app.models.detection_rule import DetectionRule
|
||||
from app.models.enums import TestResult, TestState
|
||||
from app.models.technique import Technique
|
||||
from app.models.test import Test
|
||||
from app.models.detection_rule import DetectionRule
|
||||
from app.models.test_detection_result import TestDetectionResult
|
||||
from app.models.defensive_technique import DefensiveTechniqueMapping
|
||||
from app.models.threat_actor import ThreatActor, ThreatActorTechnique
|
||||
from app.models.enums import TestState, TestResult
|
||||
from app.services.scoring_config_service import get_scoring_weights
|
||||
|
||||
_SEVERITY_FACTORS: dict[str, float] = {
|
||||
@@ -659,7 +658,6 @@ def get_score_history(db: Session, period: str = "90d") -> list:
|
||||
computing scores based on test dates within time windows.
|
||||
Returns a list of weekly data points.
|
||||
"""
|
||||
from app.models.audit import AuditLog
|
||||
|
||||
now = datetime.utcnow()
|
||||
if period == "30d":
|
||||
|
||||
@@ -35,8 +35,8 @@ import requests as _requests
|
||||
import yaml
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.models.detection_rule import DetectionRule
|
||||
from app.models.data_source import DataSource
|
||||
from app.models.detection_rule import DetectionRule
|
||||
from app.services.audit_service import log_action
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -52,6 +52,10 @@ SIGMA_ZIP_URL = (
|
||||
_DOWNLOAD_TIMEOUT = 300
|
||||
_ZIP_ROOT_PREFIX = "sigma-master"
|
||||
|
||||
# Safety limits for ZIP extraction — prevent zip-bomb DoS
|
||||
_MAX_UNCOMPRESSED_SIZE = 500 * 1024 * 1024 # 500 MB
|
||||
_MAX_ENTRIES = 50_000
|
||||
|
||||
# Regex to extract MITRE ATT&CK technique IDs from Sigma tags
|
||||
# e.g. "attack.t1059.001" → "T1059.001"
|
||||
_ATTACK_TAG_RE = re.compile(r"attack\.(t\d{4}(?:\.\d{3})?)", re.IGNORECASE)
|
||||
@@ -88,11 +92,6 @@ def _safe_extract_zip(zip_bytes: bytes, dest: str) -> None:
|
||||
directory (path traversal / Zip Slip) or if the archive exceeds the
|
||||
safety limits.
|
||||
"""
|
||||
# Maximum uncompressed size: 500 MB — prevents zip-bomb DoS
|
||||
_MAX_UNCOMPRESSED_SIZE = 500 * 1024 * 1024
|
||||
# Maximum number of entries
|
||||
_MAX_ENTRIES = 50_000
|
||||
|
||||
dest_path = Path(dest).resolve()
|
||||
|
||||
with zipfile.ZipFile(io.BytesIO(zip_bytes)) as zf:
|
||||
@@ -290,11 +289,8 @@ def sync(db: Session) -> dict:
|
||||
skipped = 0
|
||||
|
||||
for item in parsed_rules:
|
||||
# Dedup key: source_id (relative path). A rule file may produce
|
||||
# multiple entries (one per technique), but we deduplicate by
|
||||
# source_id so re-runs are safe. For multi-technique rules we
|
||||
# only skip if the exact same source_id is already present.
|
||||
dedup_key = f"{item['source_id']}::{item['mitre_technique_id']}"
|
||||
# Deduplicate by source_id: one rule file may map to multiple techniques,
|
||||
# but we skip insertion if this source_id was already imported.
|
||||
if item["source_id"] in existing_ids:
|
||||
skipped += 1
|
||||
continue
|
||||
|
||||
@@ -16,9 +16,9 @@ from sqlalchemy import func
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.domain.errors import EntityNotFoundError
|
||||
from app.models.technique import Technique
|
||||
from app.models.coverage_snapshot import CoverageSnapshot, SnapshotTechniqueState
|
||||
from app.models.enums import TechniqueStatus
|
||||
from app.models.technique import Technique
|
||||
from app.services.scoring_service import (
|
||||
bulk_technique_scores,
|
||||
calculate_organization_score,
|
||||
@@ -26,6 +26,15 @@ from app.services.scoring_service import (
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Coverage status ordering for snapshot delta comparisons (higher = better coverage)
|
||||
_STATUS_ORDER: dict[str, int] = {
|
||||
"not_evaluated": 0,
|
||||
"not_covered": 1,
|
||||
"in_progress": 2,
|
||||
"partial": 3,
|
||||
"validated": 4,
|
||||
}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Serialization and queries
|
||||
@@ -296,15 +305,6 @@ def compare_snapshots(
|
||||
.all()
|
||||
}
|
||||
|
||||
# Status priority for comparison
|
||||
STATUS_ORDER = {
|
||||
"not_evaluated": 0,
|
||||
"not_covered": 1,
|
||||
"in_progress": 2,
|
||||
"partial": 3,
|
||||
"validated": 4,
|
||||
}
|
||||
|
||||
improved = []
|
||||
worsened = []
|
||||
unchanged_count = 0
|
||||
@@ -315,8 +315,8 @@ def compare_snapshots(
|
||||
a = states_a.get(mitre_id, {"status": "not_evaluated", "score": 0})
|
||||
b = states_b.get(mitre_id, {"status": "not_evaluated", "score": 0})
|
||||
|
||||
a_order = STATUS_ORDER.get(a["status"], 0)
|
||||
b_order = STATUS_ORDER.get(b["status"], 0)
|
||||
a_order = _STATUS_ORDER.get(a["status"], 0)
|
||||
b_order = _STATUS_ORDER.get(b["status"], 0)
|
||||
|
||||
if b_order > a_order or (b_order == a_order and b["score"] > a["score"]):
|
||||
improved.append({
|
||||
|
||||
@@ -14,11 +14,11 @@ from app.domain.errors import (
|
||||
EntityNotFoundError,
|
||||
PermissionViolation,
|
||||
)
|
||||
from app.models.audit import AuditLog
|
||||
from app.models.enums import TestState
|
||||
from app.models.technique import Technique
|
||||
from app.models.test import Test
|
||||
from app.models.test_template import TestTemplate
|
||||
from app.models.audit import AuditLog
|
||||
from app.utils import escape_like
|
||||
|
||||
|
||||
|
||||
@@ -18,13 +18,16 @@ from datetime import datetime
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.config import settings
|
||||
from app.domain.exceptions import InvalidOperationError, InvalidTransitionError
|
||||
from app.domain.exceptions import InvalidOperationError
|
||||
from app.domain.test_entity import TestEntity
|
||||
from app.models.enums import TestState
|
||||
from app.models.test import Test
|
||||
from app.models.user import User
|
||||
from app.services.audit_service import log_action
|
||||
from app.services.notification_service import notify_test_state_change, create_notification
|
||||
from app.services.notification_service import (
|
||||
create_notification,
|
||||
notify_test_state_change,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -38,9 +38,9 @@ from pathlib import Path
|
||||
import requests as _requests
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.models.threat_actor import ThreatActor, ThreatActorTechnique
|
||||
from app.models.technique import Technique
|
||||
from app.models.data_source import DataSource
|
||||
from app.models.technique import Technique
|
||||
from app.models.threat_actor import ThreatActor, ThreatActorTechnique
|
||||
from app.services.audit_service import log_action
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -241,9 +241,6 @@ def sync(db: Session) -> dict:
|
||||
relationships = _parse_relationships(objects)
|
||||
attack_pattern_map = _build_attack_pattern_map(objects)
|
||||
|
||||
# Step 2: Build STIX-ID → actor dict map
|
||||
stix_to_actor = {a["stix_id"]: a for a in actor_dicts}
|
||||
|
||||
# Step 3: Load existing actors and techniques from DB
|
||||
existing_actors = {
|
||||
row.mitre_id: row
|
||||
|
||||
@@ -15,13 +15,12 @@ from sqlalchemy.orm import Session
|
||||
|
||||
from app.domain.errors import EntityNotFoundError
|
||||
from app.models.enums import TechniqueStatus
|
||||
from app.models.technique import Technique
|
||||
from app.models.test import Test
|
||||
from app.models.test_template import TestTemplate
|
||||
from app.models.threat_actor import ThreatActor, ThreatActorTechnique
|
||||
from app.models.technique import Technique
|
||||
from app.utils import escape_like
|
||||
|
||||
|
||||
# ── Public service functions ──────────────────────────────────────────
|
||||
|
||||
|
||||
|
||||
@@ -11,7 +11,11 @@ import uuid
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.auth import hash_password
|
||||
from app.domain.errors import BusinessRuleViolation, DuplicateEntityError, EntityNotFoundError
|
||||
from app.domain.errors import (
|
||||
BusinessRuleViolation,
|
||||
DuplicateEntityError,
|
||||
EntityNotFoundError,
|
||||
)
|
||||
from app.models.user import User
|
||||
|
||||
VALID_ROLES = {"admin", "red_tech", "blue_tech", "red_lead", "blue_lead", "viewer"}
|
||||
|
||||
+16
-8
@@ -1,13 +1,21 @@
|
||||
# PEP8 line length: 120 chars — the codebase uses longer identifiers and SQLAlchemy chaining
|
||||
line-length = 120
|
||||
|
||||
[lint]
|
||||
# Ignore rules that have widespread pre-existing violations.
|
||||
# These can be cleaned up incrementally in follow-up PRs.
|
||||
# PEP8 compliance rules enforced:
|
||||
# E/W — pycodestyle (core PEP8 style and warnings)
|
||||
# F — pyflakes (unused imports, undefined names)
|
||||
# I — isort (import ordering per PEP8 convention)
|
||||
# N — pep8-naming (class/function/variable naming conventions)
|
||||
select = ["E", "W", "F", "I", "N"]
|
||||
|
||||
ignore = [
|
||||
"E402", # module-level import not at top of file (app.main, some services)
|
||||
"E712", # == True comparisons (required by SQLAlchemy filter syntax)
|
||||
"F401", # unused imports (widespread; clean up incrementally)
|
||||
"F841", # unused local variables (a few occurrences)
|
||||
# SQLAlchemy filter syntax requires `== True` / `== False` comparisons
|
||||
"E712",
|
||||
]
|
||||
|
||||
[lint.per-file-ignores]
|
||||
# Test files may use broad exception catching and unusual import patterns
|
||||
"tests/**" = ["E", "F"]
|
||||
# Tests use broad exception catching and unusual import patterns
|
||||
"tests/**" = ["E", "F", "N"]
|
||||
# Data file: D3FEND technique descriptions contain URLs and long strings that cannot be meaningfully wrapped
|
||||
"app/services/d3fend_import_service.py" = ["E501"]
|
||||
|
||||
Reference in New Issue
Block a user