Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 36 additions & 10 deletions vulnerabilities/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.reverse import reverse
from rest_framework.throttling import AnonRateThrottle
from rest_framework.throttling import UserRateThrottle

Expand Down Expand Up @@ -48,7 +49,32 @@ class Meta:
fields = ["reference_url", "reference_id", "scores", "url"]


class MinimalPackageSerializer(serializers.HyperlinkedModelSerializer):
class BaseResourceSerializer(serializers.HyperlinkedModelSerializer):
"""
Base serializer containing common methods.
"""

def get_fields(self):
fields = super().get_fields()
fields["resource_url"] = serializers.SerializerMethodField(method_name="get_resource_url")
return fields

def get_resource_url(self, instance):
"""
Return the instance fully qualified URL including the schema and domain.

Usage:
resource_url = serializers.SerializerMethodField()
"""
resource_url = instance.get_absolute_url()

if request := self.context.get("request", None):
return request.build_absolute_uri(location=resource_url)

return resource_url


class MinimalPackageSerializer(BaseResourceSerializer):
"""
Used for nesting inside vulnerability focused APIs.
"""
Expand Down Expand Up @@ -79,7 +105,7 @@ class Meta:
fields = ["url", "purl", "is_vulnerable", "affected_by_vulnerabilities"]


class MinimalVulnerabilitySerializer(serializers.HyperlinkedModelSerializer):
class MinimalVulnerabilitySerializer(BaseResourceSerializer):
"""
Lookup vulnerabilities by aliases (such as a CVE).
"""
Expand All @@ -99,7 +125,7 @@ class Meta:
fields = ["alias"]


class VulnSerializerRefsAndSummary(serializers.HyperlinkedModelSerializer):
class VulnSerializerRefsAndSummary(BaseResourceSerializer):
"""
Lookup vulnerabilities references by aliases (such as a CVE).
"""
Expand Down Expand Up @@ -141,7 +167,7 @@ def to_representation(self, instance):
return representation


class VulnerabilitySerializer(serializers.HyperlinkedModelSerializer):
class VulnerabilitySerializer(BaseResourceSerializer):
fixed_packages = MinimalPackageSerializer(
many=True, source="filtered_fixed_packages", read_only=True
)
Expand All @@ -152,13 +178,12 @@ class VulnerabilitySerializer(serializers.HyperlinkedModelSerializer):
weaknesses = WeaknessSerializer(many=True)

def to_representation(self, instance):
representation = super().to_representation(instance)
data = 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]
weaknesses = data.get("weaknesses", [])
data["weaknesses"] = [weakness for weakness in weaknesses if weakness is not None]

return representation
return data

class Meta:
model = Vulnerability
Expand All @@ -174,14 +199,15 @@ class Meta:
]


class PackageSerializer(serializers.HyperlinkedModelSerializer):
class PackageSerializer(BaseResourceSerializer):
"""
Lookup software package using Package URLs
"""

def to_representation(self, instance):
data = super().to_representation(instance)
data["qualifiers"] = normalize_qualifiers(data["qualifiers"], encode=False)

return data

next_non_vulnerable_version = serializers.SerializerMethodField("get_next_non_vulnerable")
Expand Down
20 changes: 20 additions & 0 deletions vulnerabilities/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,18 @@ def get_absolute_url(self):
"""
return reverse("vulnerability_details", args=[self.vulnerability_id])

def get_details_url(self, request):
"""
Return this Package details URL.
"""
from rest_framework.reverse import reverse

return reverse(
"vulnerability_details",
kwargs={"vulnerability_id": self.vulnerability_id},
request=request,
)

def get_related_cpes(self):
"""
Return a list of CPE strings of this vulnerability.
Expand Down Expand Up @@ -633,6 +645,14 @@ def get_absolute_url(self):
"""
return reverse("package_details", args=[self.purl])

def get_details_url(self, request):
"""
Return this Package details URL.
"""
from rest_framework.reverse import reverse

return reverse("package_details", kwargs={"purl": self.purl}, request=request)

def sort_by_version(self, packages):
"""
Return a list of `packages` sorted by version.
Expand Down
139 changes: 101 additions & 38 deletions vulnerabilities/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import json
import os
from collections import OrderedDict
from urllib.parse import quote

from django.test import TestCase
Expand Down Expand Up @@ -223,18 +224,21 @@ def test_api_with_single_vulnerability(self):
"vulnerability_id": self.vulnerability.vulnerability_id,
"summary": "test",
"aliases": [],
"resource_url": f"http://testserver/vulnerabilities/{self.vulnerability.vulnerability_id}",
"fixed_packages": [
{
"url": f"http://testserver/api/packages/{self.pkg2.id}",
"purl": "pkg:deb/flask@0.1.2",
"is_vulnerable": False,
"affected_by_vulnerabilities": [],
"resource_url": f"http://testserver/packages/{self.pkg2.purl}",
},
{
"url": f"http://testserver/api/packages/{self.pkg1.id}",
"purl": "pkg:pypi/flask@0.1.2",
"is_vulnerable": False,
"affected_by_vulnerabilities": [],
"resource_url": f"http://testserver/packages/{self.pkg1.purl}",
},
],
"affected_packages": [],
Expand All @@ -257,11 +261,13 @@ def test_api_with_single_vulnerability_with_filters(self):
"vulnerability_id": self.vulnerability.vulnerability_id,
"summary": "test",
"aliases": [],
"resource_url": f"http://testserver/vulnerabilities/{self.vulnerability.vulnerability_id}",
"fixed_packages": [
{
"url": f"http://testserver/api/packages/{self.pkg1.id}",
"purl": "pkg:pypi/flask@0.1.2",
"is_vulnerable": False,
"resource_url": f"http://testserver/packages/{self.pkg1.purl}",
"affected_by_vulnerabilities": [],
},
],
Expand Down Expand Up @@ -443,49 +449,106 @@ def test_api_with_lesser_and_greater_fixed_by_packages(self):
"next_non_vulnerable_version": "2.14.0-rc1",
"latest_non_vulnerable_version": "2.14.0-rc1",
"affected_by_vulnerabilities": [
{
"url": f"http://testserver/api/vulnerabilities/{self.vuln_VCID_2nyb_8rwu_aaag.id}",
"vulnerability_id": "VCID-2nyb-8rwu-aaag",
"summary": "This is VCID-2nyb-8rwu-aaag",
"references": [],
"fixed_packages": [
{
"url": f"http://testserver/api/packages/{self.package_maven_jackson_databind_2_13_2.id}",
"purl": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.2",
"is_vulnerable": True,
"affected_by_vulnerabilities": [
{"vulnerability": "VCID-gqhw-ngh8-aaap"}
OrderedDict(
[
(
"url",
f"http://testserver/api/vulnerabilities/{self.vuln_VCID_2nyb_8rwu_aaag.id}",
),
("vulnerability_id", "VCID-2nyb-8rwu-aaag"),
("summary", "This is VCID-2nyb-8rwu-aaag"),
("references", []),
(
"fixed_packages",
[
OrderedDict(
[
(
"url",
f"http://testserver/api/packages/{self.package_maven_jackson_databind_2_13_2.id}",
),
(
"purl",
"pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.2",
),
("is_vulnerable", True),
(
"affected_by_vulnerabilities",
[{"vulnerability": "VCID-gqhw-ngh8-aaap"}],
),
(
"resource_url",
"http://testserver/packages/pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.2",
),
]
)
],
}
],
"aliases": ["CVE-2020-36518", "GHSA-57j2-w4cx-62h2"],
}
),
("aliases", ["CVE-2020-36518", "GHSA-57j2-w4cx-62h2"]),
("resource_url", "http://testserver/vulnerabilities/VCID-2nyb-8rwu-aaag"),
]
)
],
"fixing_vulnerabilities": [
{
"url": f"http://testserver/api/vulnerabilities/{self.vuln_VCID_ftmk_wbwx_aaar.id}",
"vulnerability_id": "VCID-ftmk-wbwx-aaar",
"summary": "This is VCID-ftmk-wbwx-aaar",
"references": [],
"fixed_packages": [
{
"url": f"http://testserver/api/packages/{self.package_maven_jackson_databind_2_12_6.id}",
"purl": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.12.6",
"is_vulnerable": False,
"affected_by_vulnerabilities": [],
},
{
"url": f"http://testserver/api/packages/{self.package_maven_jackson_databind_2_13_1.id}",
"purl": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.1",
"is_vulnerable": True,
"affected_by_vulnerabilities": [
{"vulnerability": "VCID-2nyb-8rwu-aaag"}
OrderedDict(
[
(
"url",
f"http://testserver/api/vulnerabilities/{self.vuln_VCID_ftmk_wbwx_aaar.id}",
),
("vulnerability_id", "VCID-ftmk-wbwx-aaar"),
("summary", "This is VCID-ftmk-wbwx-aaar"),
("references", []),
(
"fixed_packages",
[
OrderedDict(
[
(
"url",
f"http://testserver/api/packages/{self.package_maven_jackson_databind_2_12_6.id}",
),
(
"purl",
"pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.12.6",
),
("is_vulnerable", False),
("affected_by_vulnerabilities", []),
(
"resource_url",
"http://testserver/packages/pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.12.6",
),
]
),
OrderedDict(
[
(
"url",
f"http://testserver/api/packages/{self.package_maven_jackson_databind_2_13_1.id}",
),
(
"purl",
"pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.1",
),
("is_vulnerable", True),
(
"affected_by_vulnerabilities",
[{"vulnerability": "VCID-2nyb-8rwu-aaag"}],
),
(
"resource_url",
"http://testserver/packages/pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.1",
),
]
),
],
},
],
"aliases": ["CVE-2021-46877", "GHSA-3x8x-79m2-3w2w"],
},
),
("aliases", ["CVE-2021-46877", "GHSA-3x8x-79m2-3w2w"]),
("resource_url", "http://testserver/vulnerabilities/VCID-ftmk-wbwx-aaar"),
]
)
],
"resource_url": "http://testserver/packages/pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.1",
}

assert response == expected_response
Expand Down