feat(test-catalog): add template creation and lead-approval workflow
Aegis CI / lint-and-test (push) Has been cancelled
Snyk Security Scan / Python vulnerabilities (backend) (push) Has been cancelled
Snyk Security Scan / npm vulnerabilities (frontend) (push) Has been cancelled
Snyk Security Scan / Docker image vulnerabilities (backend) (push) Has been cancelled
Aegis CI / lint-and-test (push) Has been cancelled
Snyk Security Scan / Python vulnerabilities (backend) (push) Has been cancelled
Snyk Security Scan / npm vulnerabilities (frontend) (push) Has been cancelled
Snyk Security Scan / Docker image vulnerabilities (backend) (push) Has been cancelled
Leads get a Create Template button that adds directly to the catalog (existing POST /test-templates). Operators (red_tech/blue_tech) get the same button, but their proposal now lands in a new template_suggestions review queue instead — a lead on their team can approve it as-is, edit fields before approving, or discard it. Modeled on the existing ProcedureSuggestion review workflow.
This commit is contained in:
@@ -46,6 +46,7 @@ from app.models.test import Test
|
||||
from app.models.test_round_history import TestRoundHistory
|
||||
from app.models.test_template import TestTemplate
|
||||
from app.models.procedure_suggestion import ProcedureSuggestion
|
||||
from app.models.template_suggestion import TemplateSuggestion
|
||||
from app.models.user import User
|
||||
|
||||
# Assign __all__ = [
|
||||
@@ -90,4 +91,5 @@ __all__ = [
|
||||
"AlertInstance",
|
||||
"TestRoundHistory",
|
||||
"ProcedureSuggestion",
|
||||
"TemplateSuggestion",
|
||||
]
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
"""SQLAlchemy model for operator-proposed test templates, pending lead review.
|
||||
|
||||
Leads create templates directly (see TestTemplateCreate router). An
|
||||
operator (red_tech/blue_tech) gets the same "create template" action, but
|
||||
their submission lands here instead of the live catalog — a lead on their
|
||||
team reviews it, optionally edits any field, and either approves it (which
|
||||
creates the real TestTemplate) or discards it.
|
||||
"""
|
||||
|
||||
import uuid
|
||||
|
||||
from sqlalchemy import Column, DateTime, ForeignKey, String, Text, func
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class TemplateSuggestion(Base):
|
||||
"""A proposed new TestTemplate submitted by an operator, pending lead review."""
|
||||
|
||||
__tablename__ = "template_suggestions"
|
||||
|
||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
|
||||
# Proposed TestTemplate fields — mirrors TestTemplateCreate.
|
||||
mitre_technique_id = Column(String, nullable=False)
|
||||
name = Column(String, nullable=False)
|
||||
description = Column(Text, nullable=True)
|
||||
source = Column(String, nullable=False, default="custom", server_default="custom")
|
||||
source_url = Column(String, nullable=True)
|
||||
attack_procedure = Column(Text, nullable=True)
|
||||
expected_detection = Column(Text, nullable=True)
|
||||
platform = Column(String, nullable=True)
|
||||
tool_suggested = Column(String, nullable=True)
|
||||
severity = Column(String, nullable=True)
|
||||
atomic_test_id = Column(String, nullable=True)
|
||||
suggested_remediation = Column(Text, nullable=True)
|
||||
|
||||
# "red" or "blue" — derived from the submitter's role, determines which
|
||||
# lead reviews it (admins can review both).
|
||||
team = Column(String(10), nullable=False)
|
||||
submitted_by = Column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
|
||||
|
||||
status = Column(String(10), nullable=False, default="pending", server_default="pending") # pending/approved/rejected
|
||||
reviewed_by = Column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=True)
|
||||
reviewed_at = Column(DateTime, nullable=True)
|
||||
created_template_id = Column(UUID(as_uuid=True), ForeignKey("test_templates.id"), nullable=True)
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
submitter = relationship("User", foreign_keys=[submitted_by])
|
||||
reviewer = relationship("User", foreign_keys=[reviewed_by])
|
||||
created_template = relationship("TestTemplate", foreign_keys=[created_template_id])
|
||||
Reference in New Issue
Block a user