scanoss 1.30.0__py3-none-any.whl → 1.31.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.
- scanoss/__init__.py +1 -1
- scanoss/cli.py +572 -188
- scanoss/data/build_date.txt +1 -1
- scanoss/export/dependency_track.py +101 -100
- scanoss/file_filters.py +1 -5
- scanoss/inspection/dependency_track/project_violation.py +449 -0
- scanoss/inspection/policy_check.py +54 -23
- scanoss/inspection/{component_summary.py → raw/component_summary.py} +3 -3
- scanoss/inspection/{copyleft.py → raw/copyleft.py} +63 -54
- scanoss/inspection/{license_summary.py → raw/license_summary.py} +5 -4
- scanoss/inspection/{inspect_base.py → raw/raw_base.py} +9 -6
- scanoss/inspection/{undeclared_component.py → raw/undeclared_component.py} +29 -25
- scanoss/services/dependency_track_service.py +131 -0
- {scanoss-1.30.0.dist-info → scanoss-1.31.1.dist-info}/METADATA +1 -1
- {scanoss-1.30.0.dist-info → scanoss-1.31.1.dist-info}/RECORD +19 -17
- {scanoss-1.30.0.dist-info → scanoss-1.31.1.dist-info}/WHEEL +0 -0
- {scanoss-1.30.0.dist-info → scanoss-1.31.1.dist-info}/entry_points.txt +0 -0
- {scanoss-1.30.0.dist-info → scanoss-1.31.1.dist-info}/licenses/LICENSE +0 -0
- {scanoss-1.30.0.dist-info → scanoss-1.31.1.dist-info}/top_level.txt +0 -0
scanoss/data/build_date.txt
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
date:
|
|
1
|
+
date: 20250808142900, utime: 1754663340
|
|
@@ -25,85 +25,65 @@ SPDX-License-Identifier: MIT
|
|
|
25
25
|
import base64
|
|
26
26
|
import json
|
|
27
27
|
import traceback
|
|
28
|
-
from dataclasses import dataclass
|
|
29
|
-
from typing import Optional
|
|
30
28
|
|
|
31
29
|
import requests
|
|
32
30
|
|
|
33
|
-
from
|
|
34
|
-
|
|
31
|
+
from ..cyclonedx import CycloneDx
|
|
35
32
|
from ..scanossbase import ScanossBase
|
|
33
|
+
from ..services.dependency_track_service import DependencyTrackService
|
|
36
34
|
from ..utils.file import validate_json_file
|
|
37
35
|
|
|
38
36
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
trace: bool = False
|
|
43
|
-
quiet: bool = False
|
|
44
|
-
dt_url: str = None
|
|
45
|
-
dt_apikey: str = None
|
|
46
|
-
dt_projectid: Optional[str] = None
|
|
47
|
-
dt_projectname: Optional[str] = None
|
|
48
|
-
dt_projectversion: Optional[str] = None
|
|
37
|
+
def _build_payload(encoded_sbom: str, project_id, project_name, project_version) -> dict:
|
|
38
|
+
"""
|
|
39
|
+
Build the API payload
|
|
49
40
|
|
|
41
|
+
Args:
|
|
42
|
+
encoded_sbom: Base64 encoded SBOM
|
|
50
43
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
44
|
+
Returns:
|
|
45
|
+
API payload dictionary
|
|
46
|
+
"""
|
|
47
|
+
if project_id:
|
|
48
|
+
return {'project': project_id, 'bom': encoded_sbom}
|
|
49
|
+
else:
|
|
50
|
+
return {
|
|
51
|
+
'projectName': project_name,
|
|
52
|
+
'projectVersion': project_version,
|
|
53
|
+
'autoCreate': True,
|
|
54
|
+
'bom': encoded_sbom,
|
|
55
|
+
}
|
|
62
56
|
|
|
63
57
|
|
|
64
58
|
class DependencyTrackExporter(ScanossBase):
|
|
65
59
|
"""
|
|
66
60
|
Class for exporting SBOM files to Dependency Track
|
|
67
61
|
"""
|
|
68
|
-
|
|
69
|
-
def __init__(
|
|
62
|
+
def __init__( # noqa: PLR0913
|
|
70
63
|
self,
|
|
71
|
-
|
|
64
|
+
url: str = None,
|
|
65
|
+
apikey: str = None,
|
|
66
|
+
output: str = None,
|
|
72
67
|
debug: bool = False,
|
|
73
68
|
trace: bool = False,
|
|
74
|
-
quiet: bool = False
|
|
69
|
+
quiet: bool = False
|
|
75
70
|
):
|
|
76
71
|
"""
|
|
77
72
|
Initialize DependencyTrackExporter
|
|
78
73
|
|
|
79
74
|
Args:
|
|
80
|
-
|
|
75
|
+
url: Dependency Track URL
|
|
76
|
+
apikey: Dependency Track API Key
|
|
77
|
+
output: File to store output response data (optional)
|
|
81
78
|
debug: Enable debug output
|
|
82
79
|
trace: Enable trace output
|
|
83
80
|
quiet: Enable quiet mode
|
|
84
81
|
"""
|
|
85
82
|
super().__init__(debug=debug, trace=trace, quiet=quiet)
|
|
86
|
-
|
|
87
|
-
self.
|
|
88
|
-
self.
|
|
89
|
-
self.
|
|
90
|
-
self.dt_projectname = config.dt_projectname
|
|
91
|
-
self.dt_projectversion = config.dt_projectversion
|
|
92
|
-
|
|
93
|
-
self._validate_config()
|
|
94
|
-
|
|
95
|
-
def _validate_config(self):
|
|
96
|
-
"""
|
|
97
|
-
Validate that the configuration is valid.
|
|
98
|
-
"""
|
|
99
|
-
has_id = bool(self.dt_projectid)
|
|
100
|
-
has_name_version = bool(self.dt_projectname and self.dt_projectversion)
|
|
101
|
-
|
|
102
|
-
if not (has_id or has_name_version):
|
|
103
|
-
raise ValueError('Either --dt-projectid OR (--dt-projectname and --dt-projectversion) must be provided')
|
|
104
|
-
|
|
105
|
-
if has_id and has_name_version:
|
|
106
|
-
self.print_debug('Both DT project ID and name/version provided. Using project ID.')
|
|
83
|
+
self.url = url.rstrip('/')
|
|
84
|
+
self.apikey = apikey
|
|
85
|
+
self.output = output
|
|
86
|
+
self.dt_service = DependencyTrackService(self.apikey, self.url, debug=debug, trace=trace, quiet=quiet)
|
|
107
87
|
|
|
108
88
|
def _read_and_validate_sbom(self, input_file: str) -> dict:
|
|
109
89
|
"""
|
|
@@ -116,7 +96,7 @@ class DependencyTrackExporter(ScanossBase):
|
|
|
116
96
|
Parsed SBOM content as dictionary
|
|
117
97
|
|
|
118
98
|
Raises:
|
|
119
|
-
ValueError: If file doesn't exist or is invalid or not a valid CycloneDX SBOM
|
|
99
|
+
ValueError: If the file doesn't exist or is invalid or not a valid CycloneDX SBOM
|
|
120
100
|
"""
|
|
121
101
|
result = validate_json_file(input_file)
|
|
122
102
|
if not result.is_valid:
|
|
@@ -125,7 +105,6 @@ class DependencyTrackExporter(ScanossBase):
|
|
|
125
105
|
cdx = CycloneDx(debug=self.debug)
|
|
126
106
|
if not cdx.is_cyclonedx_json(json.dumps(result.data)):
|
|
127
107
|
raise ValueError(f'Input file is not a valid CycloneDX SBOM: {input_file}')
|
|
128
|
-
|
|
129
108
|
return result.data
|
|
130
109
|
|
|
131
110
|
def _encode_sbom(self, sbom_content: dict) -> str:
|
|
@@ -138,84 +117,106 @@ class DependencyTrackExporter(ScanossBase):
|
|
|
138
117
|
Returns:
|
|
139
118
|
Base64 encoded string
|
|
140
119
|
"""
|
|
120
|
+
if not sbom_content:
|
|
121
|
+
self.print_stderr('Warning: Empty SBOM content')
|
|
141
122
|
json_str = json.dumps(sbom_content, separators=(',', ':'))
|
|
142
123
|
encoded = base64.b64encode(json_str.encode('utf-8')).decode('utf-8')
|
|
143
124
|
return encoded
|
|
144
125
|
|
|
145
|
-
def
|
|
126
|
+
def upload_sbom_file(self, input_file, project_id, project_name, project_version, output_file):
|
|
146
127
|
"""
|
|
147
|
-
|
|
128
|
+
Uploads an SBOM file to the specified project with an
|
|
129
|
+
optional output file and processes the file content for validation.
|
|
148
130
|
|
|
149
131
|
Args:
|
|
150
|
-
|
|
132
|
+
input_file (str): The path to the SBOM file to be read and uploaded.
|
|
133
|
+
project_id (str): The unique identifier of the project to which the SBOM is being uploaded.
|
|
134
|
+
project_name (str): The name of the project to which the SBOM is being uploaded.
|
|
135
|
+
project_version (str): The version of the project to which the SBOM is being uploaded.
|
|
136
|
+
output_file (str): The path to save output related to the SBOM upload process.
|
|
151
137
|
|
|
152
138
|
Returns:
|
|
153
|
-
|
|
154
|
-
"""
|
|
155
|
-
if self.dt_projectid:
|
|
156
|
-
return {'project': self.dt_projectid, 'bom': encoded_sbom}
|
|
157
|
-
else:
|
|
158
|
-
return {
|
|
159
|
-
'projectName': self.dt_projectname,
|
|
160
|
-
'projectVersion': self.dt_projectversion,
|
|
161
|
-
'autoCreate': True,
|
|
162
|
-
'bom': encoded_sbom,
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
def upload_sbom(self, input_file: str) -> bool:
|
|
166
|
-
"""
|
|
167
|
-
Upload SBOM file to Dependency Track
|
|
139
|
+
bool: Returns True if the SBOM file was uploaded successfully, False otherwise.
|
|
168
140
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
Returns:
|
|
173
|
-
True if successful, False otherwise
|
|
141
|
+
Raises:
|
|
142
|
+
ValueError: Raised if there are validation issues with the SBOM content.
|
|
174
143
|
"""
|
|
175
144
|
try:
|
|
176
|
-
self.
|
|
145
|
+
if not self.quiet:
|
|
146
|
+
self.print_stderr(f'Reading SBOM file: {input_file}')
|
|
177
147
|
sbom_content = self._read_and_validate_sbom(input_file)
|
|
148
|
+
return self.upload_sbom_contents(sbom_content, project_id, project_name, project_version, output_file)
|
|
149
|
+
except ValueError as e:
|
|
150
|
+
self.print_stderr(f'Validation error: {e}')
|
|
151
|
+
return False
|
|
178
152
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
payload = self._build_payload(encoded_sbom)
|
|
153
|
+
def upload_sbom_contents(self, sbom_content: dict, project_id, project_name, project_version, output_file) -> bool:
|
|
154
|
+
"""
|
|
155
|
+
Uploads an SBOM to a Dependency Track server.
|
|
183
156
|
|
|
184
|
-
|
|
185
|
-
|
|
157
|
+
Parameters:
|
|
158
|
+
sbom_content (dict): The SBOM content in dictionary format to be uploaded.
|
|
159
|
+
project_id: The unique identifier for the project.
|
|
160
|
+
project_name: The name of the project in Dependency Track.
|
|
161
|
+
project_version: The version of the project in Dependency Track.
|
|
162
|
+
output_file: The path to the file where the token and UUID data
|
|
163
|
+
should be written. If not provided, the data will be written to
|
|
164
|
+
standard output.
|
|
186
165
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
self.print_trace(f'Headers: {headers}')
|
|
190
|
-
self.print_trace(f'Payload keys: {list(payload.keys())}')
|
|
166
|
+
Returns:
|
|
167
|
+
bool: True if the upload is successful; False otherwise.
|
|
191
168
|
|
|
169
|
+
Raises:
|
|
170
|
+
ValueError: If the SBOM encoding process fails.
|
|
171
|
+
requests.exceptions.RequestException: If an error occurs during the HTTP request.
|
|
172
|
+
Exception: For any other unexpected error.
|
|
173
|
+
"""
|
|
174
|
+
if not project_id and not (project_name and project_version):
|
|
175
|
+
self.print_stderr('Error: Missing project id or name and version.')
|
|
176
|
+
return False
|
|
177
|
+
output = self.output
|
|
178
|
+
if output_file:
|
|
179
|
+
output = output_file
|
|
180
|
+
try:
|
|
181
|
+
self.print_debug('Encoding SBOM to base64')
|
|
182
|
+
payload = _build_payload(self._encode_sbom(sbom_content), project_id, project_name, project_version)
|
|
183
|
+
url = f'{self.url}/api/v1/bom'
|
|
184
|
+
headers = {'Content-Type': 'application/json', 'X-Api-Key': self.apikey}
|
|
185
|
+
self.print_trace(f'URL: {url}, Headers: {headers}, Payload keys: {list(payload.keys())}')
|
|
192
186
|
self.print_msg('Uploading SBOM to Dependency Track...')
|
|
193
187
|
response = requests.put(url, json=payload, headers=headers)
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
188
|
+
response.raise_for_status()
|
|
189
|
+
# Treat any 2xx status as success
|
|
190
|
+
if (requests.codes.ok <= response.status_code < requests.codes.multiple_choices and
|
|
191
|
+
response.status_code != requests.codes.no_content):
|
|
192
|
+
self.print_msg('SBOM uploaded successfully')
|
|
198
193
|
try:
|
|
199
194
|
response_data = response.json()
|
|
195
|
+
token = ''
|
|
196
|
+
project_uuid = project_id
|
|
200
197
|
if 'token' in response_data:
|
|
201
|
-
|
|
198
|
+
token = response_data['token']
|
|
199
|
+
if project_name and project_version:
|
|
200
|
+
project_data = self.dt_service.get_project_by_name_version(project_name, project_version)
|
|
201
|
+
if project_data:
|
|
202
|
+
project_uuid = project_data.get("uuid", project_id)
|
|
203
|
+
token_json = json.dumps(
|
|
204
|
+
{"token": token, "project_uuid": project_uuid},
|
|
205
|
+
indent=2
|
|
206
|
+
)
|
|
207
|
+
self.print_to_file_or_stdout(token_json, output)
|
|
202
208
|
except json.JSONDecodeError:
|
|
203
209
|
pass
|
|
204
|
-
|
|
205
210
|
return True
|
|
206
211
|
else:
|
|
207
212
|
self.print_stderr(f'Upload failed with status code: {response.status_code}')
|
|
208
213
|
self.print_stderr(f'Response: {response.text}')
|
|
209
|
-
return False
|
|
210
|
-
|
|
211
214
|
except ValueError as e:
|
|
212
|
-
self.print_stderr(f'Validation error: {e}')
|
|
213
|
-
return False
|
|
215
|
+
self.print_stderr(f'DT SBOM Upload Validation error: {e}')
|
|
214
216
|
except requests.exceptions.RequestException as e:
|
|
215
|
-
self.print_stderr(f'Request error: {e}')
|
|
216
|
-
return False
|
|
217
|
+
self.print_stderr(f'DT API Request error: {e}')
|
|
217
218
|
except Exception as e:
|
|
218
219
|
self.print_stderr(f'Unexpected error: {e}')
|
|
219
220
|
if self.debug:
|
|
220
221
|
traceback.print_exc()
|
|
221
|
-
|
|
222
|
+
return False
|