Skip to content

Commit d62f377

Browse files
authored
Refactor GitlabDataSource to work with browser extension (#1524)
* -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 <[email protected]> * Add another check to avoid iterating on None Signed-off-by: Michael Ehab Mikhail <[email protected]> * Restore test file and update gitlab tests Signed-off-by: Michael Ehab Mikhail <[email protected]> --------- Signed-off-by: Michael Ehab Mikhail <[email protected]>
1 parent b7a7237 commit d62f377

File tree

2 files changed

+56
-66
lines changed

2 files changed

+56
-66
lines changed

vulntotal/datasources/gitlab.py

Lines changed: 29 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -41,18 +41,17 @@ 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-
)
48+
directory_files = fetch_directory_contents(casesensitive_package_slug)
49+
50+
if directory_files:
51+
yml_files = [file for file in directory_files if file["name"].endswith(".yml")]
52+
53+
interesting_advisories = parse_interesting_advisories(yml_files, purl)
5454
return interesting_advisories
55-
clear_download(location)
5655

5756
@classmethod
5857
def supported_ecosystem(cls):
@@ -68,6 +67,21 @@ def supported_ecosystem(cls):
6867
}
6968

7069

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

94108

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-
132109
def get_casesensitive_slug(path, package_slug):
133110
payload = [
134111
{
@@ -186,26 +163,22 @@ def get_casesensitive_slug(path, package_slug):
186163
has_next = paginated_tree["pageInfo"]["hasNextPage"]
187164

188165

189-
def parse_interesting_advisories(location, purl, delete_download=False) -> Iterable[VendorData]:
166+
def parse_interesting_advisories(yml_files, purl) -> Iterable[VendorData]:
190167
"""
191168
Parses advisories from YAML files in a given location that match a given version.
192169
193170
Parameters:
194-
location: A Path object representing the location of the YAML files.
171+
yml_files: An array having the paths of yml files to parse.
195172
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.
198173
199174
Yields:
200175
VendorData instance containing the advisory information for the package.
201176
"""
202177
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)
178+
179+
for file in yml_files:
180+
yml_data = fetch_yaml(file["path"])
181+
gitlab_advisory = saneyaml.load(yml_data)
209182
affected_range = gitlab_advisory["affected_range"]
210183
if gitlab_constraints_satisfied(affected_range, version):
211184
yield VendorData(
@@ -214,5 +187,3 @@ def parse_interesting_advisories(location, purl, delete_download=False) -> Itera
214187
affected_versions=[affected_range],
215188
fixed_versions=gitlab_advisory["fixed_versions"],
216189
)
217-
if delete_download:
218-
clear_download(location)

vulntotal/tests/test_gitlab.py

Lines changed: 27 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,36 @@ 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-2019-8341.yml", "path": "path/to/CVE-2019-8341.yml"},
61+
{"name": "CVE-2020-28493.yml", "path": "path/to/CVE-2020-28493.yml"},
4662
]
63+
64+
results = [adv.to_dict() for adv in gitlab.parse_interesting_advisories(yml_files, purl)]
65+
4766
expected_file = self.get_test_loc("parsed_advisory-expected.json", must_exist=False)
4867
util_tests.check_results_against_json(results, expected_file)

0 commit comments

Comments
 (0)