scanoss 1.12.2__py3-none-any.whl → 1.43.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- protoc_gen_swagger/__init__.py +13 -13
- protoc_gen_swagger/options/__init__.py +13 -13
- protoc_gen_swagger/options/annotations_pb2.py +18 -12
- protoc_gen_swagger/options/annotations_pb2.pyi +48 -0
- protoc_gen_swagger/options/annotations_pb2_grpc.py +20 -0
- protoc_gen_swagger/options/openapiv2_pb2.py +110 -99
- protoc_gen_swagger/options/openapiv2_pb2.pyi +1317 -0
- protoc_gen_swagger/options/openapiv2_pb2_grpc.py +20 -0
- scanoss/__init__.py +18 -18
- scanoss/api/__init__.py +17 -17
- scanoss/api/common/__init__.py +17 -17
- scanoss/api/common/v2/__init__.py +17 -17
- scanoss/api/common/v2/scanoss_common_pb2.py +49 -20
- scanoss/api/common/v2/scanoss_common_pb2_grpc.py +25 -0
- scanoss/api/components/__init__.py +17 -17
- scanoss/api/components/v2/__init__.py +17 -17
- scanoss/api/components/v2/scanoss_components_pb2.py +68 -43
- scanoss/api/components/v2/scanoss_components_pb2_grpc.py +83 -22
- scanoss/api/cryptography/v2/scanoss_cryptography_pb2.py +136 -21
- scanoss/api/cryptography/v2/scanoss_cryptography_pb2_grpc.py +766 -13
- scanoss/api/dependencies/__init__.py +17 -17
- scanoss/api/dependencies/v2/__init__.py +17 -17
- scanoss/api/dependencies/v2/scanoss_dependencies_pb2.py +56 -29
- scanoss/api/dependencies/v2/scanoss_dependencies_pb2_grpc.py +94 -8
- scanoss/api/geoprovenance/__init__.py +23 -0
- scanoss/api/geoprovenance/v2/__init__.py +23 -0
- scanoss/api/geoprovenance/v2/scanoss_geoprovenance_pb2.py +92 -0
- scanoss/api/geoprovenance/v2/scanoss_geoprovenance_pb2_grpc.py +381 -0
- scanoss/api/licenses/__init__.py +23 -0
- scanoss/api/licenses/v2/__init__.py +23 -0
- scanoss/api/licenses/v2/scanoss_licenses_pb2.py +84 -0
- scanoss/api/licenses/v2/scanoss_licenses_pb2_grpc.py +302 -0
- scanoss/api/scanning/__init__.py +17 -17
- scanoss/api/scanning/v2/__init__.py +17 -17
- scanoss/api/scanning/v2/scanoss_scanning_pb2.py +42 -13
- scanoss/api/scanning/v2/scanoss_scanning_pb2_grpc.py +86 -7
- scanoss/api/semgrep/__init__.py +17 -17
- scanoss/api/semgrep/v2/__init__.py +17 -17
- scanoss/api/semgrep/v2/scanoss_semgrep_pb2.py +50 -23
- scanoss/api/semgrep/v2/scanoss_semgrep_pb2_grpc.py +151 -16
- scanoss/api/vulnerabilities/__init__.py +17 -17
- scanoss/api/vulnerabilities/v2/__init__.py +17 -17
- scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2.py +78 -31
- scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2_grpc.py +282 -18
- scanoss/cli.py +2359 -370
- scanoss/components.py +187 -94
- scanoss/constants.py +22 -0
- scanoss/cryptography.py +308 -0
- scanoss/csvoutput.py +91 -58
- scanoss/cyclonedx.py +221 -63
- scanoss/data/build_date.txt +1 -1
- scanoss/data/osadl-copyleft.json +133 -0
- scanoss/data/scanoss-settings-schema.json +254 -0
- scanoss/delta.py +197 -0
- scanoss/export/__init__.py +23 -0
- scanoss/export/dependency_track.py +227 -0
- scanoss/file_filters.py +582 -0
- scanoss/filecount.py +75 -69
- scanoss/gitlabqualityreport.py +214 -0
- scanoss/header_filter.py +563 -0
- scanoss/inspection/__init__.py +23 -0
- scanoss/inspection/policy_check/__init__.py +0 -0
- scanoss/inspection/policy_check/dependency_track/__init__.py +0 -0
- scanoss/inspection/policy_check/dependency_track/project_violation.py +479 -0
- scanoss/inspection/policy_check/policy_check.py +222 -0
- scanoss/inspection/policy_check/scanoss/__init__.py +0 -0
- scanoss/inspection/policy_check/scanoss/copyleft.py +243 -0
- scanoss/inspection/policy_check/scanoss/undeclared_component.py +309 -0
- scanoss/inspection/summary/__init__.py +0 -0
- scanoss/inspection/summary/component_summary.py +170 -0
- scanoss/inspection/summary/license_summary.py +191 -0
- scanoss/inspection/summary/match_summary.py +341 -0
- scanoss/inspection/utils/file_utils.py +44 -0
- scanoss/inspection/utils/license_utils.py +123 -0
- scanoss/inspection/utils/markdown_utils.py +63 -0
- scanoss/inspection/utils/scan_result_processor.py +417 -0
- scanoss/osadl.py +125 -0
- scanoss/results.py +275 -0
- scanoss/scancodedeps.py +87 -38
- scanoss/scanner.py +431 -539
- scanoss/scanners/__init__.py +23 -0
- scanoss/scanners/container_scanner.py +476 -0
- scanoss/scanners/folder_hasher.py +358 -0
- scanoss/scanners/scanner_config.py +73 -0
- scanoss/scanners/scanner_hfh.py +252 -0
- scanoss/scanoss_settings.py +337 -0
- scanoss/scanossapi.py +140 -101
- scanoss/scanossbase.py +59 -22
- scanoss/scanossgrpc.py +799 -251
- scanoss/scanpostprocessor.py +294 -0
- scanoss/scantype.py +22 -21
- scanoss/services/dependency_track_service.py +132 -0
- scanoss/spdxlite.py +532 -174
- scanoss/threadeddependencies.py +148 -47
- scanoss/threadedscanning.py +53 -37
- scanoss/utils/__init__.py +23 -0
- scanoss/utils/abstract_presenter.py +103 -0
- scanoss/utils/crc64.py +96 -0
- scanoss/utils/file.py +84 -0
- scanoss/utils/scanoss_scan_results_utils.py +41 -0
- scanoss/utils/simhash.py +198 -0
- scanoss/winnowing.py +241 -63
- {scanoss-1.12.2.dist-info → scanoss-1.43.1.dist-info}/METADATA +18 -9
- scanoss-1.43.1.dist-info/RECORD +110 -0
- {scanoss-1.12.2.dist-info → scanoss-1.43.1.dist-info}/WHEEL +1 -1
- scanoss-1.12.2.dist-info/RECORD +0 -58
- {scanoss-1.12.2.dist-info → scanoss-1.43.1.dist-info}/entry_points.txt +0 -0
- {scanoss-1.12.2.dist-info → scanoss-1.43.1.dist-info/licenses}/LICENSE +0 -0
- {scanoss-1.12.2.dist-info → scanoss-1.43.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2024, SCANOSS
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in
|
|
14
|
+
all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
22
|
+
THE SOFTWARE.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from typing import List, Tuple
|
|
26
|
+
|
|
27
|
+
from packageurl import PackageURL
|
|
28
|
+
from packageurl.contrib import purl2url
|
|
29
|
+
|
|
30
|
+
from .scanoss_settings import BomEntry, ScanossSettings
|
|
31
|
+
from .scanossbase import ScanossBase
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _get_match_type_message(result_path: str, bom_entry: BomEntry, action: str) -> str:
|
|
35
|
+
"""
|
|
36
|
+
Compose message based on match type
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
result_path (str): Path of the scan result
|
|
40
|
+
bom_entry (BomEntry): BOM entry to compare with
|
|
41
|
+
action (str): Post processing action being performed
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
str: The message to be printed
|
|
45
|
+
"""
|
|
46
|
+
if bom_entry.get('path') and bom_entry.get('purl'):
|
|
47
|
+
message = f"{action} '{result_path}'. Full match found."
|
|
48
|
+
elif bom_entry.get('purl'):
|
|
49
|
+
message = f"{action} '{result_path}'. Found PURL match."
|
|
50
|
+
else:
|
|
51
|
+
message = f"{action} '{result_path}'. Found path match."
|
|
52
|
+
return message
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _is_full_match(result_path: str, result_purls: List[str], bom_entry: BomEntry) -> bool:
|
|
56
|
+
"""
|
|
57
|
+
Check if path and purl matches fully with the bom entry
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
result_path (str): Scan result path
|
|
61
|
+
result_purls (List[str]): Scan result purls
|
|
62
|
+
bom_entry (BomEntry): BOM entry to compare with
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
bool: True if the path and purl match, False otherwise
|
|
66
|
+
"""
|
|
67
|
+
if not result_purls:
|
|
68
|
+
return False
|
|
69
|
+
return bool(
|
|
70
|
+
(bom_entry.get('purl') and bom_entry.get('path'))
|
|
71
|
+
and (bom_entry.get('path') == result_path)
|
|
72
|
+
and (bom_entry.get('purl') in result_purls)
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class ScanPostProcessor(ScanossBase):
|
|
77
|
+
"""
|
|
78
|
+
Handles post-processing of the scan results
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
def __init__(
|
|
82
|
+
self,
|
|
83
|
+
scan_settings: ScanossSettings,
|
|
84
|
+
debug: bool = False,
|
|
85
|
+
trace: bool = False,
|
|
86
|
+
quiet: bool = False,
|
|
87
|
+
results: dict = None,
|
|
88
|
+
):
|
|
89
|
+
"""
|
|
90
|
+
Args:
|
|
91
|
+
scan_settings (ScanossSettings): Scan settings object
|
|
92
|
+
debug (bool, optional): Debug mode. Defaults to False.
|
|
93
|
+
trace (bool, optional): Traces. Defaults to False.
|
|
94
|
+
quiet (bool, optional): Quiet mode. Defaults to False.
|
|
95
|
+
results (dict | str, optional): Results to be processed. Defaults to None.
|
|
96
|
+
"""
|
|
97
|
+
super().__init__(debug, trace, quiet)
|
|
98
|
+
self.scan_settings = scan_settings
|
|
99
|
+
self.results: dict = results
|
|
100
|
+
self.component_info_map: dict = {}
|
|
101
|
+
|
|
102
|
+
def load_results(self, raw_results: dict):
|
|
103
|
+
"""Load the raw results
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
raw_results (dict): Raw scan results
|
|
107
|
+
"""
|
|
108
|
+
self.results = raw_results
|
|
109
|
+
self._load_component_info()
|
|
110
|
+
return self
|
|
111
|
+
|
|
112
|
+
def _load_component_info(self):
|
|
113
|
+
"""Create a map of component information from scan results for faster lookup"""
|
|
114
|
+
if not self.results:
|
|
115
|
+
return
|
|
116
|
+
for _, result in self.results.items():
|
|
117
|
+
result = result[0] if isinstance(result, list) else result
|
|
118
|
+
purls = result.get('purl', [])
|
|
119
|
+
for purl in purls:
|
|
120
|
+
self.component_info_map[purl] = result
|
|
121
|
+
|
|
122
|
+
def post_process(self):
|
|
123
|
+
"""
|
|
124
|
+
Post-process the scan results
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
dict: Processed results
|
|
128
|
+
"""
|
|
129
|
+
if self.scan_settings.is_legacy():
|
|
130
|
+
self.print_stderr(
|
|
131
|
+
'Legacy settings file detected. Post-processing is not supported for legacy settings file.'
|
|
132
|
+
)
|
|
133
|
+
return self.results
|
|
134
|
+
self._remove_dismissed_files()
|
|
135
|
+
self._replace_purls()
|
|
136
|
+
return self.results
|
|
137
|
+
|
|
138
|
+
def _remove_dismissed_files(self):
|
|
139
|
+
"""
|
|
140
|
+
Remove entries from the results based on files and/or purls specified in the SCANOSS settings file
|
|
141
|
+
"""
|
|
142
|
+
to_remove_entries = self.scan_settings.get_bom_remove()
|
|
143
|
+
if not to_remove_entries:
|
|
144
|
+
return
|
|
145
|
+
self.results = {
|
|
146
|
+
result_path: result
|
|
147
|
+
for result_path, result in self.results.items()
|
|
148
|
+
if not self._should_remove_result(result_path, result, to_remove_entries)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
def _replace_purls(self):
|
|
152
|
+
"""
|
|
153
|
+
Replace purls in the results based on the SCANOSS settings file
|
|
154
|
+
"""
|
|
155
|
+
to_replace_entries = self.scan_settings.get_bom_replace()
|
|
156
|
+
if not to_replace_entries:
|
|
157
|
+
return
|
|
158
|
+
|
|
159
|
+
for result_path, result in self.results.items():
|
|
160
|
+
result = result[0] if isinstance(result, list) else result
|
|
161
|
+
should_replace, to_replace_with_purl = self._should_replace_result(result_path, result, to_replace_entries)
|
|
162
|
+
if should_replace:
|
|
163
|
+
self.results[result_path] = [self._update_replaced_result(result, to_replace_with_purl)]
|
|
164
|
+
|
|
165
|
+
def _update_replaced_result(self, result: dict, to_replace_with_purl: str) -> dict:
|
|
166
|
+
"""
|
|
167
|
+
Update the result with the new purl and component information if available,
|
|
168
|
+
otherwise removes the old component information
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
result (dict): The result to update
|
|
172
|
+
to_replace_with_purl (str): The purl to replace with
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
dict: Updated result
|
|
176
|
+
"""
|
|
177
|
+
|
|
178
|
+
if self.component_info_map.get(to_replace_with_purl):
|
|
179
|
+
result.update(self.component_info_map[to_replace_with_purl])
|
|
180
|
+
else:
|
|
181
|
+
try:
|
|
182
|
+
new_component = PackageURL.from_string(to_replace_with_purl).to_dict()
|
|
183
|
+
new_component_url = purl2url.get_repo_url(to_replace_with_purl)
|
|
184
|
+
except RuntimeError:
|
|
185
|
+
self.print_stderr(
|
|
186
|
+
f"ERROR: Issue while replacing: Invalid PURL '{to_replace_with_purl}' in settings file. Skipping."
|
|
187
|
+
)
|
|
188
|
+
return result
|
|
189
|
+
|
|
190
|
+
result['component'] = new_component.get('name')
|
|
191
|
+
result['url'] = new_component_url
|
|
192
|
+
result['vendor'] = new_component.get('namespace')
|
|
193
|
+
|
|
194
|
+
result.pop('licenses', None)
|
|
195
|
+
result.pop('file', None)
|
|
196
|
+
result.pop('file_hash', None)
|
|
197
|
+
result.pop('file_url', None)
|
|
198
|
+
result.pop('latest', None)
|
|
199
|
+
result.pop('release_date', None)
|
|
200
|
+
result.pop('source_hash', None)
|
|
201
|
+
result.pop('url_hash', None)
|
|
202
|
+
result.pop('url_stats', None)
|
|
203
|
+
result.pop('url_stats', None)
|
|
204
|
+
result.pop('version', None)
|
|
205
|
+
|
|
206
|
+
result['purl'] = [to_replace_with_purl]
|
|
207
|
+
|
|
208
|
+
return result
|
|
209
|
+
|
|
210
|
+
def _should_replace_result(
|
|
211
|
+
self, result_path: str, result: dict, to_replace_entries: List[BomEntry]
|
|
212
|
+
) -> Tuple[bool, str]:
|
|
213
|
+
"""
|
|
214
|
+
Check if a result should be replaced based on the SCANOSS settings
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
result_path (str): Path of the result data
|
|
218
|
+
result (dict): Result to check
|
|
219
|
+
to_replace_entries (List[BomEntry]): BOM entries to replace from the settings file
|
|
220
|
+
|
|
221
|
+
Returns:
|
|
222
|
+
bool: True if the result should be replaced, False otherwise
|
|
223
|
+
str: The purl to replace with
|
|
224
|
+
"""
|
|
225
|
+
result_purls = result.get('purl', [])
|
|
226
|
+
for to_replace_entry in to_replace_entries:
|
|
227
|
+
to_replace_path = to_replace_entry.get('path')
|
|
228
|
+
to_replace_purl = to_replace_entry.get('purl')
|
|
229
|
+
to_replace_with = to_replace_entry.get('replace_with')
|
|
230
|
+
|
|
231
|
+
if not to_replace_path and not to_replace_purl or not to_replace_with:
|
|
232
|
+
continue
|
|
233
|
+
if (
|
|
234
|
+
_is_full_match(result_path, result_purls, to_replace_entry)
|
|
235
|
+
or (not to_replace_path and to_replace_purl in result_purls)
|
|
236
|
+
or (not to_replace_purl and to_replace_path == result_path)
|
|
237
|
+
):
|
|
238
|
+
self._print_message(result_path, result_purls, to_replace_entry, 'Replacing')
|
|
239
|
+
return True, to_replace_with
|
|
240
|
+
|
|
241
|
+
return False, None
|
|
242
|
+
|
|
243
|
+
def _should_remove_result(self, result_path: str, result: dict, to_remove_entries: List[BomEntry]) -> bool:
|
|
244
|
+
"""
|
|
245
|
+
Check if a result should be removed based on the SCANOSS settings
|
|
246
|
+
|
|
247
|
+
:param result_path: path of the result data
|
|
248
|
+
:param result: result to check
|
|
249
|
+
:param to_remove_entries: BOM entries to remove from the result
|
|
250
|
+
:return:
|
|
251
|
+
"""
|
|
252
|
+
result = result[0] if isinstance(result, list) else result
|
|
253
|
+
result_purls = result.get('purl', [])
|
|
254
|
+
|
|
255
|
+
for to_remove_entry in to_remove_entries:
|
|
256
|
+
to_remove_path = to_remove_entry.get('path')
|
|
257
|
+
to_remove_purl = to_remove_entry.get('purl')
|
|
258
|
+
|
|
259
|
+
if not to_remove_path and not to_remove_purl:
|
|
260
|
+
continue
|
|
261
|
+
if (
|
|
262
|
+
_is_full_match(result_path, result_purls, to_remove_entry)
|
|
263
|
+
or (not to_remove_path and to_remove_purl in result_purls)
|
|
264
|
+
or (not to_remove_purl and to_remove_path == result_path)
|
|
265
|
+
):
|
|
266
|
+
self._print_message(result_path, result_purls, to_remove_entry, 'Removing')
|
|
267
|
+
return True
|
|
268
|
+
|
|
269
|
+
return False
|
|
270
|
+
|
|
271
|
+
def _print_message(self, result_path: str, result_purls: List[str], bom_entry: BomEntry, action: str) -> None:
|
|
272
|
+
"""
|
|
273
|
+
Print a message about replacing or removing a result
|
|
274
|
+
|
|
275
|
+
:param result_path:
|
|
276
|
+
:param result_purls:
|
|
277
|
+
:param bom_entry:
|
|
278
|
+
:param action:
|
|
279
|
+
:return:
|
|
280
|
+
"""
|
|
281
|
+
message = (
|
|
282
|
+
f'{_get_match_type_message(result_path, bom_entry, action)} \n'
|
|
283
|
+
f'Details:\n'
|
|
284
|
+
f' - PURLs: {", ".join(result_purls)}\n'
|
|
285
|
+
f" - Path: '{result_path}'\n"
|
|
286
|
+
)
|
|
287
|
+
if action == 'Replacing':
|
|
288
|
+
message += f" - {action} with '{bom_entry.get('replace_with')}'"
|
|
289
|
+
self.print_debug(message)
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
#
|
|
293
|
+
# End of ScanPostProcessor Class
|
|
294
|
+
#
|
scanoss/scantype.py
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
"""
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
2
|
+
SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2021, SCANOSS
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in
|
|
14
|
+
all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
22
|
+
THE SOFTWARE.
|
|
23
23
|
"""
|
|
24
24
|
|
|
25
25
|
from enum import Enum
|
|
@@ -29,6 +29,7 @@ class ScanType(Enum):
|
|
|
29
29
|
"""
|
|
30
30
|
Octal Enum class describing all the scanning options
|
|
31
31
|
"""
|
|
32
|
+
|
|
32
33
|
SCAN_FILES = 1
|
|
33
34
|
SCAN_SNIPPETS = 2
|
|
34
35
|
SCAN_DEPENDENCIES = 4
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2025, SCANOSS
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in
|
|
14
|
+
all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
22
|
+
THE SOFTWARE.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
import requests
|
|
26
|
+
|
|
27
|
+
from ..scanossbase import ScanossBase
|
|
28
|
+
|
|
29
|
+
HTTP_OK = 200
|
|
30
|
+
|
|
31
|
+
class DependencyTrackService(ScanossBase):
|
|
32
|
+
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
api_key: str,
|
|
36
|
+
url: str,
|
|
37
|
+
debug: bool = False,
|
|
38
|
+
trace: bool = False,
|
|
39
|
+
quiet: bool = False,
|
|
40
|
+
):
|
|
41
|
+
super().__init__(debug=debug, trace=trace, quiet=quiet)
|
|
42
|
+
if not url:
|
|
43
|
+
raise ValueError("Error: Dependency Track URL is required")
|
|
44
|
+
self.url = url.strip().rstrip('/')
|
|
45
|
+
if not api_key:
|
|
46
|
+
raise ValueError("Error: Dependency Track API key is required")
|
|
47
|
+
self.api_key = api_key
|
|
48
|
+
|
|
49
|
+
def get_project_by_name_version(self, name, version):
|
|
50
|
+
"""
|
|
51
|
+
Get project information by name and version from Dependency Track
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
name: Project name to search for
|
|
55
|
+
version: Project version to search for
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
dict: Project data if found, None otherwise
|
|
59
|
+
"""
|
|
60
|
+
if not name or not version:
|
|
61
|
+
self.print_stderr('Error: Missing name or version.')
|
|
62
|
+
return None
|
|
63
|
+
# Use the project search endpoint
|
|
64
|
+
params = {
|
|
65
|
+
'name': name,
|
|
66
|
+
'version': version
|
|
67
|
+
}
|
|
68
|
+
self.print_debug(f'Searching for project by: {params}')
|
|
69
|
+
return self.get_dep_track_data(f'{self.url}/api/v1/project/lookup', params)
|
|
70
|
+
|
|
71
|
+
def get_project_status(self, upload_token):
|
|
72
|
+
"""
|
|
73
|
+
Get Dependency Track project processing status.
|
|
74
|
+
|
|
75
|
+
Queries the Dependency Track API to check if the project upload
|
|
76
|
+
processing is complete using the upload token.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
dict: Project status information or None if request fails
|
|
80
|
+
"""
|
|
81
|
+
if not upload_token:
|
|
82
|
+
self.print_stderr('Error: Missing upload token. Cannot search for project status.')
|
|
83
|
+
return None
|
|
84
|
+
self.print_trace(f'URL: {self.url} Upload token: {upload_token}')
|
|
85
|
+
return self.get_dep_track_data(f'{self.url}/api/v1/event/token/{upload_token}')
|
|
86
|
+
|
|
87
|
+
def get_project_violations(self,project_id:str):
|
|
88
|
+
"""
|
|
89
|
+
Get project violations from Dependency Track.
|
|
90
|
+
|
|
91
|
+
Waits for project processing to complete, then retrieves all policy
|
|
92
|
+
violations for the specified project ID.
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
List of policy violations or None if the request fails
|
|
96
|
+
"""
|
|
97
|
+
if not project_id:
|
|
98
|
+
self.print_stderr('Error: Missing project id. Cannot search for project violations.')
|
|
99
|
+
return None
|
|
100
|
+
# Return the result as-is - None indicates API failure, empty list means no violations
|
|
101
|
+
return self.get_dep_track_data(f'{self.url}/api/v1/violation/project/{project_id}')
|
|
102
|
+
|
|
103
|
+
def get_project_by_id(self, project_id:str):
|
|
104
|
+
"""
|
|
105
|
+
Get a Dependency Track project by id.
|
|
106
|
+
|
|
107
|
+
Queries the Dependency Track API to get a project by id
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
dict
|
|
111
|
+
"""
|
|
112
|
+
if not project_id:
|
|
113
|
+
self.print_stderr('Error: Missing project id. Cannot search for project.')
|
|
114
|
+
return None
|
|
115
|
+
self.print_trace(f'URL: {self.url}, UUID: {project_id}')
|
|
116
|
+
return self.get_dep_track_data(f'{self.url}/api/v1/project/{project_id}')
|
|
117
|
+
|
|
118
|
+
def get_dep_track_data(self, uri, params=None):
|
|
119
|
+
if not uri:
|
|
120
|
+
self.print_stderr('Error: Missing URI. Cannot search for project.')
|
|
121
|
+
return None
|
|
122
|
+
req_headers = {'X-Api-Key': self.api_key, 'Content-Type': 'application/json'}
|
|
123
|
+
try:
|
|
124
|
+
if params:
|
|
125
|
+
response = requests.get(uri, headers=req_headers, params=params)
|
|
126
|
+
else:
|
|
127
|
+
response = requests.get(uri, headers=req_headers)
|
|
128
|
+
response.raise_for_status() # Raises an HTTPError for bad responses
|
|
129
|
+
return response.json()
|
|
130
|
+
except requests.exceptions.RequestException as e:
|
|
131
|
+
self.print_stderr(f"Error: Problem getting project data: {e}")
|
|
132
|
+
return None
|