Skip to content

Commit 683233c

Browse files
committed
Migrate from VULCOID to VCID #811
Use uuid instead of base36 Reference: #811 Signed-off-by: Tushar Goel <tushar.goel.dav@gmail.com>
1 parent bf17aa4 commit 683233c

File tree

9 files changed

+124
-28
lines changed

9 files changed

+124
-28
lines changed

setup.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ install_requires =
7878
defusedxml>=0.7.1
7979
Markdown>=3.3.0
8080
dateparser>=1.1.1
81+
shortuuid>=1.0.0
8182

8283
# networking
8384
GitPython>=3.1.17
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Generated by Django 4.0.4 on 2022-09-06 11:22
2+
3+
from django.db import migrations, models
4+
import vulnerabilities.models
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
('vulnerabilities', '0021_alter_vulnerabilityreference_url'),
11+
]
12+
13+
operations = [
14+
migrations.AlterField(
15+
model_name='vulnerability',
16+
name='vulnerability_id',
17+
field=models.CharField(blank=True, default=vulnerabilities.models.build_vcid, help_text='Unique identifier for a vulnerability in the external representation. It is prefixed with VCID-', max_length=20, unique=True),
18+
),
19+
]
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from django.db import migrations
2+
from django.db.models import Q
3+
4+
from vulnerabilities.utils import build_vcid
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
('vulnerabilities', '0022_alter_vulnerability_vulnerability_id'),
11+
]
12+
13+
def save_vulnerability_id(apps, schema_editor):
14+
Vulnerabilities = apps.get_model("vulnerabilities", "Vulnerability")
15+
for vulnerability in Vulnerabilities.objects.filter(~Q(vulnerability_id__startswith="VCID-")):
16+
vulnerability.vulnerability_id = build_vcid()
17+
vulnerability.save()
18+
19+
operations = [
20+
migrations.RunPython(save_vulnerability_id, migrations.RunPython.noop)
21+
]

vulnerabilities/models.py

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,13 @@
1010
import hashlib
1111
import json
1212
import logging
13-
import uuid
1413

1514
from django.conf import settings
1615
from django.core.exceptions import ValidationError
1716
from django.core.validators import MaxValueValidator
1817
from django.core.validators import MinValueValidator
1918
from django.db import models
2019
from django.dispatch import receiver
21-
from django.utils.http import int_to_base36
2220
from packageurl import PackageURL
2321
from packageurl.contrib.django.models import PackageURLMixin
2422
from rest_framework.authtoken.models import Token
@@ -28,6 +26,7 @@
2826
from vulnerabilities.importer import Reference
2927
from vulnerabilities.improver import MAX_CONFIDENCE
3028
from vulnerabilities.severity_systems import SCORING_SYSTEMS
29+
from vulnerabilities.utils import build_vcid
3130

3231
logger = logging.getLogger(__name__)
3332

@@ -42,8 +41,9 @@ class Vulnerability(models.Model):
4241
unique=True,
4342
blank=True,
4443
max_length=20,
44+
default=build_vcid,
4545
help_text="Unique identifier for a vulnerability in the external representation. "
46-
"It is prefixed with VULCOID-",
46+
"It is prefixed with VCID-",
4747
)
4848

4949
summary = models.TextField(
@@ -59,12 +59,6 @@ class Vulnerability(models.Model):
5959
through="PackageRelatedVulnerability",
6060
)
6161

62-
def save(self, *args, **kwargs):
63-
super().save(*args, **kwargs)
64-
if not self.vulnerability_id:
65-
self.vulnerability_id = f"VULCOID-{int_to_base36(self.id).upper()}"
66-
super().save(update_fields=["vulnerability_id"])
67-
6862
@property
6963
def vulnerable_to(self):
7064
"""

vulnerabilities/templates/index.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,16 +84,16 @@
8484
<div class="dropdown-menu dropdown-instructions-width" id="dropdown-menu4" role="menu">
8585
<div class="dropdown-content dropdown-instructions-box-shadow">
8686
<div class="dropdown-item">
87-
<div>Search for comprehensive information for a <span class="inline-code">VULCOID</span> (VulnerableCode Database ID). <span class="is-italic">(Only the first of these methods requires that the input be all uppercase.)</span>
87+
<div>Search for comprehensive information for a <span class="inline-code">VCID</span> (VulnerableCode ID). <span class="is-italic">(Only the first of these methods requires that the input be all uppercase.)</span>
8888
<ul>
8989
<li>
90-
Search for a specific <span class="inline-code">VULCOID</span> (e.g., "VULCOID-1").
90+
Search for a specific <span class="inline-code">VCID</span> (e.g., "VCID-fe0c3d75-204c-4e5d-a7f7-b89f1605e6a1").
9191
</li>
9292
<li>
93-
Search for all <span class="inline-code">VULCOID</span>s that are associated with a specific <span class="inline-code">CVE</span> (e.g., "CVE-2009-3898") or <span class="inline-code">GHSA</span> (e.g., "GHSA-2qrg-x229-3v8q").
93+
Search for all <span class="inline-code">VCID</span>s that are associated with a specific <span class="inline-code">CVE</span> (e.g., "CVE-2009-3898") or <span class="inline-code">GHSA</span> (e.g., "GHSA-2qrg-x229-3v8q").
9494
</li>
9595
<li>
96-
Search for "CVE" or "GHSA" -- this will return all <span class="inline-code">VULCOID</span>s that are associated with one or more <span class="inline-code">CVE</span>s or <span class="inline-code">GHSA</span>s, respectively.
96+
Search for "CVE" or "GHSA" -- this will return all <span class="inline-code">VCID</span>s that are associated with one or more <span class="inline-code">CVE</span>s or <span class="inline-code">GHSA</span>s, respectively.
9797
</li>
9898
</ul>
9999
</div>

vulnerabilities/templates/vulnerabilities.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,16 @@
2121
<div class="dropdown-menu dropdown-instructions-width" id="dropdown-menu4" role="menu">
2222
<div class="dropdown-content dropdown-instructions-box-shadow">
2323
<div class="dropdown-item">
24-
<div>Search for comprehensive information for a <span class="inline-code">VULCOID</span> (VulnerableCode Database ID). <span class="is-italic">(Only the first of these methods requires that the input be all uppercase.)</span>
24+
<div>Search for comprehensive information for a <span class="inline-code">VCID</span> (VulnerableCode ID). <span class="is-italic">(Only the first of these methods requires that the input be all uppercase.)</span>
2525
<ul>
2626
<li>
27-
Search for a specific <span class="inline-code">VULCOID</span> (e.g., "VULCOID-1").
27+
Search for a specific <span class="inline-code">VCID</span> (e.g., "VCID-fe0c3d75-204c-4e5d-a7f7-b89f1605e6a1").
2828
</li>
2929
<li>
30-
Search for all <span class="inline-code">VULCOID</span>s that are associated with a specific <span class="inline-code">CVE</span> (e.g., "CVE-2009-3898") or <span class="inline-code">GHSA</span> (e.g., "GHSA-2qrg-x229-3v8q").
30+
Search for all <span class="inline-code">VCID</span>s that are associated with a specific <span class="inline-code">CVE</span> (e.g., "CVE-2009-3898") or <span class="inline-code">GHSA</span> (e.g., "GHSA-2qrg-x229-3v8q").
3131
</li>
3232
<li>
33-
Search for "CVE" or "GHSA" -- this will return all <span class="inline-code">VULCOID</span>s that are associated with one or more <span class="inline-code">CVE</span>s or <span class="inline-code">GHSA</span>s, respectively.
33+
Search for "CVE" or "GHSA" -- this will return all <span class="inline-code">VCID</span>s that are associated with one or more <span class="inline-code">CVE</span>s or <span class="inline-code">GHSA</span>s, respectively.
3434
</li>
3535
</ul>
3636
</div>

vulnerabilities/templates/vulnerability.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,16 @@
2121
<div class="dropdown-menu dropdown-instructions-width" id="dropdown-menu4" role="menu">
2222
<div class="dropdown-content dropdown-instructions-box-shadow">
2323
<div class="dropdown-item">
24-
<div>Search for comprehensive information for a <span class="inline-code">VULCOID</span> (VulnerableCode Database ID). <span class="is-italic">(Only the first of these methods requires that the input be all uppercase.)</span>
24+
<div>Search for comprehensive information for a <span class="inline-code">VCID</span> (VulnerableCode ID). <span class="is-italic">(Only the first of these methods requires that the input be all uppercase.)</span>
2525
<ul>
2626
<li>
27-
Search for a specific <span class="inline-code">VULCOID</span> (e.g., "VULCOID-1").
27+
Search for a specific <span class="inline-code">VCID</span> (e.g., "VCID-fe0c3d75-204c-4e5d-a7f7-b89f1605e6a1").
2828
</li>
2929
<li>
30-
Search for all <span class="inline-code">VULCOID</span>s that are associated with a specific <span class="inline-code">CVE</span> (e.g., "CVE-2009-3898") or <span class="inline-code">GHSA</span> (e.g., "GHSA-2qrg-x229-3v8q").
30+
Search for all <span class="inline-code">VCID</span>s that are associated with a specific <span class="inline-code">CVE</span> (e.g., "CVE-2009-3898") or <span class="inline-code">GHSA</span> (e.g., "GHSA-2qrg-x229-3v8q").
3131
</li>
3232
<li>
33-
Search for "CVE" or "GHSA" -- this will return all <span class="inline-code">VULCOID</span>s that are associated with one or more <span class="inline-code">CVE</span>s or <span class="inline-code">GHSA</span>s, respectively.
33+
Search for "CVE" or "GHSA" -- this will return all <span class="inline-code">VCID</span>s that are associated with one or more <span class="inline-code">CVE</span>s or <span class="inline-code">GHSA</span>s, respectively.
3434
</li>
3535
</ul>
3636
</div>

vulnerabilities/tests/test_fix_api.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ def test_api_with_single_vulnerability(self):
5959
).data
6060
assert response == {
6161
"url": f"http://testserver/api/vulnerabilities/{self.vulnerability.id}",
62-
"vulnerability_id": f"VULCOID-{int_to_base36(self.vulnerability.id).upper()}",
62+
"vulnerability_id": self.vulnerability.vulnerability_id,
6363
"summary": "test",
6464
"aliases": [],
6565
"fixed_packages": [
@@ -84,7 +84,7 @@ def test_api_with_single_vulnerability_with_filters(self):
8484
).data
8585
assert response == {
8686
"url": f"http://testserver/api/vulnerabilities/{self.vulnerability.id}",
87-
"vulnerability_id": f"VULCOID-{int_to_base36(self.vulnerability.id).upper()}",
87+
"vulnerability_id": self.vulnerability.vulnerability_id,
8888
"summary": "test",
8989
"aliases": [],
9090
"fixed_packages": [
@@ -182,7 +182,7 @@ def test_api_with_single_vulnerability_and_fixed_package(self):
182182
"affected_by_vulnerabilities": [
183183
{
184184
"url": f"http://testserver/api/vulnerabilities/{self.vuln1.id}",
185-
"vulnerability_id": f"VULCOID-{int_to_base36(self.vuln1.id).upper()}",
185+
"vulnerability_id": self.vuln1.vulnerability_id,
186186
"summary": "test-vuln1",
187187
"references": [],
188188
"fixed_packages": [],
@@ -191,7 +191,7 @@ def test_api_with_single_vulnerability_and_fixed_package(self):
191191
"fixing_vulnerabilities": [
192192
{
193193
"url": f"http://testserver/api/vulnerabilities/{self.vuln.id}",
194-
"vulnerability_id": f"VULCOID-{int_to_base36(self.vuln.id).upper()}",
194+
"vulnerability_id": self.vuln.vulnerability_id,
195195
"summary": "test-vuln",
196196
"references": [],
197197
"fixed_packages": [
@@ -206,7 +206,7 @@ def test_api_with_single_vulnerability_and_fixed_package(self):
206206
"unresolved_vulnerabilities": [
207207
{
208208
"url": f"http://testserver/api/vulnerabilities/{self.vuln1.id}",
209-
"vulnerability_id": f"VULCOID-{int_to_base36(self.vuln1.id).upper()}",
209+
"vulnerability_id": self.vuln1.vulnerability_id,
210210
"summary": "test-vuln1",
211211
"references": [],
212212
"fixed_packages": [],
@@ -228,7 +228,7 @@ def test_api_with_single_vulnerability_and_vulnerable_package(self):
228228
"affected_by_vulnerabilities": [
229229
{
230230
"url": f"http://testserver/api/vulnerabilities/{self.vuln.id}",
231-
"vulnerability_id": f"VULCOID-{int_to_base36(self.vuln.id).upper()}",
231+
"vulnerability_id": self.vuln.vulnerability_id,
232232
"summary": "test-vuln",
233233
"references": [],
234234
"fixed_packages": [
@@ -244,7 +244,7 @@ def test_api_with_single_vulnerability_and_vulnerable_package(self):
244244
"unresolved_vulnerabilities": [
245245
{
246246
"url": f"http://testserver/api/vulnerabilities/{self.vuln.id}",
247-
"vulnerability_id": f"VULCOID-{int_to_base36(self.vuln.id).upper()}",
247+
"vulnerability_id": self.vuln.vulnerability_id,
248248
"summary": "test-vuln",
249249
"references": [],
250250
"fixed_packages": [

vulnerabilities/utils.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,3 +351,64 @@ def resolve_version_range(
351351
)
352352
continue
353353
return affected_versions, unaffected_versions
354+
355+
356+
from hashlib import sha256
357+
from uuid import uuid4
358+
359+
360+
def build_vcid(prefix="VCID"):
361+
"""
362+
Return a new VulnerableCode VCID unique identifier string using the ``prefix``.
363+
364+
For example::
365+
>>> import re
366+
>>> vcid = build_vcid()
367+
>>> # VCID-6npv-94wz-hhuq
368+
>>> assert re.match('VCID(-[a-z1-9]{4}){3}', vcid), vcid
369+
"""
370+
# we keep only 64 bits (e.g. 8 bytes)
371+
uid = sha256(uuid4().bytes).digest()[:8]
372+
# we keep only 12 encoded bytes (which corresponds to 60 bits)
373+
uid = base32_custom(uid)[:12].decode("utf-8").lower()
374+
return f"{prefix}-{uid[:4]}-{uid[4:8]}-{uid[8:12]}"
375+
376+
377+
_base32_alphabet = b"ABCDEFGHJKMNPQRSTUVWXYZ123456789"
378+
_base32_table = None
379+
380+
381+
def base32_custom(btes):
382+
"""
383+
Encode the ``btes`` bytes object using a Base32 encoding using a custom
384+
alphabet and return a bytes object.
385+
386+
Code copied and modified from the Python Standard Library:
387+
base64.b32encode function
388+
389+
SPDX-License-Identifier: Python-2.0
390+
Copyright (c) The Python Software Foundation
391+
392+
For example::
393+
>>> assert base32_custom(b'abcd') == b'ABTZE25E', base32_custom(b'abcd')
394+
>>> assert base32_custom(b'abcde00000xxxxxPPPPP') == b'PFUGG3DFGA2DAPBTSB6HT8D2MBJFAXCT'
395+
"""
396+
global _base32_table
397+
# Delay the initialization of the table to not waste memory
398+
# if the function is never called
399+
if _base32_table is None:
400+
b32tab = [bytes((i,)) for i in _base32_alphabet]
401+
_base32_table = [a + b for a in b32tab for b in b32tab]
402+
403+
encoded = bytearray()
404+
from_bytes = int.from_bytes
405+
406+
for i in range(0, len(btes), 5):
407+
c = from_bytes(btes[i : i + 5], "big")
408+
encoded += (
409+
_base32_table[c >> 30]
410+
+ _base32_table[(c >> 20) & 0x3FF] # bits 1 - 10
411+
+ _base32_table[(c >> 10) & 0x3FF] # bits 11 - 20
412+
+ _base32_table[c & 0x3FF] # bits 21 - 30 # bits 31 - 40
413+
)
414+
return bytes(encoded)

0 commit comments

Comments
 (0)