Skip to content
Open
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
40 changes: 39 additions & 1 deletion vulnerabilities/api_v3.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@
from urllib.parse import urlencode

from django.contrib.postgres.aggregates import ArrayAgg
from django.contrib.postgres.aggregates import JSONBAgg
from django.db.models import Exists
from django.db.models import Max
from django.db.models import OuterRef
from django.db.models import Prefetch
from django.db.models import Q
from django.db.models.functions import JSONObject
from django_filters import rest_framework as filters
from drf_spectacular.utils import extend_schema
from packageurl import PackageURL
Expand Down Expand Up @@ -479,7 +481,23 @@ def get_affected_advisories_bulk(packages, max_advisories, base_url):
"impacted_package__fixed_by_packages__package_url",
distinct=True,
filter=Q(impacted_package__fixed_by_packages__package_url__isnull=False),
)
),
introduced_patches=JSONBAgg(
JSONObject(
commit_hash="impacted_package__introduced_by_package_commit_patches__commit_hash",
vcs_url="impacted_package__introduced_by_package_commit_patches__vcs_url",
),
distinct=True,
filter=Q(impacted_package__introduced_by_package_commit_patches__isnull=False),
),
fixed_patches=JSONBAgg(
JSONObject(
commit_hash="impacted_package__fixed_by_package_commit_patches__commit_hash",
vcs_url="impacted_package__fixed_by_package_commit_patches__vcs_url",
),
distinct=True,
filter=Q(impacted_package__fixed_by_package_commit_patches__isnull=False),
),
)
)

Expand All @@ -492,6 +510,20 @@ def get_affected_advisories_bulk(packages, max_advisories, base_url):
for row in impacts
}

introduced_patches_map = {
(row["package_id"], row["impacted_package__advisory_id"]): [
p for p in (row["introduced_patches"] or []) if p and p.get("commit_hash")
]
for row in impacts
}

fixed_patches_map = {
(row["package_id"], row["impacted_package__advisory_id"]): [
p for p in (row["fixed_patches"] or []) if p and p.get("commit_hash")
]
for row in impacts
}

# Package types with multiple importers

advisory_sets = list(
Expand Down Expand Up @@ -647,6 +679,8 @@ def get_affected_advisories_bulk(packages, max_advisories, base_url):
"exploitability": exploitability,
"risk_score": risk_score,
"fixed_by_packages": fixed_by_packages,
"introduced_in_patch": introduced_patches_map.get((package.id, primary.id), []),
"fixed_in_patch": fixed_patches_map.get((package.id, primary.id), []),
"ssvc_trees": adv.ssvc_trees,
"resource_url": resource_url,
}
Expand Down Expand Up @@ -742,6 +776,10 @@ def get_affected_advisories_bulk(packages, max_advisories, base_url):
"exploitability": advisory.exploitability,
"risk_score": advisory.risk_score,
"fixed_by_packages": fixed_by_packages,
"introduced_in_patch": introduced_patches_map.get(
(package.id, advisory_id), []
),
"fixed_in_patch": fixed_patches_map.get((package.id, advisory_id), []),
"ssvc_trees": [
{
"vector": ssvc.vector,
Expand Down
46 changes: 44 additions & 2 deletions vulnerabilities/templates/advisory_detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,16 @@
</a>
</li>
{% endif %}


<li data-tab="patch-url">
<a>
<span>
{% with pcp_length=package_commit_patches|length %}
Patches: ({{ advisory.patches.count|add:pcp_length }})
{% endwith %}
</span>
</a>
</li>
<!-- <li data-tab="history">
<a>
<span>
Expand Down Expand Up @@ -184,6 +193,18 @@
</a>
</td>
</tr>
<tr>
<td class="two-col-left"
data-tooltip="Risk expressed as a number ranging from 0 to 10. It is calculated by multiplying
the weighted severity and exploitability values, capped at a maximum of 10.
"
>Introduced and Fixed Package Commit Patches</td>
<td class="two-col-right wrap-strings">
<a href="/advisories/commits/{{ advisory.avid }}">
Package Commit Patches Details
</a>
</td>
</tr>
</tbody>
</table>
<div class="has-text-weight-bold tab-nested-div ml-1 mb-1 mt-6">
Expand Down Expand Up @@ -436,7 +457,6 @@
</tr>
{% endfor %}
</div>


<div class="tab-div content" data-content="epss">
{% if epss_data %}
Expand Down Expand Up @@ -503,6 +523,28 @@
{% endif %}
</div>

<div class="tab-div content" data-content="patch-url">
<table class="table is-bordered is-striped is-narrow is-hoverable is-fullwidth">
<thead>
<tr>
<th style="width: 250px;"> Patch URL </th>
</tr>
</thead>
{% for patch in patches %}
<tr>
<td class="wrap-strings"><a href="{{ patch.patch_url }}" target="_blank">{{ patch.patch_url }}<i
class="fa fa-external-link fa_link_custom"></i></a></td>
</tr>
{% empty %}
<tr>
<td colspan="2">
There are no known patches.
</td>
</tr>
{% endfor %}
</table>
</div>

<div class="tab-div content" data-content="severities-vectors">
{% for severity_vector in severity_vectors %}
{% if severity_vector.vector.version == '2.0' %}
Expand Down
75 changes: 75 additions & 0 deletions vulnerabilities/templates/advisory_package_commit_details.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
{% extends "base.html" %}
{% load humanize %}
{% load widget_tweaks %}
{% load static %}
{% load show_cvss %}
{% load url_filters %}

{% block title %}
VulnerableCode Advisory Package Commit Patch Details - {{ advisoryv2.advisory_id }}
{% endblock %}

{% block content %}

{% if advisoryv2 %}
<section class="section pt-4">
<div class="details-container">
<article class="panel is-info panel-header-only">
<div class="panel-heading py-2 is-size-6">
Introduce and Fixing Package Commit Patch details for Advisory:
<span class="tag is-white custom">
{{ advisoryv2.advisory_id }}
</span>
</div>
</article>

<div id="tab-content">
<table class="table vcio-table width-100-pct mt-2">
<thead>
<tr>
<th style="width: 50%;">Introduced in</th>
<th>Fixed by</th>
</tr>
</thead>
<tbody>
{% for impact in advisoryv2.impacted_packages.all %}
{% for pkg_commit_patch in impact.introduced_by_package_commit_patches.all %}
<tr>
<td>
<a href="{{ pkg_commit_patch.vcs_url }}" target="_self">
{{ pkg_commit_patch.base_purl }}@{{ pkg_commit_patch.commit_hash }}
</a>
</td>
<td></td>
</tr>
{% endfor %}

{% for pkg_commit_patch in impact.fixed_by_package_commit_patches.all %}
<tr>
<td></td>
<td>
<a href="{{ pkg_commit_patch.vcs_url }}" target="_self">
{{ impact.base_purl }}@{{ pkg_commit_patch.commit_hash }}
</a>
</td>
</tr>
{% endfor %}

{% empty %}
<tr>
<td colspan="2">
This vulnerability is not known to affect any package commits.
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>

</div>
</section>
{% endif %}

<script src="{% static 'js/main.js' %}" crossorigin="anonymous"></script>

{% endblock %}
87 changes: 87 additions & 0 deletions vulnerabilities/tests/test_api_v3.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@
from univers.version_range import PypiVersionRange

from vulnerabilities.importer import AdvisoryDataV2
from vulnerabilities.importer import AffectedPackageV2
from vulnerabilities.importer import PackageCommitPatchData
from vulnerabilities.models import AdvisorySet
from vulnerabilities.models import AdvisorySetMember
from vulnerabilities.models import ImpactedPackage
from vulnerabilities.models import ImpactedPackageAffecting
from vulnerabilities.models import PackageV2
from vulnerabilities.pipes.advisory import insert_advisory_v2
from vulnerabilities.tests.pipelines import TestLogger
Expand Down Expand Up @@ -254,3 +260,84 @@ def test_get_all_vulnerable_purls(self):
results = response.data["results"]
self.assertEqual(len(results), 100)
self.assertIn("next", response.data)


class PackageCommitPatchTests(APITestCase):
def setUp(self):
self.advisory = AdvisoryDataV2(
advisory_id="AVID-123",
aliases=[],
affected_packages=[
AffectedPackageV2(
package=PackageURL(type="pypi", name="sample"),
affected_version_range=PypiVersionRange.from_string("vers:pypi/=1.0.0"),
introduced_by_commit_patches=[
PackageCommitPatchData(
vcs_url="https://github.com/aboutcode-org/sample",
commit_hash="06580c7f99c6fde7bcf18e30bdcc61f081430957",
)
],
fixed_by_commit_patches=[
PackageCommitPatchData(
vcs_url="https://github.com/aboutcode-org/sample",
commit_hash="98e516011d6e096e25247b82fc5f196bbeecff10",
)
],
)
],
url="https://github.com/aboutcode-org/sample",
)

self.advisory = insert_advisory_v2(self.advisory, "importer_1", print, 100)
self.advisory.is_latest = True
self.advisory._all_impacts_unfurled_at = timezone.now()
self.advisory.save()
self.package, _ = PackageV2.objects.get_or_create(
package_url="pkg:pypi/sample@1.0.0",
defaults={"name": "sample", "type": "pypi", "version": "1.0.0"},
)

impacted_package = ImpactedPackage.objects.get(advisory=self.advisory)
ImpactedPackageAffecting.objects.get_or_create(
package=self.package,
impacted_package=impacted_package,
)
adv_set = AdvisorySet.objects.create(
package=self.package, primary_advisory=self.advisory, relation_type="affecting"
)
AdvisorySetMember.objects.create(advisory_set=adv_set, advisory=self.advisory)

self.client = APIClient(enforce_csrf_checks=True)

def test_packages_commit_patch(self):
url = reverse("package-v3-list")
response = self.client.post(
url,
data={"purls": ["pkg:pypi/sample@1.0.0"], "details": True},
format="json",
)

assert response.status_code == 200
results = response.data["results"]
assert len(results) == 1
pkg = results[0]
assert pkg["purl"] == "pkg:pypi/sample@1.0.0"

vulns = pkg.get("affected_by_vulnerabilities", [])
assert len(vulns) == 1
advisory_data = vulns[0]

assert advisory_data["advisory_id"] == "AVID-123"
assert advisory_data["introduced_in_patch"] == [
{
"commit_hash": "06580c7f99c6fde7bcf18e30bdcc61f081430957",
"vcs_url": "https://github.com/aboutcode-org/sample",
}
]

assert advisory_data["fixed_in_patch"] == [
{
"commit_hash": "98e516011d6e096e25247b82fc5f196bbeecff10",
"vcs_url": "https://github.com/aboutcode-org/sample",
}
]
52 changes: 52 additions & 0 deletions vulnerabilities/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -743,6 +743,7 @@ def add_ssvc(ssvc):
"advisory": advisory,
"severities": list(advisory.severities.all()),
"references": list(advisory.references.all()),
"patches": list(advisory.patches.all()),
"aliases": list(advisory.aliases.all()),
"severity_vectors": severity_vectors,
"weaknesses": weaknesses_present_in_db,
Expand Down Expand Up @@ -940,6 +941,57 @@ def get_queryset(self):
)


class AdvisoryPackageCommitPatchDetails(DetailView):
"""
View to display all packages introduce by or fixing a specific vulnerability.
URL: /advisories/commits/{id}
"""

model = models.AdvisoryV2
template_name = "advisory_package_commit_details.html"
slug_url_kwarg = "avid"

def get_object(self, queryset=None):
avid = self.kwargs.get(self.slug_url_kwarg)
if not avid:
raise Http404("Missing advisory identifier")

advisory = models.AdvisoryV2.objects.latest_for_avid(avid)

if not advisory:
raise Http404(f"No advisory found for avid: {avid}")

return advisory

def get_queryset(self):
"""
Prefetch and optimize related data to minimize database hits.
"""
return (
super()
.get_queryset()
.prefetch_related(
Prefetch(
"impacted_packages",
queryset=models.ImpactedPackage.objects.order_by("base_purl").prefetch_related(
Prefetch(
"introduced_by_package_commit_patches",
queryset=models.PackageCommitPatch.objects.only(
"commit_hash", "vcs_url", "patch_url", "commit_url"
),
),
Prefetch(
"fixed_by_package_commit_patches",
queryset=models.PackageCommitPatch.objects.only(
"commit_hash", "vcs_url", "patch_url", "commit_url"
),
),
),
)
)
)


class PipelineScheduleListView(VulnerableCodeListView, FormMixin):
model = PipelineSchedule
context_object_name = "schedule_list"
Expand Down
Loading
Loading