Skip to content

Commit 8f1f4a9

Browse files
authored
Merge branch 'main' into fix-migration
2 parents 868810d + 590c91a commit 8f1f4a9

File tree

11 files changed

+209
-70
lines changed

11 files changed

+209
-70
lines changed

vulnerabilities/api.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -642,17 +642,13 @@ def filter_cpe(self, queryset, name, value):
642642
return self.queryset.filter(vulnerabilityreference__reference_id__startswith=cpe).distinct()
643643

644644

645-
class CPEViewSet(viewsets.ReadOnlyModelViewSet):
646-
"""
647-
Lookup for vulnerabilities by CPE (https://nvd.nist.gov/products/cpe)
648-
"""
645+
class CPEViewSet(VulnerabilityViewSet):
646+
"""Lookup for vulnerabilities by CPE (https://nvd.nist.gov/products/cpe)"""
649647

650648
queryset = Vulnerability.objects.filter(
651649
vulnerabilityreference__reference_id__startswith="cpe"
652650
).distinct()
653-
serializer_class = VulnerabilitySerializer
654-
filter_backends = (filters.DjangoFilterBackend,)
655-
throttle_classes = [StaffUserRateThrottle, AnonRateThrottle]
651+
656652
filterset_class = CPEFilterSet
657653

658654
@action(detail=False, methods=["post"])

vulnerabilities/importers/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
from vulnerabilities.importers import oss_fuzz
2727
from vulnerabilities.importers import postgresql
2828
from vulnerabilities.importers import project_kb_msr2019
29-
from vulnerabilities.importers import pysec
3029
from vulnerabilities.importers import redhat
3130
from vulnerabilities.importers import retiredotnet
3231
from vulnerabilities.importers import ruby
@@ -42,9 +41,9 @@
4241
from vulnerabilities.pipelines import npm_importer
4342
from vulnerabilities.pipelines import nvd_importer
4443
from vulnerabilities.pipelines import pypa_importer
44+
from vulnerabilities.pipelines import pysec_importer
4545

4646
IMPORTERS_REGISTRY = [
47-
pysec.PyPIImporter,
4847
alpine_linux.AlpineImporter,
4948
openssl.OpensslImporter,
5049
redhat.RedhatImporter,
@@ -78,6 +77,7 @@
7877
gitlab_importer.GitLabImporterPipeline,
7978
github_importer.GitHubAPIImporterPipeline,
8079
nvd_importer.NVDImporterPipeline,
80+
pysec_importer.PyPIImporterPipeline,
8181
]
8282

8383
IMPORTERS_REGISTRY = {

vulnerabilities/importers/pysec.py

Lines changed: 0 additions & 44 deletions
This file was deleted.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Generated by Django 4.2.16 on 2024-10-24 13:51
2+
3+
from django.db import migrations
4+
5+
"""
6+
Update the created_by field on Advisory from the old qualified_name
7+
to the new pipeline_id.
8+
"""
9+
10+
11+
def update_created_by(apps, schema_editor):
12+
from vulnerabilities.pipelines.pysec_importer import PyPIImporterPipeline
13+
14+
Advisory = apps.get_model("vulnerabilities", "Advisory")
15+
Advisory.objects.filter(created_by="vulnerabilities.importers.pysec.PyPIImporter").update(
16+
created_by=PyPIImporterPipeline.pipeline_id
17+
)
18+
19+
20+
def reverse_update_created_by(apps, schema_editor):
21+
from vulnerabilities.pipelines.pysec_importer import PyPIImporterPipeline
22+
23+
Advisory = apps.get_model("vulnerabilities", "Advisory")
24+
Advisory.objects.filter(created_by=PyPIImporterPipeline.pipeline_id).update(
25+
created_by="vulnerabilities.importers.pysec.PyPIImporter"
26+
)
27+
28+
29+
class Migration(migrations.Migration):
30+
31+
dependencies = [
32+
("vulnerabilities", "0073_delete_packagerelatedvulnerability"),
33+
]
34+
35+
operations = [
36+
migrations.RunPython(update_created_by, reverse_code=reverse_update_created_by),
37+
]

vulnerabilities/models.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,7 @@ def __str__(self):
406406
@property
407407
def is_cpe(self):
408408
"""
409-
Return Trueis this is a CPE reference.
409+
Return True if this is a CPE reference.
410410
"""
411411
return self.reference_id.startswith("cpe")
412412

@@ -557,7 +557,7 @@ def for_cve(self, cve):
557557

558558
def with_is_vulnerable(self):
559559
"""
560-
Annotate Package with ``with_is_vulnerable`` boolean attribute.
560+
Annotate Package with ``is_vulnerable`` boolean attribute.
561561
"""
562562
return self.annotate(
563563
is_vulnerable=Exists(

vulnerabilities/pipelines/pypa_importer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
# See https://github.com/aboutcode-org/vulnerablecode for support or download.
77
# See https://aboutcode.org for more information about nexB OSS projects.
88
#
9-
import logging
9+
1010
from pathlib import Path
1111
from typing import Iterable
1212

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
#
2+
# Copyright (c) nexB Inc. and others. All rights reserved.
3+
# VulnerableCode is a trademark of nexB Inc.
4+
# SPDX-License-Identifier: Apache-2.0
5+
# See http://www.apache.org/licenses/LICENSE-2.0 for the license text.
6+
# See https://github.com/aboutcode-org/vulnerablecode for support or download.
7+
# See https://aboutcode.org for more information about nexB OSS projects.
8+
#
9+
import json
10+
import logging
11+
from io import BytesIO
12+
from typing import Iterable
13+
from zipfile import ZipFile
14+
15+
import requests
16+
17+
from vulnerabilities.importer import AdvisoryData
18+
from vulnerabilities.pipelines import VulnerableCodeBaseImporterPipeline
19+
20+
21+
class PyPIImporterPipeline(VulnerableCodeBaseImporterPipeline):
22+
"""Collect advisories from PyPI."""
23+
24+
pipeline_id = "pysec_importer"
25+
26+
license_url = "https://github.com/pypa/advisory-database/blob/main/LICENSE"
27+
url = "https://osv-vulnerabilities.storage.googleapis.com/PyPI/all.zip"
28+
spdx_license_expression = "CC-BY-4.0"
29+
importer_name = "PyPI Importer"
30+
31+
@classmethod
32+
def steps(cls):
33+
return (
34+
cls.fetch_zip,
35+
cls.collect_and_store_advisories,
36+
cls.import_new_advisories,
37+
)
38+
39+
def fetch_zip(self):
40+
self.log(f"Fetching `{self.url}`")
41+
self.advisory_zip = requests.get(self.url).content
42+
43+
def advisories_count(self) -> int:
44+
with ZipFile(BytesIO(self.advisory_zip)) as zip:
45+
advisory_count = sum(1 for file in zip.namelist() if file.startswith("PYSEC-"))
46+
return advisory_count
47+
48+
def collect_advisories(self) -> Iterable[AdvisoryData]:
49+
"""Yield AdvisoryData using a zipped data dump of OSV data"""
50+
from vulnerabilities.importers.osv import parse_advisory_data
51+
52+
with ZipFile(BytesIO(self.advisory_zip)) as zip_file:
53+
for file_name in zip_file.namelist():
54+
if not file_name.startswith("PYSEC-"):
55+
self.log(
56+
f"Unsupported PyPI advisory data file: {file_name}",
57+
level=logging.ERROR,
58+
)
59+
continue
60+
with zip_file.open(file_name) as f:
61+
vul_info = json.load(f)
62+
yield parse_advisory_data(
63+
raw_data=vul_info,
64+
supported_ecosystems=["pypi"],
65+
advisory_url=self.url,
66+
)

vulnerabilities/tests/test_pysec.py renamed to vulnerabilities/tests/pipelines/test_pysec_importer_pipeline.py

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,52 +7,51 @@
77
# See https://aboutcode.org for more information about nexB OSS projects.
88
#
99
import json
10-
import os
10+
from pathlib import Path
1111
from unittest import TestCase
1212

1313
from vulnerabilities.importers.osv import parse_advisory_data
1414
from vulnerabilities.tests.util_tests import VULNERABLECODE_REGEN_TEST_FIXTURES as REGEN
1515
from vulnerabilities.tests.util_tests import check_results_against_json
1616

17-
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
18-
TEST_DATA = os.path.join(BASE_DIR, "test_data/pysec")
17+
TEST_DATA = Path(__file__).parent.parent / "test_data" / "pysec"
1918

2019

2120
class TestPyPIImporter(TestCase):
2221
def test_to_advisories_with_summary(self):
23-
with open(os.path.join(TEST_DATA, "pysec-advisories_with_summary.json")) as f:
22+
with open(TEST_DATA / "pysec-advisories_with_summary.json") as f:
2423
mock_response = json.load(f)
2524
results = parse_advisory_data(mock_response, ["pypi"], "https://test.com").to_dict()
2625

27-
expected_file = os.path.join(TEST_DATA, "pysec-advisories_with_summary-expected.json")
26+
expected_file = TEST_DATA / "pysec-advisories_with_summary-expected.json"
2827
check_results_against_json(
2928
results=results,
3029
expected_file=expected_file,
3130
regen=REGEN,
3231
)
3332

3433
def test_to_advisories_without_summary(self):
35-
with open(os.path.join(TEST_DATA, "pysec-advisories_without_summary.json")) as f:
34+
with open(TEST_DATA / "pysec-advisories_without_summary.json") as f:
3635
mock_response = json.load(f)
3736

3837
results = parse_advisory_data(mock_response, ["pypi"], "https://test.com").to_dict()
3938

40-
expected_file = os.path.join(TEST_DATA, "pysec-advisories_without_summary-expected.json")
39+
expected_file = TEST_DATA / "pysec-advisories_without_summary-expected.json"
4140
check_results_against_json(
4241
results=results,
4342
expected_file=expected_file,
4443
regen=REGEN,
4544
)
4645

4746
def test_to_advisories_with_cwe(self):
48-
with open(os.path.join(TEST_DATA, "pysec-advisory_with_cwe.json")) as f:
47+
with open(TEST_DATA / "pysec-advisory_with_cwe.json") as f:
4948
mock_response = json.load(f)
5049

5150
results = parse_advisory_data(
5251
raw_data=mock_response, supported_ecosystems=["pypi"], advisory_url="https://tes.com"
5352
).to_dict()
5453

55-
expected_file = os.path.join(TEST_DATA, "pysec-advisories_with_cwe-expected.json")
54+
expected_file = TEST_DATA / "pysec-advisories_with_cwe-expected.json"
5655
check_results_against_json(
5756
results=results,
5857
expected_file=expected_file,

vulnerabilities/tests/test_api.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -705,6 +705,46 @@ def test_api_response(self):
705705
self.assertEqual(response["count"], 1)
706706

707707

708+
class TestCPEApiWithPackageVulnerabilityRelation(TestCase):
709+
def setUp(self):
710+
self.user = ApiUser.objects.create_api_user(username="[email protected]")
711+
self.auth = f"Token {self.user.auth_token.key}"
712+
self.csrf_client = APIClient(enforce_csrf_checks=True)
713+
self.csrf_client.credentials(HTTP_AUTHORIZATION=self.auth)
714+
self.vulnerability = Vulnerability.objects.create(summary="test")
715+
self.affected_package, _ = Package.objects.get_or_create_from_purl(
716+
purl="pkg:nginx/[email protected]"
717+
)
718+
self.fixed_package, _ = Package.objects.get_or_create_from_purl(purl="pkg:nginx/[email protected]")
719+
AffectedByPackageRelatedVulnerability.objects.create(
720+
vulnerability=self.vulnerability,
721+
created_by="test",
722+
package=self.affected_package,
723+
confidence=100,
724+
)
725+
FixingPackageRelatedVulnerability.objects.create(
726+
vulnerability=self.vulnerability,
727+
created_by="test",
728+
package=self.fixed_package,
729+
confidence=100,
730+
)
731+
for i in range(0, 10):
732+
ref, _ = VulnerabilityReference.objects.get_or_create(
733+
reference_id=f"cpe:/a:nginx:{i}",
734+
url=f"https://nvd.nist.gov/vuln/search/results?adv_search=true&isCpeNameSearch=true&query=cpe:/a:nginx:{i}",
735+
)
736+
VulnerabilityRelatedReference.objects.create(
737+
reference=ref, vulnerability=self.vulnerability
738+
)
739+
740+
def test_cpe_api(self):
741+
response = self.csrf_client.get("/api/cpes/", format="json")
742+
self.assertEqual(status.HTTP_200_OK, response.status_code)
743+
744+
response_data = response.json()
745+
self.assertEqual(1, response_data["count"])
746+
747+
708748
class AliasApi(TestCase):
709749
def setUp(self):
710750
self.user = ApiUser.objects.create_api_user(username="[email protected]")

0 commit comments

Comments
 (0)