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:
@@ -0,0 +1,78 @@
|
||||
"""A TestTemplate/TemplateSuggestion's mitre_technique_id must correspond
|
||||
to a real, already-synced MITRE ATT&CK technique — free text or a typo'd
|
||||
ID would otherwise create an orphaned template invisible to technique
|
||||
lookups and coverage reporting.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def technique(api, auth_headers):
|
||||
resp = api(
|
||||
"post", "/api/v1/techniques", auth_headers,
|
||||
json={"mitre_id": "T1059.500", "name": "Command Line MITRE Validation"},
|
||||
)
|
||||
assert resp.status_code == 201, resp.text
|
||||
return resp.json()["id"]
|
||||
|
||||
|
||||
class TestTemplateCreateValidation:
|
||||
def test_create_template_rejects_unknown_mitre_id(self, api, red_lead_headers, technique):
|
||||
resp = api(
|
||||
"post", "/api/v1/test-templates", red_lead_headers,
|
||||
json={"mitre_technique_id": "T9999.999", "name": "Bogus template"},
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
assert "T9999.999" in resp.text
|
||||
|
||||
def test_create_template_accepts_known_mitre_id(self, api, red_lead_headers, technique):
|
||||
resp = api(
|
||||
"post", "/api/v1/test-templates", red_lead_headers,
|
||||
json={"mitre_technique_id": "T1059.500", "name": "Valid template"},
|
||||
)
|
||||
assert resp.status_code == 201, resp.text
|
||||
|
||||
def test_update_template_rejects_unknown_mitre_id(self, api, red_lead_headers, technique):
|
||||
created = api(
|
||||
"post", "/api/v1/test-templates", red_lead_headers,
|
||||
json={"mitre_technique_id": "T1059.500", "name": "Will be edited"},
|
||||
)
|
||||
template_id = created.json()["id"]
|
||||
|
||||
resp = api(
|
||||
"patch", f"/api/v1/test-templates/{template_id}", red_lead_headers,
|
||||
json={"mitre_technique_id": "T0000.000", "name": "Will be edited"},
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
|
||||
|
||||
class TestTemplateSuggestionValidation:
|
||||
def test_propose_template_rejects_unknown_mitre_id(self, api, red_tech_headers, technique):
|
||||
resp = api(
|
||||
"post", "/api/v1/template-suggestions", red_tech_headers,
|
||||
json={"mitre_technique_id": "T8888.888", "name": "Bogus proposal"},
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
|
||||
def test_propose_template_accepts_known_mitre_id(self, api, red_tech_headers, technique):
|
||||
resp = api(
|
||||
"post", "/api/v1/template-suggestions", red_tech_headers,
|
||||
json={"mitre_technique_id": "T1059.500", "name": "Valid proposal"},
|
||||
)
|
||||
assert resp.status_code == 201, resp.text
|
||||
|
||||
def test_approve_suggestion_with_edited_unknown_mitre_id_rejected(
|
||||
self, api, red_tech_headers, red_lead_headers, technique,
|
||||
):
|
||||
created = api(
|
||||
"post", "/api/v1/template-suggestions", red_tech_headers,
|
||||
json={"mitre_technique_id": "T1059.500", "name": "Valid proposal"},
|
||||
)
|
||||
suggestion_id = created.json()["id"]
|
||||
|
||||
resp = api(
|
||||
"post", f"/api/v1/template-suggestions/{suggestion_id}/approve", red_lead_headers,
|
||||
json={"mitre_technique_id": "T7777.777"},
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
Reference in New Issue
Block a user