Skip to content

Commit a0db697

Browse files
committed
Add API support for PackageCommitPatch
Add a test Signed-off-by: ziad hany <ziadhany2016@gmail.com>
1 parent 8b90a51 commit a0db697

6 files changed

Lines changed: 305 additions & 5 deletions

File tree

vulnerabilities/api_v3.py

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,18 +221,24 @@ def get_affected_by_vulnerabilities(self, package):
221221
"""Return a dictionary with advisory as keys and their details, including fixed_by_packages."""
222222
advisories = self.context["advisory_map"].get(package.id, [])
223223
impact_map = self.context["impact_map"].get(package.id, {})
224+
introduced_patch_map = self.context.get("introduced_patch_map", {})
225+
fixed_patch_map = self.context.get("fixed_patch_map", {})
224226

225227
if advisories:
226228
result = []
227229

228230
for adv in advisories:
229231
fixed = impact_map.get(adv["avid"])
232+
introduced_patches = introduced_patch_map.get((package.id, adv["avid"]), [])
233+
fixed_patches = fixed_patch_map.get((package.id, adv["avid"]), [])
230234
adv.pop("avid", None)
231235

232236
result.append(
233237
{
234238
**adv,
235239
"fixed_by_packages": fixed,
240+
"introduced_by_patch": introduced_patches,
241+
"fixed_by_patch": fixed_patches,
236242
}
237243
)
238244

@@ -266,7 +272,8 @@ def get_affected_by_vulnerabilities(self, package):
266272
impact = impact_by_avid.get(advisory.avid)
267273
if not impact:
268274
continue
269-
275+
introduced_patches = introduced_patch_map.get((package.id, advisory.avid), [])
276+
fixed_patches = fixed_patch_map.get((package.id, advisory.avid), [])
270277
result.append(
271278
{
272279
"advisory_id": advisory.advisory_id.split("/")[-1],
@@ -276,9 +283,10 @@ def get_affected_by_vulnerabilities(self, package):
276283
"exploitability": advisory.exploitability,
277284
"risk_score": advisory.risk_score,
278285
"fixed_by_packages": [pkg.purl for pkg in impact.fixed_by_packages.all()],
286+
"introduced_by_patch": introduced_patches,
287+
"fixed_by_patch": fixed_patches,
279288
}
280289
)
281-
282290
return result
283291

284292
if not advisories:
@@ -353,6 +361,8 @@ def return_advisories_data(self, package, advisories_qs, advisories):
353361
)
354362

355363
impact_by_avid = {impact.advisory.avid: impact for impact in impacts}
364+
introduced_patch_map = self.context.get("introduced_patch_map", {})
365+
fixed_patch_map = self.context.get("fixed_patch_map", {})
356366

357367
result = []
358368
for advisory in advisories:
@@ -361,6 +371,8 @@ def return_advisories_data(self, package, advisories_qs, advisories):
361371
if not impact:
362372
continue
363373

374+
introduced_patches = introduced_patch_map.get((package.id, advisory.advisory.avid), [])
375+
fixed_patches = fixed_patch_map.get((package.id, advisory.advisory.avid), [])
364376
result.append(
365377
{
366378
"advisory_id": advisory.identifier,
@@ -372,6 +384,8 @@ def return_advisories_data(self, package, advisories_qs, advisories):
372384
"fixed_by_packages": list(
373385
set([pkg.purl for pkg in impact.fixed_by_packages.all()])
374386
),
387+
"introduced_by_patch": introduced_patches,
388+
"fixed_by_patch": fixed_patches,
375389
}
376390
)
377391

@@ -456,6 +470,7 @@ def create(self, request, *args, **kwargs):
456470
affected_advisory_map = get_affected_advisories_bulk(page)
457471
fixing_advisory_map = get_fixing_advisories_bulk(page)
458472
impact_map = get_impacts_bulk(page)
473+
introduced_patch_map, fixed_patch_map = get_patches_bulk(page)
459474
serializer = self.get_serializer(
460475
page,
461476
many=True,
@@ -464,6 +479,8 @@ def create(self, request, *args, **kwargs):
464479
"advisory_map": affected_advisory_map,
465480
"impact_map": impact_map,
466481
"fixing_advisory_map": fixing_advisory_map,
482+
"introduced_patch_map": introduced_patch_map,
483+
"fixed_patch_map": fixed_patch_map,
467484
},
468485
)
469486
return self.get_paginated_response(serializer.data)
@@ -673,6 +690,48 @@ def get_impacts_bulk(packages):
673690
return impact_map
674691

675692

693+
def get_patches_bulk(packages):
694+
"""
695+
Returns a tuple of two dicts:
696+
introduced_map: (package_id, advisory_avid) -> list of introduced package_commit_patches dicts
697+
fixed_map: (package_id, advisory_avid) -> list of fixed package_commit_patches dicts
698+
Each package_commit_patches dict contains 'commit_hash' and 'vcs_url'
699+
"""
700+
package_ids = [p.id for p in packages]
701+
if not package_ids:
702+
return {}, {}
703+
704+
impacted_packages_qs = (
705+
ImpactedPackageAffecting.objects.filter(package_id__in=package_ids)
706+
.select_related("impacted_package__advisory")
707+
.prefetch_related(
708+
"impacted_package__introduced_by_package_commit_patches",
709+
"impacted_package__fixed_by_package_commit_patches",
710+
)
711+
)
712+
713+
introduced_patches = defaultdict(list)
714+
fixed_patches = defaultdict(list)
715+
716+
for impacted_pkg_qs in impacted_packages_qs:
717+
pkg_id = impacted_pkg_qs.package_id
718+
impact = impacted_pkg_qs.impacted_package
719+
avid = impact.advisory.avid
720+
key = (pkg_id, avid)
721+
722+
for patch in impact.introduced_by_package_commit_patches.all():
723+
patch_data = {"commit_hash": patch.commit_hash, "vcs_url": patch.vcs_url}
724+
if patch_data not in introduced_patches[key]:
725+
introduced_patches[key].append(patch_data)
726+
727+
for patch in impact.fixed_by_package_commit_patches.all():
728+
patch_data = {"commit_hash": patch.commit_hash, "vcs_url": patch.vcs_url}
729+
if patch_data not in fixed_patches[key]:
730+
fixed_patches[key].append(patch_data)
731+
732+
return introduced_patches, fixed_patches
733+
734+
676735
def get_fixing_advisories_bulk(packages):
677736
package_ids = [p.id for p in packages]
678737

vulnerabilities/templates/advisory_detail.html

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,16 @@
8080
</a>
8181
</li>
8282
{% endif %}
83-
83+
84+
<li data-tab="patch-url">
85+
<a>
86+
<span>
87+
{% with pcp_length=package_commit_patches|length %}
88+
Patches: ({{ advisory.patches.count|add:pcp_length }})
89+
{% endwith %}
90+
</span>
91+
</a>
92+
</li>
8493
<!-- <li data-tab="history">
8594
<a>
8695
<span>
@@ -184,6 +193,18 @@
184193
</a>
185194
</td>
186195
</tr>
196+
<tr>
197+
<td class="two-col-left"
198+
data-tooltip="Risk expressed as a number ranging from 0 to 10. It is calculated by multiplying
199+
the weighted severity and exploitability values, capped at a maximum of 10.
200+
"
201+
>Introduced and Fixed Package Commit Patches</td>
202+
<td class="two-col-right wrap-strings">
203+
<a href="/advisories/commits/{{ advisory.avid }}">
204+
Package Commit Patches Details
205+
</a>
206+
</td>
207+
</tr>
187208
</tbody>
188209
</table>
189210
<div class="has-text-weight-bold tab-nested-div ml-1 mb-1 mt-6">
@@ -436,7 +457,6 @@
436457
</tr>
437458
{% endfor %}
438459
</div>
439-
440460

441461
<div class="tab-div content" data-content="epss">
442462
{% if epss_data %}
@@ -503,6 +523,28 @@
503523
{% endif %}
504524
</div>
505525

526+
<div class="tab-div content" data-content="patch-url">
527+
<table class="table is-bordered is-striped is-narrow is-hoverable is-fullwidth">
528+
<thead>
529+
<tr>
530+
<th style="width: 250px;"> Patch URL </th>
531+
</tr>
532+
</thead>
533+
{% for patch in patches %}
534+
<tr>
535+
<td class="wrap-strings"><a href="{{ patch.patch_url }}" target="_blank">{{ patch.patch_url }}<i
536+
class="fa fa-external-link fa_link_custom"></i></a></td>
537+
</tr>
538+
{% empty %}
539+
<tr>
540+
<td colspan="2">
541+
There are no known patches.
542+
</td>
543+
</tr>
544+
{% endfor %}
545+
</table>
546+
</div>
547+
506548
<div class="tab-div content" data-content="severities-vectors">
507549
{% for severity_vector in severity_vectors %}
508550
{% if severity_vector.vector.version == '2.0' %}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
{% extends "base.html" %}
2+
{% load humanize %}
3+
{% load widget_tweaks %}
4+
{% load static %}
5+
{% load show_cvss %}
6+
{% load url_filters %}
7+
8+
{% block title %}
9+
VulnerableCode Advisory Package Commit Patch Details - {{ advisoryv2.advisory_id }}
10+
{% endblock %}
11+
12+
{% block content %}
13+
14+
{% if advisoryv2 %}
15+
<section class="section pt-0">
16+
<div class="details-container">
17+
<article class="panel is-info panel-header-only">
18+
<div class="panel-heading py-2 is-size-6">
19+
Introduce and Fixing Package Commit Patch details for Advisory:
20+
<span class="tag is-white custom">
21+
{{ advisoryv2.advisory_id }}
22+
</span>
23+
</div>
24+
</article>
25+
26+
<div id="tab-content">
27+
<table class="table vcio-table width-100-pct mt-2">
28+
<thead>
29+
<tr>
30+
<th style="width: 50%;">Introduced in</th>
31+
<th>Fixed by</th>
32+
</tr>
33+
</thead>
34+
<tbody>
35+
{% for impact in advisoryv2.impacted_packages.all %}
36+
{% for pkg_commit_patch in impact.introduced_by_package_commit_patches.all %}
37+
<tr>
38+
<td>
39+
<a href="{{ pkg_commit_patch.vcs_url }}" target="_self">
40+
{{ pkg_commit_patch.base_purl }}@{{ pkg_commit_patch.commit_hash }}
41+
</a>
42+
</td>
43+
<td></td>
44+
</tr>
45+
{% endfor %}
46+
47+
{% for pkg_commit_patch in impact.fixed_by_package_commit_patches.all %}
48+
<tr>
49+
<td></td>
50+
<td>
51+
<a href="{{ pkg_commit_patch.vcs_url }}" target="_self">
52+
{{ impact.base_purl }}@{{ pkg_commit_patch.commit_hash }}
53+
</a>
54+
</td>
55+
</tr>
56+
{% endfor %}
57+
58+
{% empty %}
59+
<tr>
60+
<td colspan="2">
61+
This vulnerability is not known to affect any package commits.
62+
</td>
63+
</tr>
64+
{% endfor %}
65+
</tbody>
66+
</table>
67+
</div>
68+
69+
</div>
70+
</section>
71+
{% endif %}
72+
73+
<script src="{% static 'js/main.js' %}" crossorigin="anonymous"></script>
74+
75+
{% endblock %}

vulnerabilities/tests/test_api_v3.py

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
from univers.version_range import PypiVersionRange
1616

1717
from vulnerabilities.importer import AdvisoryDataV2
18+
from vulnerabilities.importer import AffectedPackageV2
19+
from vulnerabilities.importer import PackageCommitPatchData
1820
from vulnerabilities.models import AdvisoryV2
1921
from vulnerabilities.models import PackageV2
2022
from vulnerabilities.pipes.advisory import insert_advisory_v2
@@ -66,7 +68,7 @@ def test_packages_post_without_details(self):
6668
def test_packages_post_with_details(self):
6769
url = reverse("package-v3-list")
6870

69-
with self.assertNumQueries(31):
71+
with self.assertNumQueries(34):
7072
response = self.client.post(
7173
url,
7274
data={
@@ -244,3 +246,67 @@ def test_get_all_vulnerable_purls(self):
244246
results = response.data["results"]
245247
self.assertEqual(len(results), 100)
246248
self.assertIn("next", response.data)
249+
250+
251+
class PackageCommitPatchTests(APITestCase):
252+
def setUp(self):
253+
self.advisory = AdvisoryDataV2(
254+
advisory_id="AVID-123",
255+
aliases=[],
256+
affected_packages=[
257+
AffectedPackageV2(
258+
package=PackageURL(type="pypi", name="sample"),
259+
affected_version_range=PypiVersionRange.from_string("vers:pypi/=1.0.0"),
260+
introduced_by_commit_patches=[
261+
PackageCommitPatchData(
262+
vcs_url="https://github.com/aboutcode-org/sample",
263+
commit_hash="06580c7f99c6fde7bcf18e30bdcc61f081430957",
264+
)
265+
],
266+
fixed_by_commit_patches=[
267+
PackageCommitPatchData(
268+
vcs_url="https://github.com/aboutcode-org/sample",
269+
commit_hash="98e516011d6e096e25247b82fc5f196bbeecff10",
270+
)
271+
],
272+
)
273+
],
274+
url="https://github.com/aboutcode-org/sample",
275+
)
276+
277+
self.advisory = insert_advisory_v2(self.advisory, "importer_1", print, 100)
278+
self.client = APIClient(enforce_csrf_checks=True)
279+
280+
def test_packages_commit_patch(self):
281+
url = reverse("package-v3-list")
282+
response = self.client.post(
283+
url,
284+
data={"purls": ["pkg:pypi/sample@1.0.0"], "details": True},
285+
format="json",
286+
)
287+
288+
assert response.status_code == 200
289+
results = response.data["results"]
290+
assert len(results), 1
291+
pkg = results[0]
292+
assert pkg["purl"] == "pkg:pypi/sample@1.0.0"
293+
294+
vulns = pkg.get("affected_by_vulnerabilities", [])
295+
assert len(vulns) == 1
296+
advisory_data = vulns[0]
297+
298+
assert advisory_data["advisory_id"] == "AVID-123"
299+
300+
assert advisory_data["introduced_by_patch"] == [
301+
{
302+
"commit_hash": "06580c7f99c6fde7bcf18e30bdcc61f081430957",
303+
"vcs_url": "https://github.com/aboutcode-org/sample",
304+
}
305+
]
306+
307+
assert advisory_data["fixed_by_patch"] == [
308+
{
309+
"commit_hash": "98e516011d6e096e25247b82fc5f196bbeecff10",
310+
"vcs_url": "https://github.com/aboutcode-org/sample",
311+
}
312+
]

0 commit comments

Comments
 (0)