chore(backend): mypy strict clean + ruff format pass
Pre-merge sanity per devops checklist (ruff format --check, mypy --strict). Type fixes: - ORM models: `Mapped[dict]` → `Mapped[dict[str, Any]]` (audit, scenario, run, report, ttp, detection.artifact_files_json). Equivalent on Pydantic DTOs (TtpBase.params_schema_json, ScenarioStepBase.params_override_json). - Rename `TtpRead.current_version` → `TtpRead.version` to mirror the ORM column (which itself was renamed in D-009 cleanup). - Flask blueprints: add `-> ResponseReturnValue` to every view, plus typed UUID params on `_validate_step_consistency`. - `templating/filters.py`: rewrite the conditional re2 import so mypy can narrow the union (`ModuleType | None`); the runtime branch on `_re2 is not None` removes the unused-ignore that was triggered by warn_unused_ignores. - `pyproject.toml`: add `flask_login.*` and `pythonjsonlogger.*` to the `[[tool.mypy.overrides]]` `ignore_missing_imports` list (both ship without typed marker). - Misc: drop stale `# type: ignore` comments (`app.py:36`, `rbac/decorators.py:35`) flagged by `warn_unused_ignores`. Keep `logging.JsonFormatter` ignore because the symbol exists at runtime but is not re-exported through the typed surface. Formatting: - `ruff format` applied (15 files normalized; line-length unchanged at 100). Verification on this commit: - `ruff check` → All checks passed. - `ruff format --check` → 68 files already formatted. - `mypy --strict src` → Success: no issues found in 54 source files. - `pytest tests/unit` → 49 passed.
This commit is contained in:
@@ -13,6 +13,7 @@ Revision ID: 202605210001
|
||||
Revises:
|
||||
Create Date: 2026-05-21
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sqlalchemy as sa
|
||||
@@ -30,9 +31,7 @@ depends_on: str | None = None
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
USER_TYPE = sa.Enum("rt_operator", "rt_lead", "soc_analyst", name="user_type")
|
||||
ENGAGEMENT_STATUS = sa.Enum(
|
||||
"draft", "active", "closed", "archived", name="engagement_status"
|
||||
)
|
||||
ENGAGEMENT_STATUS = sa.Enum("draft", "active", "closed", "archived", name="engagement_status")
|
||||
C2_TYPE = sa.Enum("mythic", "home", name="c2_type")
|
||||
HOST_STATUS = sa.Enum("unknown", "alive", "dead", name="host_status")
|
||||
PAYLOAD_TYPE = sa.Enum(
|
||||
@@ -64,18 +63,10 @@ RUN_STEP_STATUS = sa.Enum(
|
||||
"cleanup_failed",
|
||||
name="run_step_status",
|
||||
)
|
||||
CLEANUP_STATUS = sa.Enum(
|
||||
"pending", "success", "failed", "partial", name="cleanup_status"
|
||||
)
|
||||
DETECTION_LEVEL = sa.Enum(
|
||||
"detected", "partial", "not_detected", name="detection_level"
|
||||
)
|
||||
DETECTION_SOURCE = sa.Enum(
|
||||
"ndr", "edr", "siem", "manual", "other", name="detection_source"
|
||||
)
|
||||
EVIDENCE_STATUS = sa.Enum(
|
||||
"success", "failure", "partial", name="evidence_status"
|
||||
)
|
||||
CLEANUP_STATUS = sa.Enum("pending", "success", "failed", "partial", name="cleanup_status")
|
||||
DETECTION_LEVEL = sa.Enum("detected", "partial", "not_detected", name="detection_level")
|
||||
DETECTION_SOURCE = sa.Enum("ndr", "edr", "siem", "manual", "other", name="detection_source")
|
||||
EVIDENCE_STATUS = sa.Enum("success", "failure", "partial", name="evidence_status")
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
@@ -107,8 +98,12 @@ def upgrade() -> None:
|
||||
sa.Column("local_password_hash", sa.String(255)),
|
||||
sa.Column("disabled_at", sa.DateTime(timezone=True)),
|
||||
sa.Column("last_login_at", sa.DateTime(timezone=True)),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
|
||||
sa.Column(
|
||||
"created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
|
||||
),
|
||||
sa.Column(
|
||||
"updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
|
||||
),
|
||||
sa.UniqueConstraint("email", name="uq_user_email"),
|
||||
)
|
||||
|
||||
@@ -126,8 +121,12 @@ def upgrade() -> None:
|
||||
sa.Column("id", UUID(as_uuid=True), primary_key=True),
|
||||
sa.Column("name", sa.String(80), nullable=False),
|
||||
sa.Column("description", sa.String(255)),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
|
||||
sa.Column(
|
||||
"created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
|
||||
),
|
||||
sa.Column(
|
||||
"updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
|
||||
),
|
||||
sa.UniqueConstraint("name", name="uq_group_name"),
|
||||
)
|
||||
|
||||
@@ -136,13 +135,19 @@ def upgrade() -> None:
|
||||
sa.Column(
|
||||
"group_id",
|
||||
UUID(as_uuid=True),
|
||||
sa.ForeignKey("group.id", ondelete="CASCADE", name="fk_group_permission_group_id_group"),
|
||||
sa.ForeignKey(
|
||||
"group.id", ondelete="CASCADE", name="fk_group_permission_group_id_group"
|
||||
),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column(
|
||||
"permission_id",
|
||||
UUID(as_uuid=True),
|
||||
sa.ForeignKey("permission.id", ondelete="CASCADE", name="fk_group_permission_permission_id_permission"),
|
||||
sa.ForeignKey(
|
||||
"permission.id",
|
||||
ondelete="CASCADE",
|
||||
name="fk_group_permission_permission_id_permission",
|
||||
),
|
||||
nullable=False,
|
||||
),
|
||||
sa.PrimaryKeyConstraint("group_id", "permission_id", name="pk_group_permission"),
|
||||
@@ -158,9 +163,15 @@ def upgrade() -> None:
|
||||
sa.Column("start_date", sa.Date),
|
||||
sa.Column("end_date", sa.Date),
|
||||
sa.Column("c2_type", C2_TYPE, nullable=False, server_default="mythic"),
|
||||
sa.Column("created_by_id", UUID(as_uuid=True), sa.ForeignKey("user.id", ondelete="SET NULL")),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
|
||||
sa.Column(
|
||||
"created_by_id", UUID(as_uuid=True), sa.ForeignKey("user.id", ondelete="SET NULL")
|
||||
),
|
||||
sa.Column(
|
||||
"created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
|
||||
),
|
||||
sa.Column(
|
||||
"updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
|
||||
),
|
||||
)
|
||||
|
||||
op.create_table(
|
||||
@@ -217,8 +228,12 @@ def upgrade() -> None:
|
||||
sa.Column("config_fernet", sa.LargeBinary, nullable=False),
|
||||
sa.Column("version", sa.Integer, nullable=False, server_default="1"),
|
||||
sa.Column("retired_at", sa.DateTime(timezone=True)),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
|
||||
sa.Column(
|
||||
"created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
|
||||
),
|
||||
sa.Column(
|
||||
"updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
|
||||
),
|
||||
)
|
||||
op.create_index(
|
||||
"ix_c2_credential_engagement_active",
|
||||
@@ -244,8 +259,12 @@ def upgrade() -> None:
|
||||
sa.Column("c2_type", C2_TYPE, nullable=False),
|
||||
sa.Column("status", HOST_STATUS, nullable=False, server_default="unknown"),
|
||||
sa.Column("last_seen", sa.DateTime(timezone=True)),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
|
||||
sa.Column(
|
||||
"created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
|
||||
),
|
||||
sa.Column(
|
||||
"updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
|
||||
),
|
||||
)
|
||||
op.create_index("ix_host_engagement_id", "host", ["engagement_id"])
|
||||
|
||||
@@ -267,9 +286,15 @@ def upgrade() -> None:
|
||||
sa.Column("tags", JSONB, nullable=False, server_default="[]"),
|
||||
sa.Column("version", sa.Integer, nullable=False, server_default="1"),
|
||||
sa.Column("is_published", sa.Boolean, nullable=False, server_default=sa.false()),
|
||||
sa.Column("created_by_id", UUID(as_uuid=True), sa.ForeignKey("user.id", ondelete="SET NULL")),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
|
||||
sa.Column(
|
||||
"created_by_id", UUID(as_uuid=True), sa.ForeignKey("user.id", ondelete="SET NULL")
|
||||
),
|
||||
sa.Column(
|
||||
"created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
|
||||
),
|
||||
sa.Column(
|
||||
"updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
|
||||
),
|
||||
)
|
||||
# No `ttp_version` table — H32 / D-009: snapshot lives on run.snapshot_json.
|
||||
|
||||
@@ -287,9 +312,15 @@ def upgrade() -> None:
|
||||
sa.Column("description", sa.Text),
|
||||
sa.Column("version", sa.Integer, nullable=False, server_default="1"),
|
||||
sa.Column("c2_type", C2_TYPE, nullable=False),
|
||||
sa.Column("created_by_id", UUID(as_uuid=True), sa.ForeignKey("user.id", ondelete="SET NULL")),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
|
||||
sa.Column(
|
||||
"created_by_id", UUID(as_uuid=True), sa.ForeignKey("user.id", ondelete="SET NULL")
|
||||
),
|
||||
sa.Column(
|
||||
"created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
|
||||
),
|
||||
sa.Column(
|
||||
"updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
|
||||
),
|
||||
)
|
||||
|
||||
op.create_table(
|
||||
@@ -316,8 +347,12 @@ def upgrade() -> None:
|
||||
),
|
||||
sa.Column("params_override_json", JSONB, nullable=False, server_default="{}"),
|
||||
sa.Column("delay_after_ms", sa.Integer, nullable=False, server_default="0"),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
|
||||
sa.Column(
|
||||
"created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
|
||||
),
|
||||
sa.Column(
|
||||
"updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
|
||||
),
|
||||
sa.UniqueConstraint("scenario_id", "order_idx", name="uq_scenario_step_order_idx"),
|
||||
)
|
||||
|
||||
@@ -334,10 +369,16 @@ def upgrade() -> None:
|
||||
sa.Column("status", RUN_STATUS, nullable=False, server_default="queued"),
|
||||
sa.Column("started_at", sa.DateTime(timezone=True)),
|
||||
sa.Column("ended_at", sa.DateTime(timezone=True)),
|
||||
sa.Column("started_by_id", UUID(as_uuid=True), sa.ForeignKey("user.id", ondelete="SET NULL")),
|
||||
sa.Column(
|
||||
"started_by_id", UUID(as_uuid=True), sa.ForeignKey("user.id", ondelete="SET NULL")
|
||||
),
|
||||
sa.Column("snapshot_json", JSONB, nullable=False, server_default="{}"),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
|
||||
sa.Column(
|
||||
"created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
|
||||
),
|
||||
sa.Column(
|
||||
"updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
|
||||
),
|
||||
)
|
||||
|
||||
op.create_table(
|
||||
@@ -363,8 +404,12 @@ def upgrade() -> None:
|
||||
sa.Column("output_blob_ref", sa.String(512)),
|
||||
sa.Column("exit_code", sa.Integer),
|
||||
sa.Column("resolved_payload_text", sa.Text),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
|
||||
sa.Column(
|
||||
"created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
|
||||
),
|
||||
sa.Column(
|
||||
"updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
|
||||
),
|
||||
)
|
||||
|
||||
op.create_table(
|
||||
@@ -382,9 +427,15 @@ def upgrade() -> None:
|
||||
sa.Column("ended_at", sa.DateTime(timezone=True)),
|
||||
sa.Column("resolved_command_text", sa.Text),
|
||||
sa.Column("output", sa.Text),
|
||||
sa.Column("executed_by_id", UUID(as_uuid=True), sa.ForeignKey("user.id", ondelete="SET NULL")),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
|
||||
sa.Column(
|
||||
"executed_by_id", UUID(as_uuid=True), sa.ForeignKey("user.id", ondelete="SET NULL")
|
||||
),
|
||||
sa.Column(
|
||||
"created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
|
||||
),
|
||||
sa.Column(
|
||||
"updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
|
||||
),
|
||||
)
|
||||
|
||||
# ----------------------------------------------------- detection / evidence
|
||||
@@ -408,8 +459,12 @@ def upgrade() -> None:
|
||||
sa.Column("latency_ms", sa.Integer),
|
||||
sa.Column("comment", sa.Text),
|
||||
sa.Column("recorded_at", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
|
||||
sa.Column(
|
||||
"created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
|
||||
),
|
||||
sa.Column(
|
||||
"updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
|
||||
),
|
||||
)
|
||||
|
||||
op.create_table(
|
||||
@@ -432,8 +487,12 @@ def upgrade() -> None:
|
||||
sa.Column("artifact_files_json", JSONB, nullable=False, server_default="[]"),
|
||||
sa.Column("comment", sa.Text),
|
||||
sa.Column("recorded_at", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
|
||||
sa.Column(
|
||||
"created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
|
||||
),
|
||||
sa.Column(
|
||||
"updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
|
||||
),
|
||||
)
|
||||
|
||||
# ----------------------------------------------------------------- report
|
||||
@@ -452,9 +511,15 @@ def upgrade() -> None:
|
||||
sa.Column("pdf_path", sa.String(512)),
|
||||
sa.Column("md_path", sa.String(512)),
|
||||
sa.Column("generated_at", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.Column("generated_by_id", UUID(as_uuid=True), sa.ForeignKey("user.id", ondelete="SET NULL")),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
|
||||
sa.Column(
|
||||
"generated_by_id", UUID(as_uuid=True), sa.ForeignKey("user.id", ondelete="SET NULL")
|
||||
),
|
||||
sa.Column(
|
||||
"created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
|
||||
),
|
||||
sa.Column(
|
||||
"updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
|
||||
),
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------- soc_session
|
||||
@@ -479,8 +544,12 @@ def upgrade() -> None:
|
||||
sa.Column("last_ip", sa.String(64)),
|
||||
sa.Column("last_user_agent", sa.String(512)),
|
||||
sa.Column("last_used_at", sa.DateTime(timezone=True)),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False),
|
||||
sa.Column(
|
||||
"created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
|
||||
),
|
||||
sa.Column(
|
||||
"updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
|
||||
),
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------ audit_log
|
||||
|
||||
@@ -10,6 +10,7 @@ WORM enforcement without a migration; sprint 0 fills the columns at insert.
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
from uuid import UUID
|
||||
|
||||
from sqlalchemy import JSON, DateTime, ForeignKey, String, Text, func
|
||||
@@ -26,13 +27,11 @@ class AuditLog(UuidPkMixin, Base):
|
||||
nullable=False,
|
||||
server_default=func.now(),
|
||||
)
|
||||
actor_id: Mapped[UUID | None] = mapped_column(
|
||||
ForeignKey("user.id", ondelete="SET NULL")
|
||||
)
|
||||
actor_id: Mapped[UUID | None] = mapped_column(ForeignKey("user.id", ondelete="SET NULL"))
|
||||
action: Mapped[str] = mapped_column(String(80), nullable=False)
|
||||
resource_type: Mapped[str] = mapped_column(String(80), nullable=False)
|
||||
resource_id: Mapped[str | None] = mapped_column(String(128))
|
||||
metadata_json: Mapped[dict] = mapped_column(JSON, nullable=False, default=dict)
|
||||
metadata_json: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False, default=dict)
|
||||
|
||||
prev_hash: Mapped[str | None] = mapped_column(String(64))
|
||||
row_hash: Mapped[str] = mapped_column(String(64), nullable=False)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from uuid import UUID
|
||||
|
||||
from sqlalchemy import (
|
||||
@@ -45,9 +45,7 @@ class Detection(UuidPkMixin, TimestampsMixin, Base):
|
||||
)
|
||||
latency_ms: Mapped[int | None] = mapped_column(Integer)
|
||||
comment: Mapped[str | None] = mapped_column(Text)
|
||||
recorded_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), nullable=False
|
||||
)
|
||||
recorded_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
||||
|
||||
run_step: Mapped[RunStep] = relationship()
|
||||
soc_user: Mapped[User] = relationship()
|
||||
@@ -69,14 +67,12 @@ class Evidence(UuidPkMixin, TimestampsMixin, Base):
|
||||
nullable=False,
|
||||
)
|
||||
artifacts_text: Mapped[str | None] = mapped_column(Text)
|
||||
artifact_files_json: Mapped[list[dict]] = mapped_column(
|
||||
artifact_files_json: Mapped[list[dict[str, Any]]] = mapped_column(
|
||||
JSON, default=list, nullable=False
|
||||
)
|
||||
# Each entry: {"name": str, "ref": str, "sha256": str, "size_bytes": int}
|
||||
comment: Mapped[str | None] = mapped_column(Text)
|
||||
recorded_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), nullable=False
|
||||
)
|
||||
recorded_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
||||
|
||||
run_step: Mapped[RunStep] = relationship()
|
||||
rt_user: Mapped[User] = relationship()
|
||||
|
||||
@@ -37,9 +37,7 @@ class Engagement(UuidPkMixin, TimestampsMixin, Base):
|
||||
nullable=False,
|
||||
)
|
||||
|
||||
created_by_id: Mapped[UUID | None] = mapped_column(
|
||||
ForeignKey("user.id", ondelete="SET NULL")
|
||||
)
|
||||
created_by_id: Mapped[UUID | None] = mapped_column(ForeignKey("user.id", ondelete="SET NULL"))
|
||||
|
||||
hosts: Mapped[list[Host]] = relationship(
|
||||
back_populates="engagement",
|
||||
|
||||
@@ -64,14 +64,10 @@ class GroupPermission(Base):
|
||||
class UserGroup(Base):
|
||||
__tablename__ = "user_group"
|
||||
__table_args__ = (
|
||||
PrimaryKeyConstraint(
|
||||
"user_id", "group_id", "engagement_id", name="pk_user_group"
|
||||
),
|
||||
PrimaryKeyConstraint("user_id", "group_id", "engagement_id", name="pk_user_group"),
|
||||
)
|
||||
|
||||
user_id: Mapped[UUID] = mapped_column(
|
||||
ForeignKey("user.id", ondelete="CASCADE"), nullable=False
|
||||
)
|
||||
user_id: Mapped[UUID] = mapped_column(ForeignKey("user.id", ondelete="CASCADE"), nullable=False)
|
||||
group_id: Mapped[UUID] = mapped_column(
|
||||
ForeignKey("group.id", ondelete="CASCADE"), nullable=False
|
||||
)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from uuid import UUID
|
||||
|
||||
from sqlalchemy import (
|
||||
@@ -30,7 +30,7 @@ class Report(UuidPkMixin, TimestampsMixin, Base):
|
||||
)
|
||||
version: Mapped[int] = mapped_column(Integer, default=1, nullable=False)
|
||||
|
||||
content_json: Mapped[dict] = mapped_column(JSON, nullable=False, default=dict)
|
||||
content_json: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False, default=dict)
|
||||
content_sha256: Mapped[str] = mapped_column(String(64), nullable=False)
|
||||
# SHA-256 of canonical JSON. Identical hash referenced in PDF footer, JSON
|
||||
# export and Markdown export (spec H19 / F9 / F14).
|
||||
@@ -38,11 +38,7 @@ class Report(UuidPkMixin, TimestampsMixin, Base):
|
||||
pdf_path: Mapped[str | None] = mapped_column(String(512))
|
||||
md_path: Mapped[str | None] = mapped_column(String(512))
|
||||
|
||||
generated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), nullable=False
|
||||
)
|
||||
generated_by_id: Mapped[UUID | None] = mapped_column(
|
||||
ForeignKey("user.id", ondelete="SET NULL")
|
||||
)
|
||||
generated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
||||
generated_by_id: Mapped[UUID | None] = mapped_column(ForeignKey("user.id", ondelete="SET NULL"))
|
||||
|
||||
engagement: Mapped[Engagement] = relationship()
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from uuid import UUID
|
||||
|
||||
from sqlalchemy import (
|
||||
@@ -39,11 +39,9 @@ class Run(UuidPkMixin, TimestampsMixin, Base):
|
||||
started_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
||||
ended_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
||||
|
||||
started_by_id: Mapped[UUID | None] = mapped_column(
|
||||
ForeignKey("user.id", ondelete="SET NULL")
|
||||
)
|
||||
started_by_id: Mapped[UUID | None] = mapped_column(ForeignKey("user.id", ondelete="SET NULL"))
|
||||
|
||||
snapshot_json: Mapped[dict] = mapped_column(JSON, nullable=False, default=dict)
|
||||
snapshot_json: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False, default=dict)
|
||||
# Full self-contained snapshot of scenario + steps + resolved TTPs.
|
||||
# Source of truth for replay (spec H32).
|
||||
|
||||
@@ -111,8 +109,6 @@ class RunStepCleanup(UuidPkMixin, TimestampsMixin, Base):
|
||||
ended_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
||||
resolved_command_text: Mapped[str | None] = mapped_column(Text)
|
||||
output: Mapped[str | None] = mapped_column(Text)
|
||||
executed_by_id: Mapped[UUID | None] = mapped_column(
|
||||
ForeignKey("user.id", ondelete="SET NULL")
|
||||
)
|
||||
executed_by_id: Mapped[UUID | None] = mapped_column(ForeignKey("user.id", ondelete="SET NULL"))
|
||||
|
||||
run_step: Mapped[RunStep] = relationship(back_populates="cleanup")
|
||||
|
||||
@@ -6,7 +6,7 @@ run start that every referenced host matches.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from uuid import UUID
|
||||
|
||||
from sqlalchemy import (
|
||||
@@ -45,9 +45,7 @@ class Scenario(UuidPkMixin, TimestampsMixin, Base):
|
||||
Enum(C2Type, name="c2_type", create_type=False),
|
||||
nullable=False,
|
||||
)
|
||||
created_by_id: Mapped[UUID | None] = mapped_column(
|
||||
ForeignKey("user.id", ondelete="SET NULL")
|
||||
)
|
||||
created_by_id: Mapped[UUID | None] = mapped_column(ForeignKey("user.id", ondelete="SET NULL"))
|
||||
|
||||
engagement: Mapped[Engagement] = relationship(back_populates="scenarios")
|
||||
steps: Mapped[list[ScenarioStep]] = relationship(
|
||||
@@ -78,7 +76,7 @@ class ScenarioStep(UuidPkMixin, TimestampsMixin, Base):
|
||||
ForeignKey("host.id", ondelete="RESTRICT"),
|
||||
nullable=False,
|
||||
)
|
||||
params_override_json: Mapped[dict] = mapped_column(JSON, default=dict, nullable=False)
|
||||
params_override_json: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict, nullable=False)
|
||||
delay_after_ms: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
|
||||
|
||||
scenario: Mapped[Scenario] = relationship(back_populates="steps")
|
||||
|
||||
@@ -35,9 +35,7 @@ class SocSession(UuidPkMixin, TimestampsMixin, Base):
|
||||
token_hash: Mapped[str] = mapped_column(String(255), nullable=False, unique=True)
|
||||
# bcrypt hash. Plain token returned once at creation.
|
||||
|
||||
expires_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), nullable=False
|
||||
)
|
||||
expires_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
||||
revoked_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
||||
|
||||
last_ip: Mapped[str | None] = mapped_column(String(64))
|
||||
|
||||
@@ -8,6 +8,7 @@ counter (§8) — bumped on edit, never referenced for replay.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
from uuid import UUID
|
||||
|
||||
from sqlalchemy import (
|
||||
@@ -39,7 +40,7 @@ class Ttp(UuidPkMixin, TimestampsMixin, Base):
|
||||
nullable=False,
|
||||
)
|
||||
payload_template: Mapped[str] = mapped_column(Text, nullable=False, default="")
|
||||
params_schema_json: Mapped[dict | None] = mapped_column(JSON)
|
||||
params_schema_json: Mapped[dict[str, Any] | None] = mapped_column(JSON)
|
||||
|
||||
opsec_notes: Mapped[str | None] = mapped_column(Text)
|
||||
cleanup_command: Mapped[str | None] = mapped_column(Text)
|
||||
@@ -57,6 +58,4 @@ class Ttp(UuidPkMixin, TimestampsMixin, Base):
|
||||
is_published: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
|
||||
# Promoted to the library (lead RT only — F11).
|
||||
|
||||
created_by_id: Mapped[UUID | None] = mapped_column(
|
||||
ForeignKey("user.id", ondelete="SET NULL")
|
||||
)
|
||||
created_by_id: Mapped[UUID | None] = mapped_column(ForeignKey("user.id", ondelete="SET NULL"))
|
||||
|
||||
Reference in New Issue
Block a user