Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add fixed packages in vulnerabilities details in packages endpoint. #831

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
2 changes: 2 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
80 changes: 56 additions & 24 deletions vulnerabilities/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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.
Expand Down Expand Up @@ -128,13 +117,57 @@ 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 all packages that fixes a vulnerability with
same type, namespace, name, subpath and qualifiers of the `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
Expand All @@ -148,7 +181,6 @@ class Meta:
"qualifiers",
"subpath",
"affected_by_vulnerabilities",
"fixed_packages",
"fixing_vulnerabilities",
]

Expand Down
50 changes: 22 additions & 28 deletions vulnerabilities/tests/test_fix_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,30 +139,24 @@ 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": [
{
"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",
}
],
},
],
"unresolved_vulnerabilities": [],
}

def test_api_with_single_vulnerability_and_vulnerable_package(self):
Expand All @@ -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": [],
}


Expand Down