diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 8446d2e9a..0e2c99208 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -55,6 +55,8 @@ Version v30.0.0 - Paginated initial listings to display a small number of records and provided page per size with a maximum limit of 100 records per page. +- Add Fixed packages in vulnerabilities details in packages endpoint. + Other: - we dropped calver to use a plain semver. diff --git a/vulnerabilities/api.py b/vulnerabilities/api.py index 3453427c6..4798cf3e4 100644 --- a/vulnerabilities/api.py +++ b/vulnerabilities/api.py @@ -56,11 +56,15 @@ class VulnSerializerRefsAndSummary(serializers.HyperlinkedModelSerializer): Used for nesting inside package focused APIs. """ + fixed_packages = MinimalPackageSerializer( + many=True, source="filtered_fixed_packages", read_only=True + ) + references = VulnerabilityReferenceSerializer(many=True, source="vulnerabilityreference_set") class Meta: model = Vulnerability - fields = ["url", "vulnerability_id", "summary", "references"] + fields = ["url", "vulnerability_id", "summary", "references", "fixed_packages"] class MinimalVulnerabilitySerializer(serializers.HyperlinkedModelSerializer): @@ -73,21 +77,6 @@ class Meta: fields = ["url", "vulnerability_id"] -class PackageSerializerFixedVulns(serializers.HyperlinkedModelSerializer): - """ - Used for nesting inside vulnerability focused APIs. - """ - - purl = serializers.CharField(source="package_url") - fixing_vulnerabilities = MinimalVulnerabilitySerializer( - many=True, source="resolved_to", read_only=True - ) - - class Meta: - model = Package - fields = ["url", "purl", "fixing_vulnerabilities"] - - class AliasSerializer(serializers.HyperlinkedModelSerializer): """ Used for nesting inside package focused APIs. @@ -128,13 +117,56 @@ def to_representation(self, instance): return data purl = serializers.CharField(source="package_url") - affected_by_vulnerabilities = VulnSerializerRefsAndSummary( - many=True, source="vulnerable_to", read_only=True - ) - fixing_vulnerabilities = VulnSerializerRefsAndSummary( - many=True, source="resolved_to", read_only=True - ) - fixed_packages = PackageSerializerFixedVulns(many=True, read_only=True) + + affected_by_vulnerabilities = serializers.SerializerMethodField("get_affected_vulnerabilities") + + fixing_vulnerabilities = serializers.SerializerMethodField("get_fixed_vulnerabilities") + + def get_fixed_packages(self, package): + """ + Return a queryset of packages that fixes a vulnerability in the given `package`. + """ + return Package.objects.filter( + name=package.name, + namespace=package.namespace, + type=package.type, + qualifiers=package.qualifiers, + subpath=package.subpath, + packagerelatedvulnerability__fix=True, + ).distinct() + + def get_vulnerabilities_for_a_package(self, package, fix): + """ + Return a queryset of vulnerabilities related to the given `package`. + Return vulnerabilities that affects the `package` if given `fix` flag is False, + otherwise return vulnerabilities fixed by the `package`. + """ + fixed_packages = self.get_fixed_packages(package=package) + qs = package.vulnerabilities.filter(packagerelatedvulnerability__fix=fix) + qs = qs.prefetch_related( + Prefetch( + "packages", + queryset=fixed_packages, + to_attr="filtered_fixed_packages", + ) + ) + return VulnSerializerRefsAndSummary( + instance=qs, + many=True, + context={"request": self.context["request"]}, + ).data + + def get_fixed_vulnerabilities(self, package): + """ + Return a queryset of vulnerabilities fixed in the given `package`. + """ + return self.get_vulnerabilities_for_a_package(package=package, fix=True) + + def get_affected_vulnerabilities(self, package): + """ + Return a queryset of vulnerabilities that affects the given `package`. + """ + return self.get_vulnerabilities_for_a_package(package=package, fix=False) class Meta: model = Package @@ -148,7 +180,6 @@ class Meta: "qualifiers", "subpath", "affected_by_vulnerabilities", - "fixed_packages", "fixing_vulnerabilities", ] diff --git a/vulnerabilities/tests/test_fix_api.py b/vulnerabilities/tests/test_fix_api.py index 2d2618dc5..7ef3d0a06 100644 --- a/vulnerabilities/tests/test_fix_api.py +++ b/vulnerabilities/tests/test_fix_api.py @@ -139,21 +139,8 @@ def test_api_with_single_vulnerability_and_fixed_package(self): "namespace": "nginx", "name": "test", "version": "11", - "unresolved_vulnerabilities": [], "qualifiers": {}, "subpath": "", - "fixed_packages": [ - { - "url": f"http://testserver/api/packages/{self.package.id}", - "purl": "pkg:generic/nginx/test@11", - "fixing_vulnerabilities": [ - { - "url": f"http://testserver/api/vulnerabilities/{self.vuln.id}", - "vulnerability_id": f"VULCOID-{int_to_base36(self.vuln.id).upper()}", - } - ], - } - ], "affected_by_vulnerabilities": [], "fixing_vulnerabilities": [ { @@ -161,8 +148,15 @@ def test_api_with_single_vulnerability_and_fixed_package(self): "vulnerability_id": f"VULCOID-{int_to_base36(self.vuln.id).upper()}", "summary": "test-vuln", "references": [], - } + "fixed_packages": [ + { + "url": f"http://testserver/api/packages/{self.package.id}", + "purl": "pkg:generic/nginx/test@11", + } + ], + }, ], + "unresolved_vulnerabilities": [], } def test_api_with_single_vulnerability_and_vulnerable_package(self): @@ -174,37 +168,37 @@ def test_api_with_single_vulnerability_and_vulnerable_package(self): "namespace": "nginx", "name": "test", "version": "9", - "unresolved_vulnerabilities": [ + "qualifiers": {}, + "subpath": "", + "affected_by_vulnerabilities": [ { "url": f"http://testserver/api/vulnerabilities/{self.vuln.id}", "vulnerability_id": f"VULCOID-{int_to_base36(self.vuln.id).upper()}", "summary": "test-vuln", "references": [], - } - ], - "qualifiers": {}, - "subpath": "", - "fixed_packages": [ - { - "url": f"http://testserver/api/packages/{self.package.id}", - "purl": "pkg:generic/nginx/test@11", - "fixing_vulnerabilities": [ + "fixed_packages": [ { - "url": f"http://testserver/api/vulnerabilities/{self.vuln.id}", - "vulnerability_id": f"VULCOID-{int_to_base36(self.vuln.id).upper()}", + "url": f"http://testserver/api/packages/{self.package.id}", + "purl": "pkg:generic/nginx/test@11", } ], } ], - "affected_by_vulnerabilities": [ + "fixing_vulnerabilities": [], + "unresolved_vulnerabilities": [ { "url": f"http://testserver/api/vulnerabilities/{self.vuln.id}", "vulnerability_id": f"VULCOID-{int_to_base36(self.vuln.id).upper()}", "summary": "test-vuln", "references": [], + "fixed_packages": [ + { + "url": f"http://testserver/api/packages/{self.package.id}", + "purl": "pkg:generic/nginx/test@11", + } + ], } ], - "fixing_vulnerabilities": [], }