blob: 9bdf0f76633f32a2adf4b5fd0caa5a3ab8edfb41 [file]
import asyncio
import json
import types
from app import reports
from app.reports import Reporter, _asf_member_link, _project_link, _reporter, load_pmc_report, load_pmc_reports
def _write_report(path, label, *, subj="[SECURITY] a flaw"):
path = path.parent / label
path.write_text(json.dumps([
{
"subj": subj,
"from": "Jane Reporter <jane@aisle.com>",
"to": "security@cassandra.apache.org",
"message_id": "<abc@cassandra.apache.org>",
"mailtime": 1700000000,
}
]))
return path
def test_subproject_after_leading_date(tmp_path, monkeypatch):
monkeypatch.setattr(
reports.config, "get", lambda: types.SimpleNamespace(pmcs_using_jira={})
)
path = _write_report(tmp_path, "2024-03-01 native a flaw wf untriaged.json")
report = load_pmc_report("commons", path)
assert report.subproject == "native"
def test_subproject_after_single_cve(tmp_path, monkeypatch):
monkeypatch.setattr(
reports.config, "get", lambda: types.SimpleNamespace(pmcs_using_jira={})
)
path = _write_report(tmp_path, "CVE-2024-1234 lang a flaw.json")
report = load_pmc_report("commons", path)
assert report.subproject == "lang"
def test_subproject_after_multiple_cves(tmp_path, monkeypatch):
monkeypatch.setattr(
reports.config, "get", lambda: types.SimpleNamespace(pmcs_using_jira={})
)
path = _write_report(tmp_path, "CVE-2024-1234 CVE-2024-5678 io a flaw.json")
report = load_pmc_report("commons", path)
assert report.subproject == "io"
def test_subproject_none_when_no_prefix(tmp_path, monkeypatch):
monkeypatch.setattr(
reports.config, "get", lambda: types.SimpleNamespace(pmcs_using_jira={})
)
path = _write_report(tmp_path, "single.json")
report = load_pmc_report("commons", path)
assert report.subproject is None
def _full_config(tmp_path, pmcs_in_attic=()):
return types.SimpleNamespace(
data_dir_path=tmp_path,
pmcs_in_attic=list(pmcs_in_attic),
pmcs_using_jira={},
pmcs_using_github={},
)
def test_security_pmc_includes_attic_reports(tmp_path, monkeypatch):
monkeypatch.setattr(
reports.config, "get", lambda: _full_config(tmp_path, pmcs_in_attic=["hivemind"])
)
(tmp_path / "security").mkdir()
(tmp_path / "hivemind").mkdir()
_write_report(tmp_path / "security" / "x", "own report.json")
_write_report(tmp_path / "hivemind" / "x", "attic report.json")
result = asyncio.run(load_pmc_reports("security"))
assert {r.security_team_name for r in result} == {"own report.json", "attic report.json"}
by_name = {r.security_team_name: r for r in result}
assert by_name["attic report.json"].subproject == "hivemind"
assert by_name["own report.json"].subproject is None
def test_ordinary_pmc_does_not_include_attic_reports(tmp_path, monkeypatch):
monkeypatch.setattr(
reports.config, "get", lambda: _full_config(tmp_path, pmcs_in_attic=["hivemind"])
)
(tmp_path / "cassandra").mkdir()
(tmp_path / "hivemind").mkdir()
_write_report(tmp_path / "cassandra" / "x", "own report.json")
_write_report(tmp_path / "hivemind" / "x", "attic report.json")
result = asyncio.run(load_pmc_reports("cassandra"))
assert {r.security_team_name for r in result} == {"own report.json"}
def test_security_pmc_tolerates_missing_and_invalid_attic_dirs(tmp_path, monkeypatch):
monkeypatch.setattr(
reports.config,
"get",
lambda: _full_config(tmp_path, pmcs_in_attic=["hivemind", "../escape"]),
)
(tmp_path / "security").mkdir()
_write_report(tmp_path / "security" / "x", "own report.json")
result = asyncio.run(load_pmc_reports("security"))
assert {r.security_team_name for r in result} == {"own report.json"}
def test_asf_member_link_non_apache_to_uses_security_apache_org():
email = {
'to': 'Disclosure <disclosure@aisle.com>',
'message_id': '<7200416e-bd53-4026-a0a1-f3cf4c00de86n@aisle.com>',
}
assert _asf_member_link(email) == (
'https://lists.apache.org/thread/'
'<7200416e-bd53-4026-a0a1-f3cf4c00de86n%40aisle.com>'
'?<security.apache.org>'
)
def test_asf_member_link_apache_to_uses_to_domain():
email = {
'to': 'security@cassandra.apache.org',
'message_id': '<abc123@cassandra.apache.org>',
}
assert _asf_member_link(email) == (
'https://lists.apache.org/thread/'
'<abc123%40cassandra.apache.org>'
'?<security.cassandra.apache.org>'
)
def test_project_link_returns_first_apache_email():
emails = [
{'to': 'reporter@aisle.com', 'message_id': '<a@aisle.com>'},
{'to': 'security@cassandra.apache.org', 'message_id': '<b@cassandra.apache.org>'},
{'to': 'security@cassandra.apache.org', 'message_id': '<c@cassandra.apache.org>'},
]
assert _project_link(emails) == (
'https://lists.apache.org/thread/'
'<b%40cassandra.apache.org>'
'?<security.cassandra.apache.org>'
)
def test_project_link_only_considers_first_five():
emails = [{'to': 'reporter@aisle.com', 'message_id': f'<{i}@aisle.com>'} for i in range(5)]
emails.append({'to': 'security@cassandra.apache.org', 'message_id': '<late@cassandra.apache.org>'})
assert _project_link(emails) is None
def test_project_link_returns_none_when_no_apache_recipient():
emails = [
{'to': 'reporter@aisle.com', 'message_id': '<a@aisle.com>'},
{'to': 'disclosure@vendor.example', 'message_id': '<b@vendor.example>'},
]
assert _project_link(emails) is None
def test_asf_member_link_uses_cc_when_to_is_non_apache():
email = {
'to': 'reporter@aisle.com',
'cc': 'security@cassandra.apache.org',
'message_id': '<abc@aisle.com>',
}
assert _asf_member_link(email) == (
'https://lists.apache.org/thread/'
'<abc%40aisle.com>'
'?<security.cassandra.apache.org>'
)
def test_asf_member_link_prefers_to_over_cc():
email = {
'to': 'security@cassandra.apache.org',
'cc': 'security@kafka.apache.org',
'message_id': '<abc@cassandra.apache.org>',
}
assert _asf_member_link(email) == (
'https://lists.apache.org/thread/'
'<abc%40cassandra.apache.org>'
'?<security.cassandra.apache.org>'
)
def test_project_link_finds_apache_address_in_cc():
emails = [
{
'to': 'reporter@aisle.com',
'cc': 'security@cassandra.apache.org',
'message_id': '<a@aisle.com>',
},
]
assert _project_link(emails) == (
'https://lists.apache.org/thread/'
'<a%40aisle.com>'
'?<security.cassandra.apache.org>'
)
def test_reporter_uses_from_when_not_via():
email = {'from': 'Jane Q. Reporter <jane@aisle.com>'}
assert _reporter(email) == Reporter(name='Jane Q. Reporter', email='jane@aisle.com')
def test_reporter_falls_back_to_reply_to_when_via_security_list():
email = {
'from': '\"Jane Reporter via Security\" <security@apache.org>',
'reply_to': 'Jane Reporter <jane@aisle.com>',
}
assert _reporter(email) == Reporter(name='Jane Reporter', email='jane@aisle.com')
def test_reporter_falls_back_to_reply_to_when_via_project_list():
email = {
'from': 'Jane Reporter via Security <security@cassandra.apache.org>',
'reply_to': 'Jane Reporter <jane@aisle.com>',
}
assert _reporter(email) == Reporter(name='Jane Reporter', email='jane@aisle.com')
def test_reporter_returns_none_when_via_apache_and_no_reply_to():
email = {'from': 'Jane Reporter via Security <security@cassandra.apache.org>'}
assert _reporter(email) is None
def test_reporter_returns_none_when_from_missing():
assert _reporter({}) is None
def test_reporter_initials_from_name():
assert Reporter(name='Jane Q. Reporter', email='jane@aisle.com').initials == 'JQR'
def test_reporter_initials_fall_back_to_email_local_part():
assert Reporter(name='', email='alice@example.com').initials == 'A'