feat(manager): allow manager to create auto-approved campaigns and tests from templates
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

A manager organizes and validates work, so their own campaigns skip the
draft -> submit -> pending_approval queue and go straight to active with
the start_date they provide (they're the same role that would otherwise
approve it). Manager can also now create tests from the catalog, same as
red_lead/blue_lead.
This commit is contained in:
kitos
2026-07-16 12:57:20 +02:00
parent c753797019
commit 4809c4a662
8 changed files with 212 additions and 13 deletions
+25 -3
View File
@@ -171,6 +171,11 @@ class CampaignCreate(BaseModel):
tags: Optional[list[str]] = Field(default_factory=list)
# Assign scheduled_at = None
scheduled_at: Optional[str] = None
# Only honored when the creator is a manager — see create_campaign():
# a manager's own campaign is auto-approved on creation instead of
# going through the draft -> submit -> approve queue, so they need to
# supply the start_date up front instead of via a later /approve call.
start_date: Optional[str] = None
# Define class CampaignUpdate
@@ -314,18 +319,25 @@ def create_campaign(
# Entry: db
db: Session = Depends(get_db),
# Entry: current_user
current_user: User = Depends(require_any_role("red_lead", "blue_lead")),
current_user: User = Depends(require_any_role("red_lead", "blue_lead", "manager")),
) -> dict:
"""Create a new campaign.
A manager's campaign is auto-approved on creation — a manager is the
same role that would otherwise approve it, so routing it through the
draft -> submit -> pending_approval queue would just mean approving
their own submission. red_lead/blue_lead campaigns still go through
that queue as before.
Args:
payload (CampaignCreate): Fields for the new campaign (name, type, threat actor, etc.).
db (Session): SQLAlchemy database session.
current_user (User): Authenticated red_lead or blue_lead creating the campaign.
current_user (User): Authenticated red_lead, blue_lead, or manager creating the campaign.
Returns:
dict: Serialised representation of the newly created campaign.
"""
is_manager_create = current_user.role == "manager"
# Open context manager
with UnitOfWork(db) as uow:
# Assign result = crud_create(
@@ -347,6 +359,9 @@ def create_campaign(
tags=payload.tags,
# Keyword argument: scheduled_at
scheduled_at=payload.scheduled_at,
auto_approve=is_manager_create,
start_date=payload.start_date if is_manager_create else None,
approver_id=current_user.id if is_manager_create else None,
)
campaign_id = result["id"]
log_action(
@@ -354,7 +369,7 @@ def create_campaign(
# Keyword argument: user_id
user_id=current_user.id,
# Keyword argument: action
action="create_campaign",
action="create_campaign_auto_approved" if is_manager_create else "create_campaign",
# Keyword argument: entity_type
entity_type="campaign",
entity_id=campaign_id,
@@ -363,6 +378,13 @@ def create_campaign(
# Call uow.commit()
uow.commit()
if is_manager_create:
campaign = db.query(Campaign).filter(Campaign.id == uuid.UUID(campaign_id)).first()
db.refresh(campaign)
# Create Jira tickets now if the manager's chosen start_date is
# already due — mirrors the normal manager /approve path.
_create_jira_tickets_for_campaign(db, campaign, campaign_id, current_user)
# Return result
return result
+1 -1
View File
@@ -373,7 +373,7 @@ def create_test_from_template(
# Entry: db
db: Session = Depends(get_db),
# Entry: current_user
current_user: User = Depends(require_any_role_strict("red_lead", "blue_lead")),
current_user: User = Depends(require_any_role_strict("red_lead", "blue_lead", "manager")),
) -> TestOut:
"""Instantiate a real Test from an existing TestTemplate.