Skip to content

Commit c17735e

Browse files
committed
fix(advisory): adjust advisory publishing to new mailman hyperkitty setup
Extract the advisory generation functions to the advisory helper module instead of the flask view. This allows an easier usage across different views. Instead of actually pulling the published content from the mailinglist, lets store the current security tracker content instead.
1 parent e487799 commit c17735e

File tree

7 files changed

+128
-102
lines changed

7 files changed

+128
-102
lines changed

config/00-default.conf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ advisory_url = https://security.archlinux.org/AVG-{1}
33
group_url = https://security.archlinux.org/AVG-{0}
44
issue_url = https://security.archlinux.org/{0}
55
bugtracker_url = https://bugs.archlinux.org/task/{0}
6-
mailman_url = https://lists.archlinux.org/pipermail/arch-security/
6+
mailman_url = https://lists.archlinux.org/archives/list/arch-security@lists.archlinux.org/
77
password_length_min = 16
88
password_length_max = 64
99
summary_length_max = 200

tracker/advisory.py

Lines changed: 114 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,119 @@
1-
from datetime import date
21
from datetime import datetime
32
from html import unescape
3+
from os.path import join
44
from re import IGNORECASE
55
from re import escape
66
from re import search
77
from re import sub
8+
from urllib.parse import urlparse
89

10+
from flask import render_template
911
from markupsafe import escape as html_escape
1012
from requests import get
1113

14+
from config import TRACKER_ADVISORY_URL
15+
from config import TRACKER_BUGTRACKER_URL
16+
from config import TRACKER_GROUP_URL
17+
from config import TRACKER_ISSUE_URL
1218
from config import TRACKER_MAILMAN_URL
19+
from tracker import db
20+
from tracker.model import CVE
21+
from tracker.model import Advisory
22+
from tracker.model import CVEGroup
23+
from tracker.model import CVEGroupEntry
24+
from tracker.model import CVEGroupPackage
25+
from tracker.model import Package
26+
from tracker.model.enum import Publication
27+
from tracker.model.enum import Remote
28+
from tracker.user import user_can_handle_advisory
1329
from tracker.util import chunks
1430
from tracker.util import issue_to_numeric
31+
from tracker.util import multiline_to_list
32+
33+
34+
def generate_advisory(advisory_id, with_subject=True, raw=True):
35+
entries = (db.session.query(Advisory, CVEGroup, CVEGroupPackage, CVE)
36+
.filter(Advisory.id == advisory_id)
37+
.join(CVEGroupPackage, Advisory.group_package)
38+
.join(CVEGroup, CVEGroupPackage.group)
39+
.join(CVEGroupEntry, CVEGroup.issues)
40+
.join(CVE, CVEGroupEntry.cve)
41+
.order_by(CVE.id)
42+
).all()
43+
if not entries:
44+
return None
45+
46+
advisory = entries[0][0]
47+
group = entries[0][1]
48+
package = entries[0][2]
49+
issues = sorted([issue for (advisory, group, package, issue) in entries])
50+
severity_sorted_issues = sorted(issues, key=lambda issue: issue.issue_type)
51+
severity_sorted_issues = sorted(severity_sorted_issues, key=lambda issue: issue.severity)
52+
remote = any([issue.remote is Remote.remote for issue in issues])
53+
issue_listing_formatted = advisory_format_issue_listing([issue.id for issue in issues])
54+
55+
link = TRACKER_ADVISORY_URL.format(advisory.id, group.id)
56+
upstream_released = group.affected.split('-')[0].split('+')[0] != group.fixed.split('-')[0].split('+')[0]
57+
upstream_version = group.fixed.split('-')[0].split('+')[0]
58+
if ':' in upstream_version:
59+
upstream_version = upstream_version[upstream_version.index(':') + 1:]
60+
unique_issue_types = []
61+
for issue in severity_sorted_issues:
62+
if issue.issue_type not in unique_issue_types:
63+
unique_issue_types.append(issue.issue_type)
64+
65+
references = []
66+
if group.bug_ticket:
67+
references.append(TRACKER_BUGTRACKER_URL.format(group.bug_ticket))
68+
references.extend([ref for ref in multiline_to_list(group.reference)
69+
if ref not in references])
70+
list(map(lambda issue: references.extend(
71+
[ref for ref in multiline_to_list(issue.reference) if ref not in references]), issues))
72+
73+
raw_asa = render_template('advisory.txt',
74+
advisory=advisory,
75+
group=group,
76+
package=package,
77+
issues=issues,
78+
remote=remote,
79+
issue_listing_formatted=issue_listing_formatted,
80+
link=link,
81+
workaround=advisory.workaround,
82+
impact=advisory.impact,
83+
upstream_released=upstream_released,
84+
upstream_version=upstream_version,
85+
unique_issue_types=unique_issue_types,
86+
references=references,
87+
with_subject=with_subject,
88+
TRACKER_ISSUE_URL=TRACKER_ISSUE_URL,
89+
TRACKER_GROUP_URL=TRACKER_GROUP_URL)
90+
if raw:
91+
return raw_asa
92+
93+
raw_asa = '\n'.join(raw_asa.split('\n')[2:])
94+
raw_asa = str(html_escape(raw_asa))
95+
raw_asa = advisory_extend_html(raw_asa, issues, package)
96+
return render_html_advisory(advisory=advisory, package=package, group=group, raw_asa=raw_asa, generated=True)
97+
98+
99+
def render_html_advisory(advisory, package, group, raw_asa, generated):
100+
return render_template('advisory.html',
101+
title='[{}] {}: {}'.format(advisory.id, package.pkgname, advisory.advisory_type),
102+
advisory=advisory,
103+
package=package,
104+
raw_asa=raw_asa,
105+
generated=generated,
106+
can_handle_advisory=user_can_handle_advisory(),
107+
Publication=Publication)
15108

16109

17110
def advisory_fetch_from_mailman(url):
18111
try:
19112
response = get(url)
20113
if 200 != response.status_code:
21114
return None
22-
asa = unescape(sub('</?A[^<]*?>', '', response.text))
23-
start = '<PRE>'
24-
start_marker = '{}Arch Linux Security Advisory'.format(start)
25-
end = '\n-------------- next part --------------'
26-
asa = asa[asa.index(start_marker) + len(start):asa.index(end)]
27-
return asa.strip()
115+
116+
return response.text
28117
except Exception:
29118
return None
30119

@@ -33,18 +122,29 @@ def advisory_fetch_reference_url_from_mailman(advisory):
33122
try:
34123
year = advisory.id[4:8]
35124
month = advisory.id[8:10]
36-
publish_date = date(int(year), int(month), 1)
37-
mailman_url = '{}{}-{}/'.format(TRACKER_MAILMAN_URL, year, publish_date.strftime('%B'))
38-
response = get(mailman_url)
125+
mailman_monthly = '{}{}/{}/?count=100'.format(TRACKER_MAILMAN_URL, year, month)
126+
127+
response = get(mailman_monthly)
39128
if 200 != response.status_code:
40129
return None
130+
131+
mailman_url = urlparse(TRACKER_MAILMAN_URL)
132+
thread_url_base = join(mailman_url.path, 'thread')
133+
134+
message_url = None
41135
for line in response.text.split('\n'):
136+
if thread_url_base in line:
137+
match = search(r'href="{}/([/a-zA-Z0-9]+)"'.format(thread_url_base), line)
138+
if not match:
139+
continue
140+
141+
thread = match.group(1)
142+
message_url = join(TRACKER_MAILMAN_URL, 'message', thread)
143+
42144
if not '[{}]'.format(advisory.id) in line:
43145
continue
44-
match = search(r'HREF="(\d+.html)"', line)
45-
if not match:
46-
continue
47-
return '{}{}'.format(mailman_url, match.group(1))
146+
147+
return message_url
48148
return None
49149
except Exception:
50150
return None
@@ -96,14 +196,6 @@ def advisory_extend_html(advisory, issues, package):
96196
return advisory
97197

98198

99-
def advisory_extend_model_from_advisory_text(advisory):
100-
if not advisory.content:
101-
return advisory
102-
advisory.impact = advisory_get_impact_from_text(advisory.content)
103-
advisory.workaround = advisory_get_workaround_from_text(advisory.content)
104-
return advisory
105-
106-
107199
def advisory_get_date_label(utctimetuple=None):
108200
now = utctimetuple if utctimetuple else datetime.utcnow().utctimetuple()
109201
return '{}{:02}'.format(now.tm_year, now.tm_mon)

tracker/form/validators.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from tracker import db
88
from tracker.advisory import advisory_fetch_from_mailman
9+
from tracker.advisory import generate_advisory
910
from tracker.model import Package
1011
from tracker.model.advisory import advisory_regex
1112
from tracker.model.cve import cve_id_regex
@@ -21,18 +22,20 @@ def __call__(self, form, field):
2122
if not field.data:
2223
return
2324

24-
form.advisory_content = advisory_fetch_from_mailman(field.data)
25-
if not form.advisory_content:
25+
mailman_content = advisory_fetch_from_mailman(field.data)
26+
if not mailman_content:
2627
raise ValidationError('Failed to fetch advisory')
2728

28-
m = search(advisory_regex[1:-1], form.advisory_content)
29+
m = search(advisory_regex[1:-1], mailman_content)
2930
if not m:
3031
raise ValidationError('Failed to fetch advisory')
3132

3233
found = m.group(1)
3334
if found != form.advisory_id:
3435
raise ValidationError('Advisory mismatched: {}'.format(found))
3536

37+
form.advisory_content = generate_advisory(advisory_id=form.advisory_id, with_subject=False, raw=True)
38+
3639

3740
class ValidPackageName(object):
3841
def __init__(self):

tracker/templates/advisory.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
Subject: [{{ advisory.id }}] {{ package.pkgname }}: {{ advisory.advisory_type }}
1+
{%- if with_subject %}Subject: [{{ advisory.id }}] {{ package.pkgname }}: {{ advisory.advisory_type }}
22

3+
{% endif -%}
34
{% set asa_title = 'Arch Linux Security Advisory ' + advisory.id %}
45
{{- asa_title }}
56
{% set asa_title_separator = '=' * asa_title|length %}

tracker/view/advisory.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
from config import TRACKER_ISSUE_URL
1414
from tracker import db
1515
from tracker import tracker
16-
from tracker.advisory import advisory_extend_model_from_advisory_text
1716
from tracker.advisory import advisory_fetch_reference_url_from_mailman
1817
from tracker.advisory import advisory_get_date_label
1918
from tracker.advisory import advisory_get_label
@@ -219,7 +218,6 @@ def publish_advisory(asa):
219218

220219
if advisory.reference != form.reference.data:
221220
advisory.content = form.advisory_content
222-
advisory_extend_model_from_advisory_text(advisory)
223221
advisory.reference = form.reference.data
224222
advisory.publication = Publication.published
225223
db.session.commit()

tracker/view/edit.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313

1414
from tracker import db
1515
from tracker import tracker
16-
from tracker.advisory import advisory_extend_model_from_advisory_text
1716
from tracker.advisory import advisory_fetch_reference_url_from_mailman
1817
from tracker.form import CVEForm
1918
from tracker.form import GroupForm
@@ -114,7 +113,6 @@ def edit_advisory(advisory_id):
114113
advisory.workaround = form.workaround.data or None
115114
if advisory.reference != form.reference.data:
116115
advisory.content = form.advisory_content
117-
advisory_extend_model_from_advisory_text(advisory)
118116
advisory.reference = form.reference.data or None
119117

120118
# update changed date on modification

tracker/view/show.py

Lines changed: 5 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
from tracker.advisory import advisory_escape_html
2121
from tracker.advisory import advisory_extend_html
2222
from tracker.advisory import advisory_format_issue_listing
23+
from tracker.advisory import generate_advisory
24+
from tracker.advisory import render_html_advisory
2325
from tracker.form.advisory import AdvisoryForm
2426
from tracker.model import CVE
2527
from tracker.model import Advisory
@@ -488,17 +490,6 @@ def show_package(pkgname):
488490
package=data)
489491

490492

491-
def render_html_advisory(advisory, package, group, raw_asa, generated):
492-
return render_template('advisory.html',
493-
title='[{}] {}: {}'.format(advisory.id, package.pkgname, advisory.advisory_type),
494-
advisory=advisory,
495-
package=package,
496-
raw_asa=raw_asa,
497-
generated=generated,
498-
can_handle_advisory=user_can_handle_advisory(),
499-
Publication=Publication)
500-
501-
502493
@tracker.route('/advisory/<regex("{}"):advisory_id>/raw'.format(advisory_regex[1:-1]), methods=['GET'])
503494
@tracker.route('/<regex("{}"):advisory_id>/raw'.format(advisory_regex[1:-1]), methods=['GET'])
504495
def show_advisory_raw(advisory_id):
@@ -554,67 +545,10 @@ def show_advisory(advisory_id, raw=False):
554545
@tracker.route('/advisory/<regex("{}"):advisory_id>/generate'.format(advisory_regex[1:-1]), methods=['GET'])
555546
@tracker.route('/<regex("{}"):advisory_id>/generate'.format(advisory_regex[1:-1]), methods=['GET'])
556547
def show_generated_advisory(advisory_id, raw=False):
557-
entries = (db.session.query(Advisory, CVEGroup, CVEGroupPackage, CVE)
558-
.filter(Advisory.id == advisory_id)
559-
.join(CVEGroupPackage, Advisory.group_package)
560-
.join(CVEGroup, CVEGroupPackage.group)
561-
.join(CVEGroupEntry, CVEGroup.issues)
562-
.join(CVE, CVEGroupEntry.cve)
563-
.order_by(CVE.id)
564-
).all()
565-
if not entries:
548+
advisory = generate_advisory(advisory_id, with_subject=True, raw=raw)
549+
if not advisory:
566550
return not_found()
567-
568-
advisory = entries[0][0]
569-
group = entries[0][1]
570-
package = entries[0][2]
571-
issues = sorted([issue for (advisory, group, package, issue) in entries])
572-
severity_sorted_issues = sorted(issues, key=lambda issue: issue.issue_type)
573-
severity_sorted_issues = sorted(severity_sorted_issues, key=lambda issue: issue.severity)
574-
remote = any([issue.remote is Remote.remote for issue in issues])
575-
issue_listing_formatted = advisory_format_issue_listing([issue.id for issue in issues])
576-
577-
link = TRACKER_ADVISORY_URL.format(advisory.id, group.id)
578-
upstream_released = group.affected.split('-')[0].split('+')[0] != group.fixed.split('-')[0].split('+')[0]
579-
upstream_version = group.fixed.split('-')[0].split('+')[0]
580-
if ':' in upstream_version:
581-
upstream_version = upstream_version[upstream_version.index(':') + 1:]
582-
unique_issue_types = []
583-
for issue in severity_sorted_issues:
584-
if issue.issue_type not in unique_issue_types:
585-
unique_issue_types.append(issue.issue_type)
586-
587-
references = []
588-
if group.bug_ticket:
589-
references.append(TRACKER_BUGTRACKER_URL.format(group.bug_ticket))
590-
references.extend([ref for ref in multiline_to_list(group.reference)
591-
if ref not in references])
592-
list(map(lambda issue: references.extend(
593-
[ref for ref in multiline_to_list(issue.reference) if ref not in references]), issues))
594-
595-
raw_asa = render_template('advisory.txt',
596-
advisory=advisory,
597-
group=group,
598-
package=package,
599-
issues=issues,
600-
remote=remote,
601-
issue_listing_formatted=issue_listing_formatted,
602-
link=link,
603-
workaround=advisory.workaround,
604-
impact=advisory.impact,
605-
upstream_released=upstream_released,
606-
upstream_version=upstream_version,
607-
unique_issue_types=unique_issue_types,
608-
references=references,
609-
TRACKER_ISSUE_URL=TRACKER_ISSUE_URL,
610-
TRACKER_GROUP_URL=TRACKER_GROUP_URL)
611-
if raw:
612-
return raw_asa
613-
614-
raw_asa = '\n'.join(raw_asa.split('\n')[2:])
615-
raw_asa = str(escape(raw_asa))
616-
raw_asa = advisory_extend_html(raw_asa, issues, package)
617-
return render_html_advisory(advisory=advisory, package=package, group=group, raw_asa=raw_asa, generated=True)
551+
return advisory
618552

619553

620554
@tracker.route('/advisory/<regex("{}"):advisory_id>/log'.format(advisory_regex[1:-1]), methods=['GET'])

0 commit comments

Comments
 (0)