feat(campaigns): add approval fields and CampaignModificationRequest model
This commit is contained in:
@@ -8,7 +8,7 @@ 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.campaign import Campaign, CampaignTest, CampaignModificationRequest
|
||||
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
|
||||
@@ -59,7 +59,7 @@ __all__ = [
|
||||
# Literal argument value
|
||||
"TestTemplateDetectionRule", "TestDetectionResult",
|
||||
# Literal argument value
|
||||
"Campaign", "CampaignTest",
|
||||
"Campaign", "CampaignTest", "CampaignModificationRequest",
|
||||
# Literal argument value
|
||||
"ComplianceFramework", "ComplianceControl", "ComplianceControlMapping",
|
||||
# Literal argument value
|
||||
|
||||
@@ -75,6 +75,13 @@ class Campaign(Base):
|
||||
)
|
||||
start_date = Column(DateTime, nullable=True) # campaign won't activate before this date
|
||||
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)
|
||||
completed_at = Column(DateTime, 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_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"),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user