diff --git a/vulnerabilities/api.py b/vulnerabilities/api.py index 9a21832b4..30ca4cb9f 100644 --- a/vulnerabilities/api.py +++ b/vulnerabilities/api.py @@ -28,6 +28,7 @@ from vulnerabilities.models import Vulnerability from vulnerabilities.models import VulnerabilityReference from vulnerabilities.models import VulnerabilitySeverity +from vulnerabilities.models import Weakness from vulnerabilities.models import get_purl_query_lookups from vulnerabilities.throttling import StaffUserRateThrottle @@ -121,6 +122,25 @@ class Meta: fields = ["url", "vulnerability_id", "summary", "references", "fixed_packages", "aliases"] +class WeaknessSerializer(serializers.HyperlinkedModelSerializer): + """ + Used for nesting inside weakness focused APIs. + """ + + class Meta: + model = Weakness + fields = ["cwe_id", "name", "description"] + + def to_representation(self, instance): + """ + Override to include 'weakness' only if it is not None. + """ + representation = super().to_representation(instance) + if instance.weakness is None: + return None + return representation + + class VulnerabilitySerializer(serializers.HyperlinkedModelSerializer): fixed_packages = MinimalPackageSerializer( many=True, source="filtered_fixed_packages", read_only=True @@ -129,6 +149,16 @@ class VulnerabilitySerializer(serializers.HyperlinkedModelSerializer): references = VulnerabilityReferenceSerializer(many=True, source="vulnerabilityreference_set") aliases = AliasSerializer(many=True, source="alias") + weaknesses = WeaknessSerializer(many=True) + + def to_representation(self, instance): + representation = super().to_representation(instance) + + # Exclude None values from the weaknesses list + weaknesses = representation.get("weaknesses", []) + representation["weaknesses"] = [weakness for weakness in weaknesses if weakness is not None] + + return representation class Meta: model = Vulnerability @@ -140,6 +170,7 @@ class Meta: "fixed_packages", "affected_packages", "references", + "weaknesses", ] @@ -491,11 +522,12 @@ def get_queryset(self): to a custom attribute `filtered_fixed_packages` """ return Vulnerability.objects.prefetch_related( + "weaknesses", Prefetch( "packages", queryset=self.get_fixed_packages_qs(), to_attr="filtered_fixed_packages", - ) + ), ) serializer_class = VulnerabilitySerializer diff --git a/vulnerabilities/models.py b/vulnerabilities/models.py index 47785f322..2ebf47f16 100644 --- a/vulnerabilities/models.py +++ b/vulnerabilities/models.py @@ -316,6 +316,9 @@ def description(self): """Return the weakness's description.""" return self.weakness.description if self.weakness else "" + def to_dict(self): + return {"cwe_id": self.cwe_id, "name": self.name, "description": self.description} + class VulnerabilityReferenceQuerySet(BaseQuerySet): def for_cpe(self): diff --git a/vulnerabilities/tests/test_api.py b/vulnerabilities/tests/test_api.py index a0283eac8..f94fe80cb 100644 --- a/vulnerabilities/tests/test_api.py +++ b/vulnerabilities/tests/test_api.py @@ -27,6 +27,7 @@ from vulnerabilities.models import Vulnerability from vulnerabilities.models import VulnerabilityReference from vulnerabilities.models import VulnerabilityRelatedReference +from vulnerabilities.models import Weakness BASE_DIR = os.path.dirname(os.path.abspath(__file__)) TEST_DATA = os.path.join(BASE_DIR, "test_data") @@ -197,6 +198,12 @@ def setUp(self): PackageRelatedVulnerability.objects.create( package=pkg, vulnerability=self.vulnerability, fix=True ) + self.weaknesses = Weakness.objects.create(cwe_id=119) + self.weaknesses.vulnerabilities.add(self.vulnerability) + self.invalid_weaknesses = Weakness.objects.create( + cwe_id=10000 + ) # cwe not present in weaknesses_db + self.invalid_weaknesses.vulnerabilities.add(self.vulnerability) def test_api_status(self): response = self.csrf_client.get("/api/vulnerabilities/") @@ -232,6 +239,13 @@ def test_api_with_single_vulnerability(self): ], "affected_packages": [], "references": [], + "weaknesses": [ + { + "cwe_id": 119, + "name": "Improper Restriction of Operations within the Bounds of a Memory Buffer", + "description": "The software performs operations on a memory buffer, but it can read from or write to a memory location that is outside of the intended boundary of the buffer.", + }, + ], } def test_api_with_single_vulnerability_with_filters(self): @@ -253,6 +267,13 @@ def test_api_with_single_vulnerability_with_filters(self): ], "affected_packages": [], "references": [], + "weaknesses": [ + { + "cwe_id": 119, + "name": "Improper Restriction of Operations within the Bounds of a Memory Buffer", + "description": "The software performs operations on a memory buffer, but it can read from or write to a memory location that is outside of the intended boundary of the buffer.", + }, + ], } diff --git a/vulnerabilities/tests/test_data/suse_oval/suse-oval-CVE-2008-5679-expected.json b/vulnerabilities/tests/test_data/suse_oval/suse-oval-CVE-2008-5679-expected.json index 93469b4a0..471622f96 100644 --- a/vulnerabilities/tests/test_data/suse_oval/suse-oval-CVE-2008-5679-expected.json +++ b/vulnerabilities/tests/test_data/suse_oval/suse-oval-CVE-2008-5679-expected.json @@ -10,9 +10,9 @@ "type": "rpm", "namespace": "opensuse", "name": "opera", - "version": null, - "qualifiers": null, - "subpath": null + "version": "", + "qualifiers": "", + "subpath": "" }, "affected_version_range": "vers:rpm/<9.63-1.1", "fixed_version": null @@ -29,4 +29,4 @@ "weaknesses": [], "url": "" } -] +] \ No newline at end of file