Skip to content

Commit 8bc0445

Browse files
committed
-Refactor GitlabDataSource to improve code readability and performance
-Update Gitlab Datasource tests -Reorder test_parse_interesting_advisories test files -Format changed files with black -Merge fetch yml logic and update method doc Signed-off-by: Michael Ehab Mikhail <michael.ehab@hotmail.com>
1 parent 98b57aa commit 8bc0445

3 files changed

Lines changed: 56 additions & 96 deletions

File tree

vulntotal/datasources/gitlab.py

Lines changed: 30 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -41,18 +41,16 @@ def datasource_advisory(self, purl) -> Iterable[VendorData]:
4141
VendorData instance containing the advisory information for the package.
4242
"""
4343
package_slug = get_package_slug(purl)
44-
location = download_subtree(package_slug, speculative_execution=True)
45-
if not location:
46-
clear_download(location)
44+
directory_files = fetch_directory_contents(package_slug)
45+
if not directory_files:
4746
path = self.supported_ecosystem()[purl.type]
4847
casesensitive_package_slug = get_casesensitive_slug(path, package_slug)
49-
location = download_subtree(casesensitive_package_slug)
50-
if location:
51-
interesting_advisories = parse_interesting_advisories(
52-
location, purl, delete_download=True
53-
)
54-
return interesting_advisories
55-
clear_download(location)
48+
directory_files = fetch_directory_contents(casesensitive_package_slug)
49+
50+
yml_files = [file for file in directory_files if file["name"].endswith(".yml")]
51+
52+
interesting_advisories = parse_interesting_advisories(yml_files, purl)
53+
return interesting_advisories
5654

5755
@classmethod
5856
def supported_ecosystem(cls):
@@ -68,6 +66,21 @@ def supported_ecosystem(cls):
6866
}
6967

7068

69+
def fetch_directory_contents(package_slug):
70+
url = f"https://gitlab.com/api/v4/projects/12006272/repository/tree?path={package_slug}"
71+
response = requests.get(url)
72+
if response.status_code == 200:
73+
return response.json()
74+
75+
76+
def fetch_yaml(file_path):
77+
response = requests.get(
78+
f"https://gitlab.com/gitlab-org/security-products/gemnasium-db/-/raw/master/{file_path}"
79+
)
80+
if response.status_code == 200:
81+
return response.text
82+
83+
7184
def get_package_slug(purl):
7285
"""
7386
Constructs a package slug from a given purl.
@@ -92,43 +105,6 @@ def get_package_slug(purl):
92105
return f"{ecosystem}/{package_name}"
93106

94107

95-
def download_subtree(package_slug: str, speculative_execution=False):
96-
"""
97-
Downloads and extracts a tar file from a given package slug.
98-
99-
Parameters:
100-
package_slug: A string representing the package slug to query.
101-
speculative_execution: A boolean indicating whether to log errors or not.
102-
103-
Returns:
104-
A Path object representing the extracted location, or None if an error occurs.
105-
"""
106-
url = f"https://gitlab.com/gitlab-org/security-products/gemnasium-db/-/archive/master/gemnasium-db-master.tar.gz?path={package_slug}"
107-
response = fetch(url)
108-
if os.path.getsize(response.location) > 0:
109-
extracted_location = Path(response.location).parent.joinpath(
110-
"temp_vulntotal_gitlab_datasource"
111-
)
112-
with tarfile.open(response.location, "r") as file_obj:
113-
file_obj.extractall(extracted_location)
114-
os.remove(response.location)
115-
return extracted_location
116-
if not speculative_execution:
117-
logger.error(f"{package_slug} doesn't exist")
118-
os.remove(response.location)
119-
120-
121-
def clear_download(location):
122-
"""
123-
Deletes a directory and its contents.
124-
125-
Parameters:
126-
location: A Path object representing the directory to delete.
127-
"""
128-
if location:
129-
shutil.rmtree(location)
130-
131-
132108
def get_casesensitive_slug(path, package_slug):
133109
payload = [
134110
{
@@ -180,32 +156,28 @@ def get_casesensitive_slug(path, package_slug):
180156

181157
# If the namespace/subfolder contains multiple packages, then progressive transverse through folders tree
182158
if package_slug.lower().startswith(slug_flatpath.lower()):
183-
return get_gitlab_style_slug(slug_flatpath, package_slug)
159+
return get_casesensitive_slug(slug_flatpath, package_slug)
184160

185161
payload[0]["variables"]["nextPageCursor"] = paginated_tree["pageInfo"]["endCursor"]
186162
has_next = paginated_tree["pageInfo"]["hasNextPage"]
187163

188164

189-
def parse_interesting_advisories(location, purl, delete_download=False) -> Iterable[VendorData]:
165+
def parse_interesting_advisories(yml_files, purl) -> Iterable[VendorData]:
190166
"""
191167
Parses advisories from YAML files in a given location that match a given version.
192168
193169
Parameters:
194-
location: A Path object representing the location of the YAML files.
170+
yml_files: An array having the paths of yml files to parse.
195171
purl: PURL for the advisory.
196-
version: A string representing the version to check against the affected range.
197-
delete_download: A boolean indicating whether to delete the downloaded files after parsing.
198172
199173
Yields:
200174
VendorData instance containing the advisory information for the package.
201175
"""
202176
version = purl.version
203-
path = Path(location)
204-
pattern = "**/*.yml"
205-
files = [p for p in path.glob(pattern) if p.is_file()]
206-
for file in sorted(files):
207-
with open(file) as f:
208-
gitlab_advisory = saneyaml.load(f)
177+
178+
for file in yml_files:
179+
yml_data = fetch_yaml(file["path"])
180+
gitlab_advisory = saneyaml.load(yml_data)
209181
affected_range = gitlab_advisory["affected_range"]
210182
if gitlab_constraints_satisfied(affected_range, version):
211183
yield VendorData(
@@ -214,5 +186,3 @@ def parse_interesting_advisories(location, purl, delete_download=False) -> Itera
214186
affected_versions=[affected_range],
215187
fixed_versions=gitlab_advisory["fixed_versions"],
216188
)
217-
if delete_download:
218-
clear_download(location)

vulntotal/tests/test_data/gitlab/temp_vulntotal_gitlab_datasource/gemnasium-db-master-pypi-Jinja2/pypi/Jinja2/CVE-2019-8341.yml

Lines changed: 0 additions & 28 deletions
This file was deleted.

vulntotal/tests/test_gitlab.py

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
#
21
# Copyright (c) nexB Inc. and others. All rights reserved.
32
# VulnerableCode is a trademark of nexB Inc.
43
# SPDX-License-Identifier: Apache-2.0
@@ -8,6 +7,7 @@
87
#
98

109
from pathlib import Path
10+
from unittest import mock
1111

1212
from commoncode import testcase
1313
from packageurl import PackageURL
@@ -32,17 +32,35 @@ def test_generate_package_advisory_url(self):
3232
expected_file = self.get_test_loc("package_advisory_url-expected.json", must_exist=False)
3333
util_tests.check_results_against_json(results, expected_file)
3434

35-
def test_parse_html_advisory(self):
35+
@mock.patch("vulntotal.datasources.gitlab.fetch_yaml")
36+
def test_parse_interesting_advisories(self, mock_fetch_yaml):
37+
# Mock the yaml file responses
3638
advisory_folder = (
3739
Path(__file__)
3840
.resolve()
39-
.parent.joinpath("test_data/gitlab/temp_vulntotal_gitlab_datasource")
40-
)
41-
results = [
42-
adv.to_dict()
43-
for adv in gitlab.parse_interesting_advisories(
44-
advisory_folder, PackageURL("generic", "namespace", "test", "0.1.1"), False
41+
.parent.joinpath(
42+
"test_data/gitlab/temp_vulntotal_gitlab_datasource/gemnasium-db-master-pypi-Jinja2/pypi/Jinja2"
4543
)
44+
)
45+
yaml_files = []
46+
sorted_files = sorted(advisory_folder.iterdir(), key=lambda x: x.name)
47+
for file in sorted_files:
48+
if file.suffix == ".yml":
49+
with open(file, "r") as f:
50+
yaml_files.append(f.read())
51+
52+
mock_fetch_yaml.side_effect = yaml_files
53+
54+
purl = PackageURL("generic", "namespace", "test", "0.1.1")
55+
56+
yml_files = [
57+
{"name": "CVE-2014-1402.yml", "path": "path/to/CVE-2014-1402.yml"},
58+
{"name": "CVE-2016-10745.yml", "path": "path/to/CVE-2016-10745.yml"},
59+
{"name": "CVE-2019-10906.yml", "path": "path/to/CVE-2019-10906.yml"},
60+
{"name": "CVE-2020-28493.yml", "path": "path/to/CVE-2020-28493.yml"},
4661
]
62+
63+
results = [adv.to_dict() for adv in gitlab.parse_interesting_advisories(yml_files, purl)]
64+
4765
expected_file = self.get_test_loc("parsed_advisory-expected.json", must_exist=False)
4866
util_tests.check_results_against_json(results, expected_file)

0 commit comments

Comments
 (0)