feat(assign): let leads manually assign operators + sync Jira; send platform as Jira label
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
Adds GET /users/operators (leads+admin) and wires a lead-only assign
control into the test detail header, calling the existing but
previously unreachable POST /tests/{id}/assign endpoint. Manual
assignment now also pushes the Jira ticket assignee immediately
instead of waiting for the operator to start execution.
Also adds test.platform as a kebab-case label on Jira ticket creation.
This commit is contained in:
@@ -31,6 +31,9 @@ from __future__ import annotations
|
||||
# Import logging
|
||||
import logging
|
||||
|
||||
# Import re
|
||||
import re
|
||||
|
||||
# Import datetime from datetime
|
||||
from datetime import datetime
|
||||
|
||||
@@ -697,12 +700,16 @@ def auto_create_test_issue(
|
||||
|
||||
poc = test.procedure_text or "N/A"
|
||||
severity = _technique_severity(technique).capitalize()
|
||||
labels = ["aegis", "security-test", mitre_id.replace(".", "-")]
|
||||
if test.platform:
|
||||
# Jira labels can't contain whitespace — normalize to kebab-case.
|
||||
labels.append(re.sub(r"[^a-z0-9_-]+", "-", test.platform.lower()).strip("-"))
|
||||
fields: dict = {
|
||||
"project": {"key": project_key},
|
||||
"summary": f"[Aegis] {mitre_id} — {test.name}",
|
||||
"description": _build_test_description(test, technique),
|
||||
"issuetype": {"name": issue_type},
|
||||
"labels": ["aegis", "security-test", mitre_id.replace(".", "-")],
|
||||
"labels": labels,
|
||||
# customfield_10309 = Proof of Concept field (required by team's Jira config)
|
||||
"customfield_10309": f"{{code}}{poc}{{code}}",
|
||||
JIRA_FIELD_TTP: mitre_id,
|
||||
@@ -862,6 +869,47 @@ def push_test_event(
|
||||
)
|
||||
|
||||
|
||||
def push_assignee_update(db: Session, test: Test, assignee: User) -> None:
|
||||
"""Sync a lead's manual operator assignment to the linked Jira ticket.
|
||||
|
||||
Called from ``POST /tests/{id}/assign`` so Jira reflects the assignment
|
||||
immediately, instead of waiting for the operator to start execution
|
||||
(the only other point that pushes a Jira assignee — see
|
||||
``push_test_event``'s ``red_executing`` handling above).
|
||||
"""
|
||||
if not has_admin_jira_configured(db):
|
||||
return
|
||||
|
||||
link = (
|
||||
db.query(JiraLink)
|
||||
.filter(
|
||||
JiraLink.entity_type == JiraLinkEntityType.test,
|
||||
JiraLink.entity_id == test.id,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if not link:
|
||||
return
|
||||
|
||||
jira_account_id = getattr(assignee, "jira_account_id", None)
|
||||
if not jira_account_id:
|
||||
return
|
||||
|
||||
try:
|
||||
jira = get_admin_jira_client(db)
|
||||
jira.assign_issue(link.jira_issue_key, account_id=jira_account_id)
|
||||
link.last_synced_at = datetime.utcnow()
|
||||
db.flush()
|
||||
logger.info(
|
||||
"Assigned Jira ticket %s to account %s (manual assignment)",
|
||||
link.jira_issue_key, jira_account_id,
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.warning(
|
||||
"Could not assign %s to %s: %s", link.jira_issue_key, jira_account_id, exc,
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# On-hold Jira notification
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user