feat(campaigns): add approval fields and CampaignModificationRequest model

This commit is contained in:
kitos
2026-07-02 16:10:22 +02:00
parent 8e7f98301e
commit a6b46f5f76
2 changed files with 59 additions and 2 deletions
+2 -2
View File
@@ -8,7 +8,7 @@ from app.models.threat_actor import ThreatActor, ThreatActorTechnique
from app.models.defensive_technique import DefensiveTechnique, DefensiveTechniqueMapping from app.models.defensive_technique import DefensiveTechnique, DefensiveTechniqueMapping
from app.models.test_template_detection_rule import TestTemplateDetectionRule from app.models.test_template_detection_rule import TestTemplateDetectionRule
from app.models.test_detection_result import TestDetectionResult from app.models.test_detection_result import TestDetectionResult
from app.models.campaign import Campaign, CampaignTest from app.models.campaign import Campaign, CampaignTest, CampaignModificationRequest
from app.models.compliance import ComplianceFramework, ComplianceControl, ComplianceControlMapping from app.models.compliance import ComplianceFramework, ComplianceControl, ComplianceControlMapping
from app.models.coverage_snapshot import CoverageSnapshot, SnapshotTechniqueState from app.models.coverage_snapshot import CoverageSnapshot, SnapshotTechniqueState
from app.models.jira_link import JiraLink, JiraLinkEntityType, JiraSyncDirection from app.models.jira_link import JiraLink, JiraLinkEntityType, JiraSyncDirection
@@ -59,7 +59,7 @@ __all__ = [
# Literal argument value # Literal argument value
"TestTemplateDetectionRule", "TestDetectionResult", "TestTemplateDetectionRule", "TestDetectionResult",
# Literal argument value # Literal argument value
"Campaign", "CampaignTest", "Campaign", "CampaignTest", "CampaignModificationRequest",
# Literal argument value # Literal argument value
"ComplianceFramework", "ComplianceControl", "ComplianceControlMapping", "ComplianceFramework", "ComplianceControl", "ComplianceControlMapping",
# Literal argument value # Literal argument value
+57
View File
@@ -75,6 +75,13 @@ class Campaign(Base):
) )
start_date = Column(DateTime, nullable=True) # campaign won't activate before this date start_date = Column(DateTime, nullable=True) # campaign won't activate before this date
scheduled_at = Column(DateTime, nullable=True) scheduled_at = Column(DateTime, nullable=True)
approved_by = Column(
UUID(as_uuid=True),
ForeignKey("users.id", ondelete="SET NULL"),
nullable=True,
)
approved_at = Column(DateTime, nullable=True)
rejection_reason = Column(Text, nullable=True)
# Assign completed_at = Column(DateTime, nullable=True) # Assign completed_at = Column(DateTime, nullable=True)
completed_at = Column(DateTime, nullable=True) completed_at = Column(DateTime, nullable=True)
# Assign target_platform = Column(String, nullable=True) # Assign target_platform = Column(String, nullable=True)
@@ -229,3 +236,53 @@ class CampaignTest(Base):
Index('ix_campaign_tests_campaign', 'campaign_id'), Index('ix_campaign_tests_campaign', 'campaign_id'),
Index('ix_campaign_tests_test', 'test_id'), Index('ix_campaign_tests_test', 'test_id'),
) )
class CampaignModificationRequest(Base):
"""A lead's request to add or remove a test from an already-active campaign.
The underlying ``CampaignTest`` row is only created/deleted once a
manager approves the request — see ``approve_modification_request``
in ``campaign_crud_service.py``.
"""
__tablename__ = "campaign_modification_requests"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
campaign_id = Column(
UUID(as_uuid=True),
ForeignKey("campaigns.id", ondelete="CASCADE"),
nullable=False,
)
requested_by = Column(
UUID(as_uuid=True),
ForeignKey("users.id", ondelete="SET NULL"),
nullable=True,
)
action = Column(String, nullable=False) # add_test, remove_test
test_id = Column(
UUID(as_uuid=True),
ForeignKey("tests.id", ondelete="CASCADE"),
nullable=False,
)
order_index = Column(Integer, nullable=True)
phase = Column(String, nullable=True)
justification = Column(Text, nullable=False)
status = Column(String, nullable=False, default="pending") # pending, approved, rejected
reviewed_by = Column(
UUID(as_uuid=True),
ForeignKey("users.id", ondelete="SET NULL"),
nullable=True,
)
reviewed_at = Column(DateTime, nullable=True)
review_notes = Column(Text, nullable=True)
created_at = Column(DateTime(timezone=True), server_default=func.now())
campaign = relationship("Campaign")
test = relationship("Test")
requester = relationship("User", foreign_keys=[requested_by])
reviewer = relationship("User", foreign_keys=[reviewed_by])
__table_args__ = (
Index("ix_campaign_mod_requests_campaign", "campaign_id"),
Index("ix_campaign_mod_requests_status", "status"),
)