Skip to content

Commit 2a70836

Browse files
committed
Migrate from VULCOID to VCID #811
Use uuid instead of base36 Reference: #811 Signed-off-by: Tushar Goel <[email protected]>
1 parent b922c8f commit 2a70836

File tree

5 files changed

+109
-15
lines changed

5 files changed

+109
-15
lines changed
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 & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
from django.core.validators import MinValueValidator
1818
from django.db import models
1919
from django.dispatch import receiver
20-
from django.utils.http import int_to_base36
2120
from packageurl import PackageURL
2221
from packageurl.contrib.django.models import PackageURLMixin
2322
from rest_framework.authtoken.models import Token
@@ -27,6 +26,7 @@
2726
from vulnerabilities.importer import Reference
2827
from vulnerabilities.improver import MAX_CONFIDENCE
2928
from vulnerabilities.severity_systems import SCORING_SYSTEMS
29+
from vulnerabilities.utils import build_vcid
3030

3131
logger = logging.getLogger(__name__)
3232

@@ -41,8 +41,9 @@ class Vulnerability(models.Model):
4141
unique=True,
4242
blank=True,
4343
max_length=20,
44+
default=build_vcid,
4445
help_text="Unique identifier for a vulnerability in the external representation. "
45-
"It is prefixed with VULCOID-",
46+
"It is prefixed with VCID-",
4647
)
4748

4849
summary = models.TextField(
@@ -63,12 +64,6 @@ def severities(self):
6364
for reference in self.references.all():
6465
yield from VulnerabilitySeverity.objects.filter(reference=reference.id)
6566

66-
def save(self, *args, **kwargs):
67-
super().save(*args, **kwargs)
68-
if not self.vulnerability_id:
69-
self.vulnerability_id = f"VULCOID-{int_to_base36(self.id).upper()}"
70-
super().save(update_fields=["vulnerability_id"])
71-
7267
@property
7368
def vulnerable_to(self):
7469
"""

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: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@
1515
import re
1616
from collections import defaultdict
1717
from functools import total_ordering
18+
from hashlib import sha256
1819
from typing import List
1920
from typing import Optional
2021
from typing import Tuple
2122
from unittest.mock import MagicMock
23+
from uuid import uuid4
2224

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

0 commit comments

Comments
 (0)