Skip to content

Commit 2000f51

Browse files
committed
fix and improve vuln filtering
Signed-off-by: tdruez <tdruez@aboutcode.org>
1 parent 46ddb00 commit 2000f51

2 files changed

Lines changed: 95 additions & 35 deletions

File tree

product_portfolio/templates/product_portfolio/tabs/tab_packages_vulnerabilities.html

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@
1414
{% include 'includes/object_list_table_header.html' with filter=filterset include_actions=True %}
1515
<tbody>
1616
{% for product_package in page_obj.object_list %}
17+
{% if product_package.display_vulnerabilities %}
1718
<tr>
18-
<td rowspan="{{ product_package.package.vulnerability_count }}">
19+
<td rowspan="{{ product_package.display_vulnerabilities|length }}">
1920
<strong>
2021
<a href="{{ product_package.package.get_absolute_url }}#vulnerabilities" target="_blank">{{ product_package.package }}</a>
2122
</strong>
@@ -35,7 +36,7 @@
3536
{% endif %}
3637
</div>
3738
</td>
38-
{% for vulnerability in product_package.package.affected_by_vulnerabilities.all %}
39+
{% for vulnerability in product_package.display_vulnerabilities %}
3940
{% if not forloop.first %}<tr>{% endif %}
4041
<td>
4142
<strong>
@@ -136,6 +137,7 @@
136137
{% if not forloop.first %}</tr>{% endif %}
137138
{% endfor %}
138139
</tr>
140+
{% endif %}
139141
{% empty %}
140142
<tr>
141143
<td colspan="10">

product_portfolio/views.py

Lines changed: 91 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1239,7 +1239,7 @@ class ProductTabVulnerabilitiesView(
12391239
),
12401240
Header(
12411241
"triage_action",
1242-
_("Recommended action"),
1242+
_("Recommendation"),
12431243
help_text=_("Action recommended by the triage engine for this vulnerability"),
12441244
filter="triage_action",
12451245
),
@@ -1275,6 +1275,94 @@ class ProductTabVulnerabilitiesView(
12751275
),
12761276
)
12771277

1278+
def attach_vulnerability_analyses(self, page_obj):
1279+
"""Set the matching VulnerabilityAnalysis instance on each prefetched vulnerability."""
1280+
for product_package in page_obj.object_list:
1281+
for vulnerability in product_package.package.affected_by_vulnerabilities.all():
1282+
for analysis in vulnerability.vulnerability_analyses.all():
1283+
if analysis.product_package_id == product_package.id:
1284+
vulnerability.vulnerability_analysis = analysis
1285+
continue
1286+
1287+
REACHABILITY_FILTER_MAP = {"yes": True, "no": False, "unknown": None}
1288+
1289+
def get_vulnerability_display_filters(self):
1290+
"""Return the active per-vulnerability filters from the request."""
1291+
params = self.request.GET
1292+
prefix = self.tab_id
1293+
return {
1294+
"triage_action": params.get(f"{prefix}-triage_action", ""),
1295+
"state": params.get(f"{prefix}-vulnerability_analyses__state", ""),
1296+
"justification": params.get(f"{prefix}-vulnerability_analyses__justification", ""),
1297+
"is_reachable": params.get(f"{prefix}-is_reachable", ""),
1298+
}
1299+
1300+
def vulnerability_passes_display_filters(self, vulnerability, display_filters):
1301+
"""Return True if the vulnerability matches all active display filters."""
1302+
triage_action = display_filters.get("triage_action")
1303+
if triage_action:
1304+
record = getattr(vulnerability, "triage_record", None)
1305+
if getattr(record, "action", "") != triage_action:
1306+
return False
1307+
analysis = getattr(vulnerability, "vulnerability_analysis", None)
1308+
state = display_filters.get("state")
1309+
if state:
1310+
if getattr(analysis, "state", "") != state:
1311+
return False
1312+
justification = display_filters.get("justification")
1313+
if justification:
1314+
if getattr(analysis, "justification", "") != justification:
1315+
return False
1316+
is_reachable_filter = display_filters.get("is_reachable")
1317+
if is_reachable_filter:
1318+
expected = self.REACHABILITY_FILTER_MAP[is_reachable_filter]
1319+
if getattr(analysis, "is_reachable", object()) != expected:
1320+
return False
1321+
return True
1322+
1323+
def attach_triage_data(self, product, page_obj):
1324+
"""
1325+
Attach the winning TriageRecord to each vulnerability and build
1326+
display_vulnerabilities on each product_package, filtered by any
1327+
active per-vulnerability filters.
1328+
"""
1329+
vulnerability_ids = {
1330+
vulnerability.id
1331+
for product_package in page_obj.object_list
1332+
for vulnerability in product_package.package.affected_by_vulnerabilities.all()
1333+
}
1334+
action_labels = dict(TriageAction.choices)
1335+
triage_records = list(
1336+
TriageRecord.objects.filter(
1337+
product=product,
1338+
vulnerability_id__in=vulnerability_ids,
1339+
)
1340+
.primary_actions()
1341+
.select_related("ruleset")
1342+
)
1343+
for record in triage_records:
1344+
record.action_label = action_labels.get(record.action, record.action)
1345+
badge_class, icon = TRIAGE_ACTION_STYLES.get(record.action, TRIAGE_ACTION_DEFAULT_STYLE)
1346+
record.action_badge_class = badge_class
1347+
record.action_icon = icon
1348+
1349+
triage_by_vulnerability = {record.vulnerability_id: record for record in triage_records}
1350+
display_filters = self.get_vulnerability_display_filters()
1351+
has_display_filters = any(display_filters.values())
1352+
1353+
for product_package in page_obj.object_list:
1354+
all_vulnerabilities = list(product_package.package.affected_by_vulnerabilities.all())
1355+
for vulnerability in all_vulnerabilities:
1356+
vulnerability.triage_record = triage_by_vulnerability.get(vulnerability.id)
1357+
if has_display_filters:
1358+
product_package.display_vulnerabilities = [
1359+
vulnerability
1360+
for vulnerability in all_vulnerabilities
1361+
if self.vulnerability_passes_display_filters(vulnerability, display_filters)
1362+
]
1363+
else:
1364+
product_package.display_vulnerabilities = all_vulnerabilities
1365+
12781366
def get_context_data(self, **kwargs):
12791367
product = self.object
12801368

@@ -1324,38 +1412,8 @@ def get_context_data(self, **kwargs):
13241412
page_number = self.request.GET.get(self.query_dict_page_param)
13251413
page_obj = paginator.get_page(page_number)
13261414

1327-
# Set the proper VulnerabilityAnalysis instance on each vulnerability
1328-
for product_package in page_obj.object_list:
1329-
for vulnerability in product_package.package.affected_by_vulnerabilities.all():
1330-
for analysis in vulnerability.vulnerability_analyses.all():
1331-
if analysis.product_package_id == product_package.id:
1332-
vulnerability.vulnerability_analysis = analysis
1333-
continue
1334-
1335-
# Attach the winning triage record to each vulnerability
1336-
vulnerability_ids = {
1337-
vulnerability.id
1338-
for product_package in page_obj.object_list
1339-
for vulnerability in product_package.package.affected_by_vulnerabilities.all()
1340-
}
1341-
action_labels = dict(TriageAction.choices)
1342-
triage_records = list(
1343-
TriageRecord.objects.filter(
1344-
product=product,
1345-
vulnerability_id__in=vulnerability_ids,
1346-
)
1347-
.primary_actions()
1348-
.select_related("ruleset")
1349-
)
1350-
for record in triage_records:
1351-
record.action_label = action_labels.get(record.action, record.action)
1352-
badge_class, icon = TRIAGE_ACTION_STYLES.get(record.action, TRIAGE_ACTION_DEFAULT_STYLE)
1353-
record.action_badge_class = badge_class
1354-
record.action_icon = icon
1355-
triage_by_vulnerability = {record.vulnerability_id: record for record in triage_records}
1356-
for product_package in page_obj.object_list:
1357-
for vulnerability in product_package.package.affected_by_vulnerabilities.all():
1358-
vulnerability.triage_record = triage_by_vulnerability.get(vulnerability.id)
1415+
self.attach_vulnerability_analyses(page_obj)
1416+
self.attach_triage_data(product, page_obj)
13591417

13601418
context_data.update(
13611419
{

0 commit comments

Comments
 (0)