Skip to content

Commit a2fceea

Browse files
committed
Add cache on latest non vulnerable and next vulnerable
Signed-off-by: Tushar Goel <tushar.goel.dav@gmail.com>
1 parent 21ce0fc commit a2fceea

File tree

1 file changed

+75
-12
lines changed

1 file changed

+75
-12
lines changed

vulnerabilities/models.py

Lines changed: 75 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from django.contrib.auth import get_user_model
2121
from django.contrib.auth.models import UserManager
2222
from django.core import exceptions
23+
from django.core.cache import cache
2324
from django.core.exceptions import ValidationError
2425
from django.core.paginator import Paginator
2526
from django.core.validators import MaxValueValidator
@@ -471,18 +472,19 @@ def get_fixed_by_package_versions(self, purl: PackageURL, fix=True):
471472
Return a queryset of all the package versions of this `package` that fix any vulnerability.
472473
If `fix` is False, return all package versions whether or not they fix a vulnerability.
473474
"""
474-
filter_dict = {
475-
"name": purl.name,
476-
"namespace": purl.namespace,
475+
# TODO: Move this to Package object method
476+
filters = {
477477
"type": purl.type,
478+
"namespace": purl.namespace,
479+
"name": purl.name,
478480
"qualifiers": purl.qualifiers,
479481
"subpath": purl.subpath,
480482
}
481483

482484
if fix:
483-
filter_dict["fixing_vulnerabilities__isnull"] = False
485+
filters["fixing_vulnerabilities__isnull"] = False
484486

485-
return Package.objects.filter(**filter_dict).distinct()
487+
return Package.objects.filter(**filters).distinct()
486488

487489
def get_or_create_from_purl(self, purl: Union[PackageURL, str]):
488490
"""
@@ -648,7 +650,8 @@ class Package(PackageURLMixin):
648650
fixing_vulnerabilities = models.ManyToManyField(
649651
to="Vulnerability",
650652
through="FixingPackageRelatedVulnerability",
651-
related_name="fixed_by_packages", # Unique related_name
653+
# Unique related_name
654+
related_name="fixed_by_packages",
652655
)
653656

654657
package_url = models.CharField(
@@ -779,6 +782,10 @@ def version_class(self):
779782
def current_version(self):
780783
return self.version_class(self.version)
781784

785+
@property
786+
def vulnerabilities(self):
787+
return self.affected_by_vulnerabilities.all() | self.fixing_vulnerabilities.all()
788+
782789
@property
783790
def next_non_vulnerable_version(self):
784791
"""
@@ -787,10 +794,6 @@ def next_non_vulnerable_version(self):
787794
next_non_vulnerable, _ = self.get_non_vulnerable_versions()
788795
return next_non_vulnerable.version if next_non_vulnerable else None
789796

790-
@property
791-
def vulnerabilities(self):
792-
return self.affected_by_vulnerabilities.all() | self.fixing_vulnerabilities.all()
793-
794797
@property
795798
def latest_non_vulnerable_version(self):
796799
"""
@@ -823,6 +826,67 @@ def get_non_vulnerable_versions(self):
823826

824827
return None, None
825828

829+
@property
830+
def non_vulnerable_versions(self):
831+
"""
832+
Cache the result of get_non_vulnerable_versions_v2 to avoid redundant computations.
833+
"""
834+
if not hasattr(self, "_non_vulnerable_versions_cache"):
835+
self._non_vulnerable_versions_cache = self.get_non_vulnerable_versions_v2()
836+
return self._non_vulnerable_versions_cache
837+
838+
@property
839+
def next_non_vulnerable_package(self):
840+
"""
841+
Return the purl of the next non-vulnerable package version.
842+
"""
843+
next_non_vulnerable, _ = self.get_non_vulnerable_versions_v2()
844+
return next_non_vulnerable.purl if next_non_vulnerable else None
845+
846+
@property
847+
def latest_non_vulnerable_package(self):
848+
"""
849+
Return the purl of the latest non-vulnerable package version.
850+
"""
851+
_, latest_non_vulnerable = self.get_non_vulnerable_versions_v2()
852+
return latest_non_vulnerable.purl if latest_non_vulnerable else None
853+
854+
def get_non_vulnerable_versions_v2(self):
855+
"""
856+
Return a tuple of three Package instance:
857+
- first fixing version
858+
- next non-vulnerable version
859+
- latest non-vulnerable version
860+
Return a tuple of (None, None) if there is no non-vulnerable version.
861+
"""
862+
cache_key = f"non_vulnerable_versions_{self.id}"
863+
result = cache.get(cache_key)
864+
if result is not None:
865+
return result
866+
867+
non_vulnerable_versions = Package.objects.get_fixed_by_package_versions(
868+
self, fix=False
869+
).only_non_vulnerable()
870+
sorted_versions = self.sort_by_version(non_vulnerable_versions)
871+
872+
later_non_vulnerable_versions = [
873+
non_vuln_ver
874+
for non_vuln_ver in sorted_versions
875+
if self.version_class(non_vuln_ver.version) > self.current_version
876+
]
877+
878+
if later_non_vulnerable_versions:
879+
sorted_versions = self.sort_by_version(later_non_vulnerable_versions)
880+
next_non_vulnerable = sorted_versions[0]
881+
latest_non_vulnerable = sorted_versions[-1]
882+
cache.set(
883+
cache_key, (next_non_vulnerable, latest_non_vulnerable), timeout=3600
884+
)
885+
return next_non_vulnerable, latest_non_vulnerable
886+
887+
cache.set(cache_key, (None, None), timeout=3600)
888+
return None, None
889+
826890
@property
827891
def fixed_package_details(self):
828892
"""
@@ -928,15 +992,14 @@ class PackageRelatedVulnerabilityBase(models.Model):
928992
package = models.ForeignKey(
929993
Package,
930994
on_delete=models.CASCADE,
931-
# related_name="%(class)s_set", # Unique related_name per subclass
932995
)
933996

934997
vulnerability = models.ForeignKey(
935998
Vulnerability,
936999
on_delete=models.CASCADE,
937-
# related_name="%(class)s_set", # Unique related_name per subclass
9381000
)
9391001

1002+
# TODO: Fix the help text
9401003
created_by = models.CharField(
9411004
max_length=100,
9421005
blank=True,

0 commit comments

Comments
 (0)