Skip to content

Commit c22de9c

Browse files
committed
Add plain_purl option and add tests
Signed-off-by: Tushar Goel <[email protected]>
1 parent 17317ca commit c22de9c

File tree

6 files changed

+100
-18
lines changed

6 files changed

+100
-18
lines changed

vulnerabilities/api.py

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -241,22 +241,50 @@ def bulk_search(self, request):
241241

242242
purls = request.data.get("purls", []) or []
243243
purl_only = request.data.get("purl_only", False)
244+
plain_purl = request.data.get("plain_purl", False)
244245
if not purls or not isinstance(purls, list):
245246
return Response(
246247
status=400,
247248
data={"Error": "A non-empty 'purls' list of PURLs is required."},
248249
)
249250

250-
query = Package.objects.filter(package_url__in=purls)
251+
if plain_purl:
252+
purl_objects = [PackageURL.from_string(purl) for purl in purls]
253+
plain_purl_objects = [
254+
PackageURL(
255+
type=purl.type,
256+
namespace=purl.namespace,
257+
name=purl.name,
258+
version=purl.version,
259+
)
260+
for purl in purl_objects
261+
]
262+
plain_purls = [str(purl) for purl in plain_purl_objects]
263+
264+
query = (
265+
Package.objects.filter(plain_package_url__in=plain_purls)
266+
.order_by("plain_package_url")
267+
.distinct("plain_package_url")
268+
)
269+
270+
if not purl_only:
271+
return Response(
272+
PackageSerializer(query, many=True, context={"request": request}).data
273+
)
274+
275+
# using order by and distinct because there will be
276+
# many fully qualified purl for a single plain purl
277+
vulnerable_purls = query.vulnerable().only("plain_package_url")
278+
vulnerable_purls = [str(package.plain_package_url) for package in vulnerable_purls]
279+
return Response(data=vulnerable_purls)
280+
281+
query = Package.objects.filter(package_url__in=purls).distinct()
251282

252283
if not purl_only:
253-
return Response(
254-
PackageSerializer(query.distinct(), many=True, context={"request": request}).data
255-
)
284+
return Response(PackageSerializer(query, many=True, context={"request": request}).data)
256285

257-
vulnerable_purls = (
258-
query.filter(packagerelatedvulnerability__fix=False).only("package").distinct()
259-
)
286+
vulnerable_purls = query.vulnerable().only("package_url")
287+
vulnerable_purls = [str(package.package_url) for package in vulnerable_purls]
260288
return Response(data=vulnerable_purls)
261289

262290
@action(detail=False, methods=["get"], throttle_scope="vulnerable_packages")

vulnerabilities/migrations/0034_package_package_url.py renamed to vulnerabilities/migrations/0034_package_package_url_package_plain_package_url.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Generated by Django 4.0.7 on 2022-11-25 17:28
1+
# Generated by Django 4.0.7 on 2022-11-28 12:58
22

33
from django.db import migrations, models
44

@@ -13,6 +13,11 @@ class Migration(migrations.Migration):
1313
migrations.AddField(
1414
model_name='package',
1515
name='package_url',
16-
field=models.CharField(blank=True, db_index=True, help_text='The Package URL for this package.', max_length=255),
16+
field=models.CharField(blank=True, db_index=True, help_text='The Package URL for this package.', max_length=1000),
17+
),
18+
migrations.AddField(
19+
model_name='package',
20+
name='plain_package_url',
21+
field=models.CharField(blank=True, db_index=True, help_text='The Package URL for this package without qualifiers and subpath.', max_length=1000),
1722
),
1823
]

vulnerabilities/migrations/0035_add_package_url_to_packages.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,25 @@ def save_purls(apps, schema_editor):
1515
qualifiers=package.qualifiers,
1616
subpath=package.subpath,
1717
)
18+
plain_purl = PackageURL(
19+
type=package.type,
20+
namespace=package.namespace,
21+
name=package.name,
22+
version=package.version,
23+
)
1824
package.package_url = str(purl)
25+
package.plain_package_url = str(plain_purl)
1926
updatables.append(package)
2027

2128
updated = Package.objects.bulk_update(
2229
objs = updatables,
23-
fields=["package_url"],
30+
fields=["package_url", "plain_package_url"],
2431
batch_size=500,
2532
)
2633
print(f"Migrated {updated} packages with package_url")
2734

2835
dependencies = [
29-
('vulnerabilities', '0034_package_package_url'),
36+
("vulnerabilities", "0034_package_package_url_package_plain_package_url"),
3037
]
3138

3239
operations = [

vulnerabilities/migrations/0036_alter_package_package_url.py renamed to vulnerabilities/migrations/0036_alter_package_package_url_and_more.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Generated by Django 4.0.7 on 2022-11-25 17:34
1+
# Generated by Django 4.0.7 on 2022-11-28 13:00
22

33
from django.db import migrations, models
44

@@ -13,6 +13,11 @@ class Migration(migrations.Migration):
1313
migrations.AlterField(
1414
model_name='package',
1515
name='package_url',
16-
field=models.CharField(db_index=True, help_text='The Package URL for this package.', max_length=255),
16+
field=models.CharField(db_index=True, help_text='The Package URL for this package.', max_length=1000),
17+
),
18+
migrations.AlterField(
19+
model_name='package',
20+
name='plain_package_url',
21+
field=models.CharField(db_index=True, help_text='The Package URL for this package without qualifiers and subpath.', max_length=1000),
1722
),
1823
]

vulnerabilities/models.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -532,12 +532,19 @@ class Package(PackageURLMixin):
532532
)
533533

534534
package_url = models.CharField(
535-
max_length=255,
535+
max_length=1000,
536536
null=False,
537537
help_text="The Package URL for this package.",
538538
db_index=True,
539539
)
540540

541+
plain_package_url = models.CharField(
542+
max_length=1000,
543+
null=False,
544+
help_text="The Package URL for this package without qualifiers and subpath.",
545+
db_index=True,
546+
)
547+
541548
objects = PackageQuerySet.as_manager()
542549

543550
def save(self, *args, **kwargs):
@@ -549,7 +556,14 @@ def save(self, *args, **kwargs):
549556
qualifiers=self.qualifiers,
550557
subpath=self.subpath,
551558
)
559+
plain_purl = PackageURL(
560+
type=self.type,
561+
namespace=self.namespace,
562+
name=self.name,
563+
version=self.version,
564+
)
552565
self.package_url = str(purl_object)
566+
self.plain_package_url = str(plain_purl)
553567
super().save(*args, **kwargs)
554568

555569
@property

vulnerabilities/tests/test_fix_api.py

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,19 @@ def setUp(self):
358358
attrs = {k: v for k, v in purl.to_dict().items() if v}
359359
Package.objects.create(**attrs)
360360

361+
vulnerable_packages = [
362+
"pkg:nginx/[email protected]?foo=bar",
363+
"pkg:nginx/[email protected]?foo=baz",
364+
]
365+
366+
vuln = Vulnerability.objects.create(summary="test")
367+
368+
for package in vulnerable_packages:
369+
purl = PackageURL.from_string(package)
370+
attrs = {k: v for k, v in purl.to_dict().items() if v}
371+
pkg = Package.objects.create(**attrs)
372+
PackageRelatedVulnerability.objects.create(package=pkg, vulnerability=vuln)
373+
361374
def test_bulk_api_response(self):
362375
request_body = {
363376
"purls": self.packages,
@@ -370,9 +383,7 @@ def test_bulk_api_response(self):
370383
assert len(response) == 13
371384

372385
def test_bulk_api_response_with_ignoring_qualifiers(self):
373-
request_body = {
374-
"purls": ["pkg:nginx/[email protected]?qualifiers=dev"],
375-
}
386+
request_body = {"purls": ["pkg:nginx/[email protected]?qualifiers=dev"], "plain_purl": True}
376387
response = self.csrf_client.post(
377388
"/api/packages/bulk_search",
378389
data=json.dumps(request_body),
@@ -382,16 +393,28 @@ def test_bulk_api_response_with_ignoring_qualifiers(self):
382393
assert response[0]["purl"] == "pkg:nginx/[email protected]"
383394

384395
def test_bulk_api_response_with_ignoring_subpath(self):
396+
request_body = {"purls": ["pkg:nginx/[email protected]#dev/subpath"], "plain_purl": True}
397+
response = self.csrf_client.post(
398+
"/api/packages/bulk_search",
399+
data=json.dumps(request_body),
400+
content_type="application/json",
401+
).json()
402+
assert len(response) == 1
403+
assert response[0]["purl"] == "pkg:nginx/[email protected]"
404+
405+
def test_bulk_api_with_purl_only_option(self):
385406
request_body = {
386407
"purls": ["pkg:nginx/[email protected]#dev/subpath"],
408+
"purl_only": True,
409+
"plain_purl": True,
387410
}
388411
response = self.csrf_client.post(
389412
"/api/packages/bulk_search",
390413
data=json.dumps(request_body),
391414
content_type="application/json",
392415
).json()
393416
assert len(response) == 1
394-
assert response[0]["purl"] == "pkg:nginx/[email protected]"
417+
assert response[0] == "pkg:nginx/[email protected]"
395418

396419

397420
class BulkSearchAPICPE(TestCase):

0 commit comments

Comments
 (0)