Skip to content

Commit d987e95

Browse files
committed
Allow vulnerabilities look up from a CPE
Change models and add migrations for same, do some minor nit picks in nvd importer Signed-off-by: Tushar Goel <tushar.goel.dav@gmail.com>
1 parent 016d40a commit d987e95

6 files changed

Lines changed: 114 additions & 22 deletions

File tree

vulnerabilities/api.py

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -183,26 +183,19 @@ class CPEFilterSet(filters.FilterSet):
183183
cpe = filters.CharFilter(method="filter_cpe")
184184

185185
class Meta:
186-
model = VulnerabilityReference
187-
fields = ["reference_id"]
186+
model = Vulnerability
187+
fields = ["vulnerabilityreference__reference_id"]
188188

189189
def filter_cpe(self, queryset, name, value):
190190
cpe = unquote(value)
191-
return self.queryset.filter(reference_id=cpe)
192-
193-
194-
class CPESerializer(serializers.HyperlinkedModelSerializer):
195-
196-
vulnerability = MinimalVulnerabilitySerializer(read_only=True)
197-
198-
class Meta:
199-
model = VulnerabilityReference
200-
fields = ["vulnerability"]
191+
return self.queryset.filter(vulnerabilityreference__reference_id__startswith=cpe).distinct()
201192

202193

203194
class CPEViewSet(viewsets.ReadOnlyModelViewSet):
204-
queryset = VulnerabilityReference.objects.filter(reference_id__startswith="cpe")
205-
serializer_class = CPESerializer
195+
queryset = Vulnerability.objects.filter(
196+
vulnerabilityreference__reference_id__startswith="cpe"
197+
).distinct()
198+
serializer_class = VulnerabilitySerializer
206199
paginate_by = 50
207200
filter_backends = (filters.DjangoFilterBackend,)
208201
filterset_class = CPEFilterSet

vulnerabilities/importers/nvd.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,12 @@ class NVDImporter(Importer):
4545
spdx_license_expression = "LicenseRef-scancode-unknown"
4646

4747
def advisory_data(self):
48-
advisory_data = []
4948
current_year = date.today().year
5049
# NVD json feeds start from 2002.
5150
for year in range(2002, current_year + 1):
5251
download_url = f"https://nvd.nist.gov/feeds/json/cve/1.1/nvdcve-1.1-{year}.json.gz"
5352
data = fetch(download_url)
54-
advisory_data.extend(to_advisories(data))
55-
return advisory_data
53+
yield from to_advisories(data)
5654

5755

5856
# Isolating network calls for simplicity of testing

vulnerabilities/improve_runner.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,11 @@ def process_inferences(inferences: List[Inference], advisory: Advisory, improver
6363

6464
for ref in inference.references:
6565
reference, _ = models.VulnerabilityReference.objects.get_or_create(
66-
vulnerability=vuln, reference_id=ref.reference_id, url=ref.url
66+
reference_id=ref.reference_id, url=ref.url
67+
)
68+
69+
models.VulnerabilityRelatedReference.objects.update_or_create(
70+
reference=reference, vulnerability=vuln
6771
)
6872

6973
for severity in ref.severities:
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Generated by Django 4.0.3 on 2022-04-22 15:43
2+
3+
from django.db import migrations, models
4+
import django.db.models.deletion
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
('vulnerabilities', '0009_alter_advisory_summary_and_more'),
11+
]
12+
13+
operations = [
14+
migrations.CreateModel(
15+
name='VulnerabilityRelatedReference',
16+
fields=[
17+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
18+
],
19+
),
20+
migrations.AlterUniqueTogether(
21+
name='vulnerabilityreference',
22+
unique_together={('url', 'reference_id')},
23+
),
24+
migrations.AddField(
25+
model_name='vulnerability',
26+
name='references',
27+
field=models.ManyToManyField(through='vulnerabilities.VulnerabilityRelatedReference', to='vulnerabilities.vulnerabilityreference'),
28+
),
29+
migrations.AddField(
30+
model_name='vulnerabilityreference',
31+
name='vulnerabilities',
32+
field=models.ManyToManyField(through='vulnerabilities.VulnerabilityRelatedReference', to='vulnerabilities.vulnerability'),
33+
),
34+
migrations.AddField(
35+
model_name='vulnerabilityrelatedreference',
36+
name='reference',
37+
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='vulnerabilities.vulnerabilityreference'),
38+
),
39+
migrations.AddField(
40+
model_name='vulnerabilityrelatedreference',
41+
name='vulnerability',
42+
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='vulnerabilities.vulnerability'),
43+
),
44+
migrations.RemoveField(
45+
model_name='vulnerabilityreference',
46+
name='vulnerability',
47+
),
48+
migrations.AlterUniqueTogether(
49+
name='vulnerabilityrelatedreference',
50+
unique_together={('vulnerability', 'reference')},
51+
),
52+
]

vulnerabilities/models.py

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ class Vulnerability(models.Model):
6060
blank=True,
6161
)
6262

63+
references = models.ManyToManyField(
64+
to="VulnerabilityReference", through="VulnerabilityRelatedReference"
65+
)
66+
6367
@property
6468
def vulcoid(self):
6569
return f"VULCOID-{self.vulnerability_id}"
@@ -92,10 +96,11 @@ class VulnerabilityReference(models.Model):
9296
package manager.
9397
"""
9498

95-
vulnerability = models.ForeignKey(
96-
Vulnerability,
97-
on_delete=models.CASCADE,
99+
vulnerabilities = models.ManyToManyField(
100+
to="Vulnerability",
101+
through="VulnerabilityRelatedReference",
98102
)
103+
99104
url = models.URLField(
100105
max_length=1024, help_text="URL to the vulnerability reference", blank=True
101106
)
@@ -111,7 +116,6 @@ def severities(self):
111116

112117
class Meta:
113118
unique_together = (
114-
"vulnerability",
115119
"url",
116120
"reference_id",
117121
)
@@ -121,6 +125,25 @@ def __str__(self):
121125
return f"{self.url}{reference_id}"
122126

123127

128+
class VulnerabilityRelatedReference(models.Model):
129+
"""
130+
A reference related to a vulnerability.
131+
"""
132+
133+
vulnerability = models.ForeignKey(
134+
Vulnerability,
135+
on_delete=models.CASCADE,
136+
)
137+
138+
reference = models.ForeignKey(
139+
VulnerabilityReference,
140+
on_delete=models.CASCADE,
141+
)
142+
143+
class Meta:
144+
unique_together = ("vulnerability", "reference")
145+
146+
124147
class Package(PackageURLMixin):
125148
"""
126149
A software package with related vulnerabilities.

vulnerabilities/tests/test_fix_api.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525

2626
from vulnerabilities.models import Package
2727
from vulnerabilities.models import Vulnerability
28+
from vulnerabilities.models import VulnerabilityReference
29+
from vulnerabilities.models import VulnerabilityRelatedReference
2830

2931

3032
class APITestCaseVulnerability(TestCase):
@@ -85,3 +87,23 @@ def test_api_with_single_vulnerability(self):
8587
assert response["name"] == "test-vulnDB"
8688
assert response["version"] == "1.0"
8789
assert response["type"] == "generic"
90+
91+
92+
class CPEApi(TestCase):
93+
def setUp(self):
94+
self.vulnerability = Vulnerability.objects.create(summary="test")
95+
for i in range(0, 10):
96+
ref, _ = VulnerabilityReference.objects.get_or_create(
97+
reference_id=f"cpe:/a:nginx:{i}",
98+
)
99+
VulnerabilityRelatedReference.objects.create(
100+
reference=ref, vulnerability=self.vulnerability
101+
)
102+
103+
def test_api_status(self):
104+
response = self.client.get("/api/cpes/", format="json")
105+
self.assertEqual(status.HTTP_200_OK, response.status_code)
106+
107+
def test_api_response(self):
108+
response = self.client.get("/api/cpes/?cpe=cpe:/a:nginx:9", format="json").data
109+
self.assertEqual(response["count"], 1)

0 commit comments

Comments
 (0)