Skip to content

Commit d567bb0

Browse files
authored
Merge branch 'main' into 1541-fix-importer-crash
2 parents be28af3 + cb38a58 commit d567bb0

23 files changed

+2074
-265
lines changed

CHANGELOG.rst

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,20 @@
11
Release notes
22
=============
33

4+
Version v34.0.0rc5
5+
-------------------
6+
7+
- Add safetydb importer.
8+
- Add missing width setting for the table in the vulnerability details UI.
9+
- Add KEV support.
10+
- Add UI template for API.
11+
- Use VersionRange.normalize to compare advisory.
12+
- Use integer column to display score.
13+
- Add support for CVSSv4 & SSVC and import the data using vulnrichment.
14+
- Add support for reference_type in the API.
15+
- Add API improvements for the package endpoint.
16+
17+
418
Version v34.0.0rc4
519
-------------------
620

setup.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ dev =
118118
commoncode
119119
# debug
120120
django-debug-toolbar
121+
pyinstrument
121122

122123
[options.entry_points]
123124
console_scripts =

vulnerabilities/api.py

Lines changed: 58 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ def get_vulnerability(self, vuln):
108108

109109
purl = serializers.CharField(source="package_url")
110110

111+
is_vulnerable = serializers.BooleanField()
112+
111113
class Meta:
112114
model = Package
113115
fields = ["url", "purl", "is_vulnerable", "affected_by_vulnerabilities"]
@@ -248,21 +250,27 @@ def get_latest_non_vulnerable(self, package):
248250

249251
affected_by_vulnerabilities = serializers.SerializerMethodField("get_affected_vulnerabilities")
250252

251-
fixing_vulnerabilities = serializers.SerializerMethodField("get_fixed_vulnerabilities")
253+
fixing_vulnerabilities = serializers.SerializerMethodField("get_fixing_vulnerabilities")
254+
255+
is_vulnerable = serializers.BooleanField()
252256

253257
def get_fixed_packages(self, package):
254258
"""
255259
Return a queryset of all packages that fix a vulnerability with
256260
same type, namespace, name, subpath and qualifiers of the `package`
257261
"""
258-
return Package.objects.filter(
259-
name=package.name,
260-
namespace=package.namespace,
261-
type=package.type,
262-
qualifiers=package.qualifiers,
263-
subpath=package.subpath,
264-
packagerelatedvulnerability__fix=True,
265-
).distinct()
262+
return (
263+
Package.objects.filter(
264+
name=package.name,
265+
namespace=package.namespace,
266+
type=package.type,
267+
qualifiers=package.qualifiers,
268+
subpath=package.subpath,
269+
packagerelatedvulnerability__fix=True,
270+
)
271+
.with_is_vulnerable()
272+
.distinct()
273+
)
266274

267275
def get_vulnerabilities_for_a_package(self, package, fix) -> dict:
268276
"""
@@ -285,7 +293,7 @@ def get_vulnerabilities_for_a_package(self, package, fix) -> dict:
285293
context={"request": self.context["request"]},
286294
).data
287295

288-
def get_fixed_vulnerabilities(self, package) -> dict:
296+
def get_fixing_vulnerabilities(self, package) -> dict:
289297
"""
290298
Return a mapping of vulnerabilities fixed in the given `package`.
291299
"""
@@ -322,12 +330,15 @@ class Meta:
322330
"version",
323331
"qualifiers",
324332
"subpath",
333+
"is_vulnerable",
325334
"next_non_vulnerable_version",
326335
"latest_non_vulnerable_version",
327336
"affected_by_vulnerabilities",
328337
"fixing_vulnerabilities",
329338
]
330339

340+
is_vulnerable = serializers.BooleanField()
341+
331342

332343
class PackageFilterSet(filters.FilterSet):
333344
purl = filters.CharFilter(method="filter_purl")
@@ -390,6 +401,9 @@ class PackageViewSet(viewsets.ReadOnlyModelViewSet):
390401
filterset_class = PackageFilterSet
391402
throttle_classes = [StaffUserRateThrottle, AnonRateThrottle]
392403

404+
def get_queryset(self):
405+
return super().get_queryset().with_is_vulnerable()
406+
393407
@extend_schema(
394408
request=PackageBulkSearchRequestSerializer,
395409
responses={200: PackageSerializer(many=True)},
@@ -436,6 +450,7 @@ def bulk_search(self, request):
436450
Package.objects.filter(plain_package_url__in=plain_purls)
437451
.order_by("plain_package_url")
438452
.distinct("plain_package_url")
453+
.with_is_vulnerable()
439454
)
440455

441456
if not purl_only:
@@ -449,7 +464,7 @@ def bulk_search(self, request):
449464
vulnerable_purls = [str(package.plain_package_url) for package in vulnerable_purls]
450465
return Response(data=vulnerable_purls)
451466

452-
query = Package.objects.filter(package_url__in=purls).distinct()
467+
query = Package.objects.filter(package_url__in=purls).distinct().with_is_vulnerable()
453468

454469
if not purl_only:
455470
return Response(PackageSerializer(query, many=True, context={"request": request}).data)
@@ -463,7 +478,9 @@ def all(self, request):
463478
"""
464479
Return the Package URLs of all packages known to be vulnerable.
465480
"""
466-
vulnerable_packages = Package.objects.vulnerable().only("package_url").distinct()
481+
vulnerable_packages = (
482+
Package.objects.vulnerable().only("package_url").distinct().with_is_vulnerable()
483+
)
467484
vulnerable_purls = [str(package.package_url) for package in vulnerable_packages]
468485
return Response(vulnerable_purls)
469486

@@ -494,11 +511,8 @@ def lookup(self, request):
494511
validated_data = serializer.validated_data
495512
purl = validated_data.get("purl")
496513

497-
return Response(
498-
PackageSerializer(
499-
Package.objects.for_purls([purl]), many=True, context={"request": request}
500-
).data
501-
)
514+
qs = self.get_queryset().for_purls([purl]).with_is_vulnerable()
515+
return Response(PackageSerializer(qs, many=True, context={"request": request}).data)
502516

503517
@extend_schema(
504518
request=PackageurlListSerializer,
@@ -529,7 +543,7 @@ def bulk_lookup(self, request):
529543

530544
return Response(
531545
PackageSerializer(
532-
Package.objects.for_purls(purls),
546+
Package.objects.for_purls(purls).with_is_vulnerable(),
533547
many=True,
534548
context={"request": request},
535549
).data
@@ -547,33 +561,47 @@ class VulnerabilityViewSet(viewsets.ReadOnlyModelViewSet):
547561
Lookup for vulnerabilities affecting packages.
548562
"""
549563

564+
queryset = Vulnerability.objects.all()
565+
550566
def get_fixed_packages_qs(self):
551567
"""
552568
Filter the packages that fixes a vulnerability
553569
on fields like name, namespace and type.
554570
"""
555-
package_filter_data = {"packagerelatedvulnerability__fix": True}
571+
return self.get_packages_qs().filter(packagerelatedvulnerability__fix=True)
556572

573+
def get_packages_qs(self):
574+
"""
575+
Filter the packages on type, namespace and name.
576+
"""
557577
query_params = self.request.query_params
558-
for field_name in ["name", "namespace", "type"]:
559-
value = query_params.get(field_name)
560-
if value:
578+
package_filter_data = {}
579+
for field_name in ("type", "namespace", "name"):
580+
if value := query_params.get(field_name):
561581
package_filter_data[field_name] = value
562582

563-
return PackageFilterSet(package_filter_data).qs
583+
return PackageFilterSet(package_filter_data).qs.with_is_vulnerable()
564584

565585
def get_queryset(self):
566586
"""
567587
Assign filtered packages queryset from `get_fixed_packages_qs`
568588
to a custom attribute `filtered_fixed_packages`
569589
"""
570-
return Vulnerability.objects.prefetch_related(
571-
"weaknesses",
572-
Prefetch(
573-
"packages",
574-
queryset=self.get_fixed_packages_qs(),
575-
to_attr="filtered_fixed_packages",
576-
),
590+
return (
591+
super()
592+
.get_queryset()
593+
.prefetch_related(
594+
Prefetch(
595+
"packages",
596+
queryset=self.get_packages_qs(),
597+
),
598+
"weaknesses",
599+
Prefetch(
600+
"packages",
601+
queryset=self.get_fixed_packages_qs(),
602+
to_attr="filtered_fixed_packages",
603+
),
604+
)
577605
)
578606

579607
serializer_class = VulnerabilitySerializer

vulnerabilities/importers/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
from vulnerabilities.importers import suse_scores
3939
from vulnerabilities.importers import ubuntu
4040
from vulnerabilities.importers import ubuntu_usn
41+
from vulnerabilities.importers import vulnrichment
4142
from vulnerabilities.importers import xen
4243

4344
IMPORTERS_REGISTRY = [
@@ -73,6 +74,7 @@
7374
ruby.RubyImporter,
7475
github_osv.GithubOSVImporter,
7576
epss.EPSSImporter,
77+
vulnrichment.VulnrichImporter,
7678
]
7779

7880
IMPORTERS_REGISTRY = {x.qualified_name: x for x in IMPORTERS_REGISTRY}

vulnerabilities/importers/nvd.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,16 @@ def severities(self):
163163
"""
164164
severities = []
165165
impact = self.cve_item.get("impact") or {}
166+
base_metric_v4 = impact.get("baseMetricV4") or {}
167+
if base_metric_v4:
168+
cvss_v4 = base_metric_v4.get("cvssV4") or {}
169+
vs = VulnerabilitySeverity(
170+
system=severity_systems.CVSSV4,
171+
value=str(cvss_v4.get("baseScore") or ""),
172+
scoring_elements=str(cvss_v4.get("vectorString") or ""),
173+
)
174+
severities.append(vs)
175+
166176
base_metric_v3 = impact.get("baseMetricV3") or {}
167177
if base_metric_v3:
168178
cvss_v3 = get_item(base_metric_v3, "cvssV3")

0 commit comments

Comments
 (0)