Skip to content

Commit cd115b3

Browse files
Implement Commit link improver
+ Refactor Improver model to support custom improver + Update Improver documentation Signed-off-by: JafarAkhondali <jafar.akhoondali@gmail.com> Signed-off-by: JafarAkhondali <jafar.akhoondali@gmail.com> Signed-off-by: JafarAkhondali <jafar.akhoondali@gmail.com> Signed-off-by: JafarAkhondali <jafar.akhoondali@gmail.com>
1 parent 26cfb22 commit cd115b3

7 files changed

Lines changed: 115 additions & 7 deletions

File tree

docs/source/contributing.rst

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -454,7 +454,10 @@ Improver
454454
^^^^^^^^^
455455

456456
All the Improvers must inherit from ``Improver`` superclass and implement the
457-
``interesting_advisories`` property and the ``get_inferences`` method.
457+
``interesting_advisories`` property and the ``get_inferences`` method,
458+
unless they are not improving advisory data. In this case they should override
459+
``is_custom_improver`` property to True and implement the ``run`` method.
460+
458461

459462
Writing an improver
460463
---------------------

vulnerabilities/improve_runner.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,23 @@ class ImproveRunner:
3939
def __init__(self, improver_class):
4040
self.improver_class = improver_class
4141

42-
def run(self) -> None:
42+
def __run_advisory_improver(self) -> None:
4343
improver = self.improver_class()
44-
logger.info(f"Running improver: {improver.qualified_name}")
4544
for advisory in improver.interesting_advisories:
4645
inferences = improver.get_inferences(advisory_data=advisory.to_advisory_data())
4746
process_inferences(
4847
inferences=inferences, advisory=advisory, improver_name=improver.qualified_name
4948
)
49+
50+
def __run_custom_improver(self) -> None:
51+
self.improver_class().run()
52+
53+
def run(self) -> None:
54+
logger.info(f"Running improver: {self.improver_class().qualified_name}")
55+
if self.improver_class().is_custom_improver:
56+
self.__run_custom_improver()
57+
else:
58+
self.__run_advisory_improver()
5059
logger.info("Finished improving using %s.", self.improver_class.qualified_name)
5160

5261

vulnerabilities/improver.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,16 @@ class Improver:
110110
required to override the ``interesting_advisories`` property method to return a QuerySet of
111111
``Advisory`` objects. These advisories are then passed to ``get_inferences`` method which is
112112
responsible for returning an iterable of ``Inferences`` for that particular ``Advisory``
113+
114+
Some improvers are related to already imported data, but not related the advisories directly
115+
Such improver must set 'custom_improver' to true and implement the run method in the improver file.
116+
113117
"""
114118

119+
@classproperty
120+
def is_custom_improver(cls):
121+
return False
122+
115123
@classproperty
116124
def qualified_name(cls):
117125
"""
@@ -135,3 +143,11 @@ def get_inferences(self, advisory_data: AdvisoryData) -> Iterable[Inference]:
135143
Subclasses must implement.
136144
"""
137145
raise NotImplementedError
146+
147+
def run(self) -> None:
148+
"""
149+
Runs a custom Improver which doesn't improve the advisory data, and needs custom action.
150+
151+
Subclasses must implement.
152+
"""
153+
raise NotImplementedError

vulnerabilities/improvers/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
# See https://aboutcode.org for more information about nexB OSS projects.
88
#
99

10+
from vulnerabilities.improvers import add_commit_ref
1011
from vulnerabilities.improvers import default
1112
from vulnerabilities.improvers import valid_versions
1213

@@ -24,6 +25,7 @@
2425
valid_versions.IstioImprover,
2526
valid_versions.DebianOvalImprover,
2627
valid_versions.UbuntuOvalImprover,
28+
add_commit_ref.CommitRelationImprover,
2729
]
2830

2931
IMPROVERS_REGISTRY = {x.qualified_name: x for x in IMPROVERS_REGISTRY}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
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/nexB/vulnerablecode for support or download.
7+
# See https://aboutcode.org for more information about nexB OSS projects.
8+
#
9+
10+
import logging
11+
import re
12+
13+
from django.db import transaction
14+
from django.db.models.query import QuerySet
15+
16+
from vulnerabilities.improver import Improver
17+
from vulnerabilities.models import Commit
18+
from vulnerabilities.models import VulnerabilityReference
19+
20+
logger = logging.getLogger(__name__)
21+
22+
"""
23+
Improver that looks for commits related to a vulnerability
24+
"""
25+
26+
27+
class CommitRelationImprover(Improver):
28+
"""
29+
Detect related commits to an advisory by applying a REGEX.
30+
"""
31+
32+
def __init__(self):
33+
# using cached insertion for memory efficiency
34+
self.insert_chunk_size = 500
35+
self.commit_instances = []
36+
37+
@property
38+
def is_custom_improver(cls):
39+
return True
40+
41+
@property
42+
def interesting_references(self) -> QuerySet:
43+
# Regex base coming from: https://github.com/secureIT-project/CVEfixes/
44+
# Below regex is the compatible form for Postgresql
45+
# For now, we are only interested in Bitbucket, Github and Gitlab sources
46+
# TODO: Add other sources such as Apache related sources, Linux kernel, etc.
47+
git_url = r"((https|http)://(bitbucket|github|gitlab)\.(org|com)/([^/]+)/([^/]*))/(commit|commits)/(\w+)#?"
48+
return VulnerabilityReference.objects.filter(
49+
url__iregex=git_url,
50+
)
51+
52+
def __generate_instance(self):
53+
commit_pattern = r"(((?P<repo>(https|http):\/\/(bitbucket|github|gitlab)\.(org|com)\/(?P<owner>[^\/]+)\/(?P<project>[^\/]*))\/(commit|commits)\/(?P<hash>\w+)#?)+)"
54+
for ref in self.interesting_references:
55+
commit_groups = re.search(commit_pattern, ref.url)
56+
yield Commit(
57+
reference=ref,
58+
hash=commit_groups.group("hash"),
59+
)
60+
61+
def __insert_bulk(self) -> None:
62+
if len(self.commit_instances) == 0:
63+
return
64+
65+
with transaction.atomic():
66+
# Ignore_conflicts allows mass
67+
Commit.objects.bulk_create(self.commit_instances, ignore_conflicts=True)
68+
69+
# Empty the cache buffer further inserts
70+
self.commit_instances.clear()
71+
72+
def run(self) -> None:
73+
for i, commit in enumerate(self.__generate_instance()):
74+
self.commit_instances.append(commit)
75+
if len(self.commit_instances) >= self.insert_chunk_size:
76+
self.__insert_bulk()
77+
# Add remaining commits
78+
self.__insert_bulk()

vulnerabilities/migrations/0040_commit.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Generated by Django 4.1.7 on 2023-07-05 15:01
1+
# Generated by Django 4.1.7 on 2023-07-05 17:38
22

33
from django.db import migrations, models
44
import django.db.models.deletion
@@ -32,7 +32,7 @@ class Migration(migrations.Migration):
3232
),
3333
(
3434
"reference",
35-
models.OneToOneField(
35+
models.ForeignKey(
3636
on_delete=django.db.models.deletion.CASCADE,
3737
to="vulnerabilities.vulnerabilityreference",
3838
),

vulnerabilities/models.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -893,7 +893,7 @@ class Commit(models.Model):
893893
Reference to a commit(s) that fixed the vulnerability
894894
"""
895895

896-
reference = models.OneToOneField(
896+
reference = models.ForeignKey(
897897
VulnerabilityReference,
898898
on_delete=models.CASCADE,
899899
)
@@ -907,7 +907,7 @@ class Commit(models.Model):
907907
chain_urls = models.JSONField(
908908
default=list,
909909
help_text="List of URLS used to reach the commit",
910-
blank = True,
910+
blank=True,
911911
)
912912

913913
class Meta:

0 commit comments

Comments
 (0)