feat(templates,campaigns): validate MITRE technique IDs, surface template suggestions first, manager can re-approve rejected campaigns
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
- TestTemplate/TemplateSuggestion creation and updates now reject any mitre_technique_id that doesn't match a real, already-synced MITRE ATT&CK technique. Frontend swaps the free-text ID input for a technique picker. - Test Catalog now shows pending template suggestions before the catalog grid, with full field detail (platform, severity, tool, atomic ID, source URL, remediation) instead of just name/procedure. - A manager can edit and directly re-approve a campaign they previously rejected, instead of needing the original lead to resubmit it.
This commit is contained in:
@@ -367,6 +367,11 @@ def approve_campaign(
|
||||
) -> Campaign:
|
||||
"""Approve a pending campaign: fix its start date and activate it.
|
||||
|
||||
Also allows approving a previously-rejected campaign directly (status
|
||||
``draft`` with a ``rejection_reason`` set) — a manager can edit it via
|
||||
``update_campaign()`` and then approve it themselves, without needing
|
||||
the original lead to resubmit it through the normal queue.
|
||||
|
||||
Raises EntityNotFoundError, BusinessRuleViolation.
|
||||
Does not commit; caller commits.
|
||||
"""
|
||||
@@ -374,8 +379,9 @@ def approve_campaign(
|
||||
if not campaign:
|
||||
raise EntityNotFoundError("Campaign", campaign_id)
|
||||
|
||||
if campaign.status != "pending_approval":
|
||||
raise BusinessRuleViolation("Only campaigns pending approval can be approved")
|
||||
is_previously_rejected_draft = campaign.status == "draft" and campaign.rejection_reason
|
||||
if campaign.status != "pending_approval" and not is_previously_rejected_draft:
|
||||
raise BusinessRuleViolation("Only campaigns pending approval (or previously rejected) can be approved")
|
||||
|
||||
if not start_date:
|
||||
raise BusinessRuleViolation("start_date is required to approve a campaign")
|
||||
@@ -454,7 +460,7 @@ def update_campaign(
|
||||
Does not commit; caller commits.
|
||||
"""
|
||||
# Assign campaign = db.query(Campaign).filter(Campaign.id == campaign_id).first()
|
||||
campaign = db.query(Campaign).filter(Campaign.id == campaign_id).first()
|
||||
campaign = db.query(Campaign).filter(Campaign.id == uuid.UUID(campaign_id)).first()
|
||||
# Check: not campaign
|
||||
if not campaign:
|
||||
# Raise EntityNotFoundError
|
||||
@@ -465,10 +471,16 @@ def update_campaign(
|
||||
# Raise BusinessRuleViolation
|
||||
raise BusinessRuleViolation("Can only update draft or active campaigns")
|
||||
|
||||
# Check: str(campaign.created_by) != str(updater_id) and updater_role != "ad...
|
||||
if str(campaign.created_by) != str(updater_id) and updater_role != "admin":
|
||||
# A manager may edit a campaign they (or another manager) previously
|
||||
# rejected — this is what lets them fix it up and self-approve it,
|
||||
# instead of bouncing it back to the original lead to resubmit.
|
||||
is_manager_editing_own_rejection = (
|
||||
updater_role == "manager" and campaign.status == "draft" and campaign.rejection_reason
|
||||
)
|
||||
is_owner_or_admin = str(campaign.created_by) == str(updater_id) or updater_role == "admin"
|
||||
if not is_owner_or_admin and not is_manager_editing_own_rejection:
|
||||
# Raise PermissionViolation
|
||||
raise PermissionViolation("Only the creator or admin can update this campaign")
|
||||
raise PermissionViolation("Only the creator, admin, or a manager reviewing a rejected campaign can update this campaign")
|
||||
|
||||
# Check: "scheduled_at" in fields and fields["scheduled_at"]
|
||||
if "scheduled_at" in fields and fields["scheduled_at"]:
|
||||
|
||||
Reference in New Issue
Block a user