Skip to content

Commit c9552b4

Browse files
committed
Optimize get non vulnerable versions
Signed-off-by: Tushar Goel <tushar.goel.dav@gmail.com>
1 parent 3d413f2 commit c9552b4

3 files changed

Lines changed: 63 additions & 42 deletions

File tree

vulnerabilities/api_v3.py

Lines changed: 59 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from urllib.parse import urlencode
1313

1414
from django.db.models import Exists
15+
from django.db.models import Max
1516
from django.db.models import OuterRef
1617
from django.db.models import Prefetch
1718
from django_filters import rest_framework as filters
@@ -226,8 +227,7 @@ def get_affected_by_vulnerabilities(self, package):
226227

227228
for adv in advisories:
228229
fixed = impact_map.get(adv["avid"])
229-
if not fixed:
230-
continue
230+
adv.pop("avid", None)
231231

232232
result.append(
233233
{
@@ -294,12 +294,9 @@ def get_affected_by_vulnerabilities(self, package):
294294
return self.return_advisories_data(package, advisories_qs, advisories)
295295

296296
def get_fixing_vulnerabilities(self, package):
297-
fixing_advisories = AdvisorySet.objects.filter(
298-
package=package, relation_type="fixing"
299-
).values_list("primary_advisory__advisory_id", flat=True)
300-
301-
if fixing_advisories:
302-
return [{"advisory_id": adv_id.split("/")[-1]} for adv_id in fixing_advisories]
297+
advisories = self.context["fixing_advisory_map"].get(package.id, [])
298+
if advisories:
299+
return advisories
303300

304301
advisories_qs = AdvisoryV2.objects.latest_fixed_by_advisories_for_purl(package.package_url)
305302

@@ -326,6 +323,8 @@ def get_fixing_vulnerabilities(self, package):
326323
"impacted_packages__affecting_packages",
327324
"impacted_packages__fixed_by_packages",
328325
)
326+
if not advisories_qs.exists():
327+
return []
329328
advisories: List[GroupedAdvisory] = merge_and_save_grouped_advisories(
330329
package, advisories_qs, "fixing"
331330
)
@@ -454,12 +453,13 @@ def create(self, request, *args, **kwargs):
454453
query = PackageV2.objects.filter(package_url__in=purls).order_by("package_url")
455454

456455
page = self.paginate_queryset(query)
457-
advisory_map = get_grouped_advisories_bulk(page)
456+
affected_advisory_map = get_affected_advisories_bulk(page)
457+
fixing_advisory_map = get_fixing_advisories_bulk(page)
458458
impact_map = get_impacts_bulk(page)
459459
serializer = self.get_serializer(
460460
page,
461461
many=True,
462-
context={"request": request, "advisory_map": advisory_map, "impact_map": impact_map},
462+
context={"request": request, "advisory_map": affected_advisory_map, "impact_map": impact_map, "fixing_advisory_map": fixing_advisory_map},
463463
)
464464
return self.get_paginated_response(serializer.data)
465465

@@ -562,41 +562,35 @@ class AffectedByAdvisoriesViewSet(PackageAdvisoriesViewSet):
562562
serializer_class = AffectedByAdvisoryV3Serializer
563563

564564

565-
def get_grouped_advisories_bulk(packages):
565+
def get_affected_advisories_bulk(packages):
566566
package_ids = [p.id for p in packages]
567567

568568
advisory_sets = list(
569569
AdvisorySet.objects.filter(
570570
package_id__in=package_ids,
571571
relation_type="affecting",
572572
)
573-
.select_related("primary_advisory", "package")
574-
.prefetch_related(
575-
Prefetch("aliases", queryset=AdvisoryAlias.objects.only("alias")),
576-
Prefetch(
577-
"members",
578-
queryset=AdvisorySetMember.objects.filter(is_primary=False)
579-
.select_related("advisory")
580-
.only(
581-
"advisory__avid",
582-
"advisory__weighted_severity",
583-
"advisory__exploitability",
584-
),
585-
to_attr="secondary_members",
573+
.select_related("primary_advisory")
574+
.prefetch_related(Prefetch("aliases", queryset=AdvisoryAlias.objects.only("alias")))
575+
.annotate(
576+
max_severity=Max(
577+
"members__advisory__weighted_severity",
578+
),
579+
max_exploitability=Max(
580+
"members__advisory__exploitability",
586581
),
587582
)
588583
.only(
589584
"id",
590585
"package_id",
591586
"primary_advisory__avid",
592587
"primary_advisory__summary",
593-
"primary_advisory__weighted_severity",
594-
"primary_advisory__exploitability",
595588
"primary_advisory__advisory_id",
596589
)
597590
)
598591

599592
package_map = defaultdict(list)
593+
600594
for adv in advisory_sets:
601595
adv._aliases_cache = [a.alias for a in adv.aliases.all()]
602596
package_map[adv.package_id].append(adv)
@@ -609,23 +603,14 @@ def get_grouped_advisories_bulk(packages):
609603

610604
for adv in groups:
611605
primary = adv.primary_advisory
612-
secondaries = [m.advisory for m in adv.secondary_members]
613606

614-
max_sev = primary.weighted_severity or 0.0
615-
max_exp = primary.exploitability or 0.0
616-
617-
for sec in secondaries:
618-
if sec.weighted_severity:
619-
max_sev = max(max_sev, sec.weighted_severity)
620-
if sec.exploitability:
621-
max_exp = max(max_exp, sec.exploitability)
607+
max_sev = adv.max_severity or 0.0
608+
max_exp = adv.max_exploitability or 0.0
622609

623610
weighted_severity = round(max_sev, 1) if max_sev else None
624611
exploitability = max_exp or None
625612

626-
risk_score = None
627-
if exploitability and weighted_severity:
628-
risk_score = round(min(exploitability * weighted_severity, 10.0), 1)
613+
risk_score = round(min(max_exp * max_sev, 10.0), 1) if max_exp and max_sev else None
629614

630615
identifier = primary.advisory_id.split("/")[-1]
631616

@@ -681,3 +666,39 @@ def get_impacts_bulk(packages):
681666
impact_map[impact.package_id][avid] = fixed_cache[ip.id]
682667

683668
return impact_map
669+
670+
671+
def get_fixing_advisories_bulk(packages):
672+
package_ids = [p.id for p in packages]
673+
674+
advisory_sets = list(
675+
AdvisorySet.objects.filter(
676+
package_id__in=package_ids,
677+
relation_type="fixing",
678+
)
679+
.only(
680+
"id",
681+
"package_id",
682+
"primary_advisory__advisory_id",
683+
)
684+
)
685+
686+
package_map = defaultdict(list)
687+
688+
for adv in advisory_sets:
689+
package_map[adv.package_id].append(adv.primary_advisory.advisory_id)
690+
691+
result = {}
692+
693+
for package in packages:
694+
groups = package_map.get(package.id, [])
695+
grouped = []
696+
697+
for adv_id in groups:
698+
grouped.append(
699+
{"advisory_id": adv_id.split("/")[-1]}
700+
)
701+
702+
result[package.id] = grouped
703+
704+
return result

vulnerabilities/models.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3585,7 +3585,7 @@ def calculate_version_rank(self):
35853585
return self.version_rank
35863586

35873587
@cached_property
3588-
def _non_vulnerable_versions(self):
3588+
def get_non_vulnerable_versions(self):
35893589
"""
35903590
Cached computation to avoid duplicate queries.
35913591
Returns (next, latest)
@@ -3607,12 +3607,12 @@ def _non_vulnerable_versions(self):
36073607

36083608
@property
36093609
def next_non_vulnerable_version(self):
3610-
next_nv, _ = self._non_vulnerable_versions
3610+
next_nv, _ = self.get_non_vulnerable_versions
36113611
return next_nv if next_nv else None
36123612

36133613
@property
36143614
def latest_non_vulnerable_version(self):
3615-
_, latest_nv = self._non_vulnerable_versions
3615+
_, latest_nv = self.get_non_vulnerable_versions
36163616
return latest_nv if latest_nv else None
36173617

36183618
@cached_property

vulnerabilities/views.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ def get_context_data(self, **kwargs):
257257
context = super().get_context_data(**kwargs)
258258
package = self.object
259259

260-
next_non_vulnerable, latest_non_vulnerable = package.get_non_vulnerable_versions()
260+
next_non_vulnerable, latest_non_vulnerable = package.get_non_vulnerable_versions
261261

262262
context["package"] = package
263263
context["next_non_vulnerable"] = next_non_vulnerable

0 commit comments

Comments
 (0)