scanoss 1.26.0__tar.gz → 1.26.2__tar.gz
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-1.26.0/src/scanoss.egg-info → scanoss-1.26.2}/PKG-INFO +1 -1
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/__init__.py +1 -1
- scanoss-1.26.2/src/scanoss/data/build_date.txt +1 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/inspection/component_summary.py +17 -8
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/inspection/copyleft.py +21 -20
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/inspection/inspect_base.py +51 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/inspection/license_summary.py +25 -47
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/inspection/undeclared_component.py +2 -33
- {scanoss-1.26.0 → scanoss-1.26.2/src/scanoss.egg-info}/PKG-INFO +1 -1
- {scanoss-1.26.0 → scanoss-1.26.2}/tests/test_policy_inspect.py +49 -28
- {scanoss-1.26.0 → scanoss-1.26.2}/tests/test_spdxlite.py +2 -2
- scanoss-1.26.0/src/scanoss/data/build_date.txt +0 -1
- {scanoss-1.26.0 → scanoss-1.26.2}/LICENSE +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/PACKAGE.md +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/README.md +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/pyproject.toml +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/setup.cfg +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/protoc_gen_swagger/__init__.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/protoc_gen_swagger/options/__init__.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/protoc_gen_swagger/options/annotations_pb2.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/protoc_gen_swagger/options/annotations_pb2_grpc.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/protoc_gen_swagger/options/openapiv2_pb2.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/protoc_gen_swagger/options/openapiv2_pb2_grpc.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/__init__.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/common/__init__.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/common/v2/__init__.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/common/v2/scanoss_common_pb2.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/common/v2/scanoss_common_pb2_grpc.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/components/__init__.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/components/v2/__init__.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/components/v2/scanoss_components_pb2.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/components/v2/scanoss_components_pb2_grpc.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/cryptography/v2/scanoss_cryptography_pb2.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/cryptography/v2/scanoss_cryptography_pb2_grpc.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/dependencies/__init__.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/dependencies/v2/__init__.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/dependencies/v2/scanoss_dependencies_pb2.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/dependencies/v2/scanoss_dependencies_pb2_grpc.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/geoprovenance/__init__.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/geoprovenance/v2/__init__.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/geoprovenance/v2/scanoss_geoprovenance_pb2.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/geoprovenance/v2/scanoss_geoprovenance_pb2_grpc.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/scanning/__init__.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/scanning/v2/__init__.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/scanning/v2/scanoss_scanning_pb2.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/scanning/v2/scanoss_scanning_pb2_grpc.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/semgrep/__init__.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/semgrep/v2/__init__.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/semgrep/v2/scanoss_semgrep_pb2.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/semgrep/v2/scanoss_semgrep_pb2_grpc.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/vulnerabilities/__init__.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/vulnerabilities/v2/__init__.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2_grpc.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/cli.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/components.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/constants.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/cryptography.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/csvoutput.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/cyclonedx.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/data/scanoss-settings-schema.json +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/data/spdx-exceptions.json +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/data/spdx-licenses.json +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/file_filters.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/filecount.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/inspection/__init__.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/inspection/policy_check.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/inspection/utils/license_utils.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/results.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/scancodedeps.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/scanner.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/scanners/__init__.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/scanners/container_scanner.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/scanners/folder_hasher.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/scanners/scanner_config.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/scanners/scanner_hfh.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/scanoss_settings.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/scanossapi.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/scanossbase.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/scanossgrpc.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/scanpostprocessor.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/scantype.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/spdxlite.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/threadeddependencies.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/threadedscanning.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/utils/__init__.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/utils/abstract_presenter.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/utils/crc64.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/utils/file.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/utils/simhash.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/winnowing.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss.egg-info/SOURCES.txt +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss.egg-info/dependency_links.txt +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss.egg-info/entry_points.txt +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss.egg-info/requires.txt +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss.egg-info/top_level.txt +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/tests/test_csv_output.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/tests/test_file_filters.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/tests/test_scan_post_processor.py +0 -0
- {scanoss-1.26.0 → scanoss-1.26.2}/tests/test_winnowing.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
date: 20250624181246, utime: 1750788766
|
|
@@ -35,12 +35,18 @@ class ComponentSummary(InspectBase):
|
|
|
35
35
|
:param components: List of all components
|
|
36
36
|
:return: Dict with license summary information
|
|
37
37
|
"""
|
|
38
|
+
# A component is considered unique by its combination of PURL (Package URL) and license
|
|
39
|
+
component_licenses = self._group_components_by_license(scan_components)
|
|
40
|
+
total_components = len(component_licenses)
|
|
41
|
+
# Get undeclared components
|
|
42
|
+
undeclared_components = len([c for c in component_licenses if c['status'] == 'pending'])
|
|
43
|
+
|
|
38
44
|
components: list = []
|
|
39
|
-
|
|
40
|
-
|
|
45
|
+
total_undeclared_files = 0
|
|
46
|
+
total_files_detected = 0
|
|
41
47
|
for component in scan_components:
|
|
42
|
-
|
|
43
|
-
|
|
48
|
+
total_files_detected += component['count']
|
|
49
|
+
total_undeclared_files += component['undeclared']
|
|
44
50
|
components.append({
|
|
45
51
|
'purl': component['purl'],
|
|
46
52
|
'version': component['version'],
|
|
@@ -50,10 +56,13 @@ class ComponentSummary(InspectBase):
|
|
|
50
56
|
})
|
|
51
57
|
## End for loop components
|
|
52
58
|
return {
|
|
53
|
-
|
|
54
|
-
'
|
|
55
|
-
'
|
|
56
|
-
'
|
|
59
|
+
"components": component_licenses,
|
|
60
|
+
'totalComponents': total_components,
|
|
61
|
+
'undeclaredComponents': undeclared_components,
|
|
62
|
+
'declaredComponents': total_components - undeclared_components,
|
|
63
|
+
'totalFilesDetected': total_files_detected,
|
|
64
|
+
'totalFilesUndeclared': total_undeclared_files,
|
|
65
|
+
'totalFilesDeclared': total_files_detected - total_undeclared_files,
|
|
57
66
|
}
|
|
58
67
|
|
|
59
68
|
def _get_components(self):
|
|
@@ -78,12 +78,14 @@ class Copyleft(PolicyCheck):
|
|
|
78
78
|
:param components: List of components with copyleft licenses
|
|
79
79
|
:return: Dictionary with formatted JSON details and summary
|
|
80
80
|
"""
|
|
81
|
+
# A component is considered unique by its combination of PURL (Package URL) and license
|
|
82
|
+
component_licenses = self._group_components_by_license(components)
|
|
81
83
|
details = {}
|
|
82
84
|
if len(components) > 0:
|
|
83
85
|
details = {'components': components}
|
|
84
86
|
return {
|
|
85
87
|
'details': f'{json.dumps(details, indent=2)}\n',
|
|
86
|
-
'summary': f'{len(
|
|
88
|
+
'summary': f'{len(component_licenses)} component(s) with copyleft licenses were found.\n',
|
|
87
89
|
}
|
|
88
90
|
|
|
89
91
|
def _markdown(self, components: list) -> Dict[str, Any]:
|
|
@@ -93,24 +95,24 @@ class Copyleft(PolicyCheck):
|
|
|
93
95
|
:param components: List of components with copyleft licenses
|
|
94
96
|
:return: Dictionary with formatted Markdown details and summary
|
|
95
97
|
"""
|
|
96
|
-
|
|
98
|
+
# A component is considered unique by its combination of PURL (Package URL) and license
|
|
99
|
+
component_licenses = self._group_components_by_license(components)
|
|
100
|
+
headers = ['Component', 'License', 'URL', 'Copyleft']
|
|
97
101
|
centered_columns = [1, 4]
|
|
98
102
|
rows: [[]] = []
|
|
99
|
-
for
|
|
100
|
-
for lic in component['licenses']:
|
|
103
|
+
for comp_lic_item in component_licenses:
|
|
101
104
|
row = [
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
'YES' if lic['copyleft'] else 'NO',
|
|
105
|
+
comp_lic_item['purl'],
|
|
106
|
+
comp_lic_item['spdxid'],
|
|
107
|
+
comp_lic_item['url'],
|
|
108
|
+
'YES' if comp_lic_item['copyleft'] else 'NO',
|
|
107
109
|
]
|
|
108
110
|
rows.append(row)
|
|
109
111
|
# End license loop
|
|
110
112
|
# End component loop
|
|
111
113
|
return {
|
|
112
114
|
'details': f'### Copyleft licenses\n{self.generate_table(headers, rows, centered_columns)}\n',
|
|
113
|
-
'summary': f'{len(
|
|
115
|
+
'summary': f'{len(component_licenses)} component(s) with copyleft licenses were found.\n',
|
|
114
116
|
}
|
|
115
117
|
|
|
116
118
|
def _jira_markdown(self, components: list) -> Dict[str, Any]:
|
|
@@ -120,24 +122,24 @@ class Copyleft(PolicyCheck):
|
|
|
120
122
|
:param components: List of components with copyleft licenses
|
|
121
123
|
:return: Dictionary with formatted Markdown details and summary
|
|
122
124
|
"""
|
|
123
|
-
|
|
125
|
+
# A component is considered unique by its combination of PURL (Package URL) and license
|
|
126
|
+
component_licenses = self._group_components_by_license(components)
|
|
127
|
+
headers = ['Component', 'License', 'URL', 'Copyleft']
|
|
124
128
|
centered_columns = [1, 4]
|
|
125
129
|
rows: [[]] = []
|
|
126
|
-
for
|
|
127
|
-
for lic in component['licenses']:
|
|
130
|
+
for comp_lic_item in component_licenses:
|
|
128
131
|
row = [
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
'YES' if lic['copyleft'] else 'NO',
|
|
132
|
+
comp_lic_item['purl'],
|
|
133
|
+
comp_lic_item['spdxid'],
|
|
134
|
+
comp_lic_item['url'],
|
|
135
|
+
'YES' if comp_lic_item['copyleft'] else 'NO',
|
|
134
136
|
]
|
|
135
137
|
rows.append(row)
|
|
136
138
|
# End license loop
|
|
137
139
|
# End component loop
|
|
138
140
|
return {
|
|
139
141
|
'details': f'{self.generate_jira_table(headers, rows, centered_columns)}',
|
|
140
|
-
'summary': f'{len(
|
|
142
|
+
'summary': f'{len(component_licenses)} component(s) with copyleft licenses were found.\n',
|
|
141
143
|
}
|
|
142
144
|
|
|
143
145
|
def _filter_components_with_copyleft_licenses(self, components: list) -> list:
|
|
@@ -161,7 +163,6 @@ class Copyleft(PolicyCheck):
|
|
|
161
163
|
lic.pop('count', None) # None is default value if key doesn't exist
|
|
162
164
|
|
|
163
165
|
filtered_component['licenses'] = copyleft_licenses
|
|
164
|
-
del filtered_component['status']
|
|
165
166
|
filtered_components.append(filtered_component)
|
|
166
167
|
# End component loop
|
|
167
168
|
self.print_debug(f'Copyleft components: {filtered_components}')
|
|
@@ -372,6 +372,57 @@ class InspectBase(ScanossBase):
|
|
|
372
372
|
self.print_debug("No priority sources found, returning all licenses as list")
|
|
373
373
|
return licenses_data
|
|
374
374
|
|
|
375
|
+
def _group_components_by_license(self,components):
|
|
376
|
+
"""
|
|
377
|
+
Groups components by their unique component-license pairs.
|
|
378
|
+
|
|
379
|
+
This method processes a list of components and creates unique entries for each
|
|
380
|
+
component-license combination. If a component has multiple licenses, it will create
|
|
381
|
+
separate entries for each license.
|
|
382
|
+
|
|
383
|
+
Args:
|
|
384
|
+
components: A list of component dictionaries. Each component should have:
|
|
385
|
+
- purl: Package URL identifying the component
|
|
386
|
+
- licenses: List of license dictionaries, each containing:
|
|
387
|
+
- spdxid: SPDX identifier for the license (optional)
|
|
388
|
+
|
|
389
|
+
Returns:
|
|
390
|
+
list: A list of dictionaries, each containing:
|
|
391
|
+
- purl: The component's package URL
|
|
392
|
+
- license: The SPDX identifier of the license (or 'Unknown' if not provided)
|
|
393
|
+
"""
|
|
394
|
+
component_licenses: dict = {}
|
|
395
|
+
for component in components:
|
|
396
|
+
purl = component.get('purl', '')
|
|
397
|
+
status = component.get('status', '')
|
|
398
|
+
licenses = component.get('licenses', [])
|
|
399
|
+
|
|
400
|
+
# Component without license
|
|
401
|
+
if not licenses:
|
|
402
|
+
key = f'{purl}-unknown'
|
|
403
|
+
component_licenses[key] = {
|
|
404
|
+
'purl': purl,
|
|
405
|
+
'spdxid': 'unknown',
|
|
406
|
+
'status': status,
|
|
407
|
+
'copyleft': False,
|
|
408
|
+
'url': '-',
|
|
409
|
+
}
|
|
410
|
+
continue
|
|
411
|
+
|
|
412
|
+
# Iterate over licenses component licenses
|
|
413
|
+
for lic in licenses:
|
|
414
|
+
spdxid = lic.get('spdxid', 'unknown')
|
|
415
|
+
if spdxid not in component_licenses:
|
|
416
|
+
key = f'{purl}-{spdxid}'
|
|
417
|
+
component_licenses[key] = {
|
|
418
|
+
'purl': purl,
|
|
419
|
+
'spdxid': spdxid,
|
|
420
|
+
'status': status,
|
|
421
|
+
'copyleft': lic['copyleft'],
|
|
422
|
+
'url': lic['url'],
|
|
423
|
+
}
|
|
424
|
+
return list(component_licenses.values())
|
|
425
|
+
|
|
375
426
|
|
|
376
427
|
#
|
|
377
428
|
# End of PolicyCheck Class
|
|
@@ -23,7 +23,6 @@ SPDX-License-Identifier: MIT
|
|
|
23
23
|
"""
|
|
24
24
|
|
|
25
25
|
import json
|
|
26
|
-
from typing import Any, Dict
|
|
27
26
|
|
|
28
27
|
from .inspect_base import InspectBase
|
|
29
28
|
|
|
@@ -73,35 +72,6 @@ class LicenseSummary(InspectBase):
|
|
|
73
72
|
self.exclude = exclude
|
|
74
73
|
self.explicit = explicit
|
|
75
74
|
|
|
76
|
-
def _validate_license(self, license_data: Dict[str, Any]) -> bool:
|
|
77
|
-
"""
|
|
78
|
-
Validate that a license has all required fields.
|
|
79
|
-
|
|
80
|
-
:param license_data: Dictionary containing license information
|
|
81
|
-
:return: True if license is valid, False otherwise
|
|
82
|
-
"""
|
|
83
|
-
for field in self.REQUIRED_LICENSE_FIELDS:
|
|
84
|
-
value = license_data.get(field)
|
|
85
|
-
if value is None:
|
|
86
|
-
self.print_debug(f'WARNING: {field} is empty in license: {license_data}')
|
|
87
|
-
return False
|
|
88
|
-
return True
|
|
89
|
-
|
|
90
|
-
def _append_license(self, licenses: dict, new_license) -> None:
|
|
91
|
-
"""Add or update a license in the licenses' dictionary."""
|
|
92
|
-
spdxid = new_license.get("spdxid")
|
|
93
|
-
url = new_license.get("url")
|
|
94
|
-
copyleft = new_license.get("copyleft")
|
|
95
|
-
if spdxid not in licenses:
|
|
96
|
-
licenses[spdxid] = {
|
|
97
|
-
'spdxid': spdxid,
|
|
98
|
-
'url': url,
|
|
99
|
-
'copyleft':copyleft,
|
|
100
|
-
'count': new_license.get("count"),
|
|
101
|
-
}
|
|
102
|
-
else:
|
|
103
|
-
licenses[spdxid]['count'] += new_license.get("count")
|
|
104
|
-
|
|
105
75
|
def _get_licenses_summary_from_components(self, components: list)-> dict:
|
|
106
76
|
"""
|
|
107
77
|
Get a license summary from detected components.
|
|
@@ -109,27 +79,35 @@ class LicenseSummary(InspectBase):
|
|
|
109
79
|
:param components: List of all components
|
|
110
80
|
:return: Dict with license summary information
|
|
111
81
|
"""
|
|
82
|
+
# A component is considered unique by its combination of PURL (Package URL) and license
|
|
83
|
+
component_licenses = self._group_components_by_license(components)
|
|
84
|
+
license_component_count = {}
|
|
85
|
+
# Count license per component
|
|
86
|
+
for lic in component_licenses:
|
|
87
|
+
if lic['spdxid'] not in license_component_count:
|
|
88
|
+
license_component_count[lic['spdxid']] = 1
|
|
89
|
+
else:
|
|
90
|
+
license_component_count[lic['spdxid']] += 1
|
|
112
91
|
licenses:dict = {}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
licenses_with_copyleft += lic.get("count")
|
|
125
|
-
## Add license
|
|
126
|
-
self._append_license(licenses, lic)
|
|
92
|
+
for comp_lic in component_licenses:
|
|
93
|
+
spdxid = comp_lic.get("spdxid")
|
|
94
|
+
url = comp_lic.get("url")
|
|
95
|
+
copyleft = comp_lic.get("copyleft")
|
|
96
|
+
if spdxid not in licenses:
|
|
97
|
+
licenses[spdxid] = {
|
|
98
|
+
'spdxid': spdxid,
|
|
99
|
+
'url': url,
|
|
100
|
+
'copyleft': copyleft,
|
|
101
|
+
'componentCount': license_component_count.get(spdxid, 0), # Append component count to license
|
|
102
|
+
}
|
|
127
103
|
## End for loop licenses
|
|
128
104
|
## End for loop components
|
|
105
|
+
detected_licenses = list(licenses.values())
|
|
106
|
+
licenses_with_copyleft = [lic for lic in detected_licenses if lic['copyleft']]
|
|
129
107
|
return {
|
|
130
|
-
'licenses':
|
|
131
|
-
'
|
|
132
|
-
'
|
|
108
|
+
'licenses': detected_licenses,
|
|
109
|
+
'detectedLicenses': len(detected_licenses), # Count unique licenses. SPDXID is considered unique
|
|
110
|
+
'detectedLicensesWithCopyleft': len(licenses_with_copyleft),
|
|
133
111
|
}
|
|
134
112
|
|
|
135
113
|
|
|
@@ -80,7 +80,6 @@ class UndeclaredComponent(PolicyCheck):
|
|
|
80
80
|
for component in components:
|
|
81
81
|
if component['status'] == 'pending':
|
|
82
82
|
# Remove unused keys
|
|
83
|
-
del component['status']
|
|
84
83
|
del component['count']
|
|
85
84
|
del component['declared']
|
|
86
85
|
del component['undeclared']
|
|
@@ -177,7 +176,7 @@ class UndeclaredComponent(PolicyCheck):
|
|
|
177
176
|
# TODO look at using SpdxLite license name lookup method
|
|
178
177
|
component_licenses = self._group_components_by_license(components)
|
|
179
178
|
for component in component_licenses:
|
|
180
|
-
rows.append([component.get('purl'), component.get('
|
|
179
|
+
rows.append([component.get('purl'), component.get('spdxid')])
|
|
181
180
|
return {
|
|
182
181
|
'details': f'### Undeclared components\n{self.generate_table(headers, rows)}\n',
|
|
183
182
|
'summary': self._get_summary(component_licenses),
|
|
@@ -195,7 +194,7 @@ class UndeclaredComponent(PolicyCheck):
|
|
|
195
194
|
# TODO look at using SpdxLite license name lookup method
|
|
196
195
|
component_licenses = self._group_components_by_license(components)
|
|
197
196
|
for component in component_licenses:
|
|
198
|
-
rows.append([component.get('purl'), component.get('
|
|
197
|
+
rows.append([component.get('purl'), component.get('spdxid')])
|
|
199
198
|
return {
|
|
200
199
|
'details': f'{self.generate_jira_table(headers, rows)}',
|
|
201
200
|
'summary': self._get_jira_summary(component_licenses),
|
|
@@ -265,36 +264,6 @@ class UndeclaredComponent(PolicyCheck):
|
|
|
265
264
|
# Convert to list and process licenses
|
|
266
265
|
return self._convert_components_to_list(components)
|
|
267
266
|
|
|
268
|
-
def _group_components_by_license(self,components):
|
|
269
|
-
"""
|
|
270
|
-
Groups components by their unique component-license pairs.
|
|
271
|
-
|
|
272
|
-
This method processes a list of components and creates unique entries for each
|
|
273
|
-
component-license combination. If a component has multiple licenses, it will create
|
|
274
|
-
separate entries for each license.
|
|
275
|
-
|
|
276
|
-
Args:
|
|
277
|
-
components: A list of component dictionaries. Each component should have:
|
|
278
|
-
- purl: Package URL identifying the component
|
|
279
|
-
- licenses: List of license dictionaries, each containing:
|
|
280
|
-
- spdxid: SPDX identifier for the license (optional)
|
|
281
|
-
|
|
282
|
-
Returns:
|
|
283
|
-
list: A list of dictionaries, each containing:
|
|
284
|
-
- purl: The component's package URL
|
|
285
|
-
- license: The SPDX identifier of the license (or 'Unknown' if not provided)
|
|
286
|
-
"""
|
|
287
|
-
component_licenses: dict = {}
|
|
288
|
-
for component in components:
|
|
289
|
-
for lic in component['licenses']:
|
|
290
|
-
spdxid = lic.get('spdxid', 'Unknown')
|
|
291
|
-
key = f'{component["purl"]}-{spdxid}'
|
|
292
|
-
component_licenses[key] = {
|
|
293
|
-
'purl': component['purl'],
|
|
294
|
-
'license': spdxid,
|
|
295
|
-
}
|
|
296
|
-
return list(component_licenses.values())
|
|
297
|
-
|
|
298
267
|
def run(self):
|
|
299
268
|
"""
|
|
300
269
|
Run the undeclared component inspection process.
|
|
@@ -145,10 +145,10 @@ class MyTestCase(unittest.TestCase):
|
|
|
145
145
|
copyleft = Copyleft(filepath=input_file_name, format_type='md', explicit='MIT')
|
|
146
146
|
status, results = copyleft.run()
|
|
147
147
|
expected_detail_output = (
|
|
148
|
-
'### Copyleft licenses \n | Component |
|
|
149
|
-
' | - | :-: | - | -
|
|
150
|
-
' | pkg:npm/%40electron/rebuild |
|
|
151
|
-
'| pkg:npm/%40emotion/react |
|
|
148
|
+
'### Copyleft licenses \n | Component | License | URL | Copyleft |\n'
|
|
149
|
+
' | - | :-: | - | - |\n'
|
|
150
|
+
' | pkg:npm/%40electron/rebuild | MIT | https://spdx.org/licenses/MIT.html | YES |\n'
|
|
151
|
+
'| pkg:npm/%40emotion/react | MIT | https://spdx.org/licenses/MIT.html | YES | \n'
|
|
152
152
|
)
|
|
153
153
|
expected_summary_output = '2 component(s) with copyleft licenses were found.\n'
|
|
154
154
|
self.assertEqual(
|
|
@@ -181,11 +181,14 @@ class MyTestCase(unittest.TestCase):
|
|
|
181
181
|
status, results = undeclared.run()
|
|
182
182
|
details = json.loads(results['details'])
|
|
183
183
|
summary = results['summary']
|
|
184
|
-
expected_summary_output = """
|
|
184
|
+
expected_summary_output = """3 undeclared component(s) were found.
|
|
185
185
|
Add the following snippet into your `sbom.json` file
|
|
186
186
|
```json
|
|
187
187
|
{
|
|
188
188
|
"components":[
|
|
189
|
+
{
|
|
190
|
+
"purl": "pkg:github/scanoss/jenkins-pipeline-example"
|
|
191
|
+
},
|
|
189
192
|
{
|
|
190
193
|
"purl": "pkg:github/scanoss/scanner.c"
|
|
191
194
|
},
|
|
@@ -195,7 +198,7 @@ class MyTestCase(unittest.TestCase):
|
|
|
195
198
|
]
|
|
196
199
|
}```
|
|
197
200
|
"""
|
|
198
|
-
self.assertEqual(len(details['components']),
|
|
201
|
+
self.assertEqual(len(details['components']), 4)
|
|
199
202
|
self.assertEqual(
|
|
200
203
|
re.sub(r'\s|\\(?!`)|\\(?=`)', '', summary), re.sub(r'\s|\\(?!`)|\\(?=`)', '', expected_summary_output)
|
|
201
204
|
)
|
|
@@ -216,14 +219,18 @@ class MyTestCase(unittest.TestCase):
|
|
|
216
219
|
expected_details_output = """ ### Undeclared components
|
|
217
220
|
| Component | License |
|
|
218
221
|
| - | - |
|
|
222
|
+
| pkg:github/scanoss/jenkins-pipeline-example | unknown |
|
|
219
223
|
| pkg:github/scanoss/scanner.c | GPL-2.0-only |
|
|
220
224
|
| pkg:github/scanoss/wfp | GPL-2.0-only | """
|
|
221
225
|
|
|
222
|
-
expected_summary_output = """
|
|
226
|
+
expected_summary_output = """3 undeclared component(s) were found.
|
|
223
227
|
Add the following snippet into your `sbom.json` file
|
|
224
228
|
```json
|
|
225
229
|
{
|
|
226
230
|
"components":[
|
|
231
|
+
{
|
|
232
|
+
"purl": "pkg:github/scanoss/jenkins-pipeline-example"
|
|
233
|
+
},
|
|
227
234
|
{
|
|
228
235
|
"purl": "pkg:github/scanoss/scanner.c"
|
|
229
236
|
},
|
|
@@ -256,16 +263,20 @@ class MyTestCase(unittest.TestCase):
|
|
|
256
263
|
expected_details_output = """ ### Undeclared components
|
|
257
264
|
| Component | License |
|
|
258
265
|
| - | - |
|
|
266
|
+
| pkg:github/scanoss/jenkins-pipeline-example | unknown |
|
|
259
267
|
| pkg:github/scanoss/scanner.c | GPL-2.0-only |
|
|
260
268
|
| pkg:github/scanoss/wfp | GPL-2.0-only | """
|
|
261
269
|
|
|
262
|
-
expected_summary_output = """
|
|
270
|
+
expected_summary_output = """3 undeclared component(s) were found.
|
|
263
271
|
Add the following snippet into your `scanoss.json` file
|
|
264
272
|
|
|
265
273
|
```json
|
|
266
274
|
{
|
|
267
275
|
"bom": {
|
|
268
276
|
"include": [
|
|
277
|
+
{
|
|
278
|
+
"purl": "pkg:github/scanoss/jenkins-pipeline-example"
|
|
279
|
+
},
|
|
269
280
|
{
|
|
270
281
|
"purl": "pkg:github/scanoss/scanner.c"
|
|
271
282
|
},
|
|
@@ -296,13 +307,16 @@ class MyTestCase(unittest.TestCase):
|
|
|
296
307
|
status, results = undeclared.run()
|
|
297
308
|
details = json.loads(results['details'])
|
|
298
309
|
summary = results['summary']
|
|
299
|
-
expected_summary_output = """
|
|
310
|
+
expected_summary_output = """3 undeclared component(s) were found.
|
|
300
311
|
Add the following snippet into your `scanoss.json` file
|
|
301
312
|
|
|
302
313
|
```json
|
|
303
314
|
{
|
|
304
315
|
"bom": {
|
|
305
316
|
"include": [
|
|
317
|
+
{
|
|
318
|
+
"purl": "pkg:github/scanoss/jenkins-pipeline-example"
|
|
319
|
+
},
|
|
306
320
|
{
|
|
307
321
|
"purl": "pkg:github/scanoss/scanner.c"
|
|
308
322
|
},
|
|
@@ -314,7 +328,7 @@ class MyTestCase(unittest.TestCase):
|
|
|
314
328
|
}
|
|
315
329
|
```"""
|
|
316
330
|
self.assertEqual(status, 0)
|
|
317
|
-
self.assertEqual(len(details['components']),
|
|
331
|
+
self.assertEqual(len(details['components']), 4)
|
|
318
332
|
self.assertEqual(
|
|
319
333
|
re.sub(r'\s|\\(?!`)|\\(?=`)', '', summary), re.sub(r'\s|\\(?!`)|\\(?=`)', '', expected_summary_output)
|
|
320
334
|
)
|
|
@@ -328,15 +342,19 @@ class MyTestCase(unittest.TestCase):
|
|
|
328
342
|
details = results['details']
|
|
329
343
|
summary = results['summary']
|
|
330
344
|
expected_details_output = """|*Component*|*License*|
|
|
345
|
+
|pkg:github/scanoss/jenkins-pipeline-example|unknown|
|
|
331
346
|
|pkg:github/scanoss/scanner.c|GPL-2.0-only|
|
|
332
347
|
|pkg:github/scanoss/wfp|GPL-2.0-only|
|
|
333
348
|
"""
|
|
334
|
-
expected_summary_output = """
|
|
349
|
+
expected_summary_output = """3 undeclared component(s) were found.
|
|
335
350
|
Add the following snippet into your `scanoss.json` file
|
|
336
351
|
{code:json}
|
|
337
352
|
{
|
|
338
353
|
"bom": {
|
|
339
354
|
"include": [
|
|
355
|
+
{
|
|
356
|
+
"purl": "pkg:github/scanoss/jenkins-pipeline-example"
|
|
357
|
+
},
|
|
340
358
|
{
|
|
341
359
|
"purl": "pkg:github/scanoss/scanner.c"
|
|
342
360
|
},
|
|
@@ -359,12 +377,10 @@ Add the following snippet into your `scanoss.json` file
|
|
|
359
377
|
copyleft = Copyleft(filepath=input_file_name, format_type='jira_md')
|
|
360
378
|
status, results = copyleft.run()
|
|
361
379
|
details = results['details']
|
|
362
|
-
expected_details_output = """|*Component*|*
|
|
363
|
-
|pkg:github/scanoss/scanner.c|
|
|
364
|
-
|pkg:github/scanoss/
|
|
365
|
-
|pkg:github/scanoss/
|
|
366
|
-
|pkg:github/scanoss/wfp|6afc1f6|GPL-2.0-only|https://spdx.org/licenses/GPL-2.0-only.html|YES|
|
|
367
|
-
|pkg:github/scanoss/engine|4.0.4|GPL-2.0-only|https://spdx.org/licenses/GPL-2.0-only.html|YES|
|
|
380
|
+
expected_details_output = """|*Component*|*License*|*URL*|*Copyleft*|
|
|
381
|
+
|pkg:github/scanoss/scanner.c|GPL-2.0-only|https://spdx.org/licenses/GPL-2.0-only.html|YES|
|
|
382
|
+
|pkg:github/scanoss/engine|GPL-2.0-only|https://spdx.org/licenses/GPL-2.0-only.html|YES|
|
|
383
|
+
|pkg:github/scanoss/wfp|GPL-2.0-only|https://spdx.org/licenses/GPL-2.0-only.html|YES|
|
|
368
384
|
"""
|
|
369
385
|
self.assertEqual(status, 0)
|
|
370
386
|
self.assertEqual(expected_details_output, details)
|
|
@@ -375,9 +391,8 @@ Add the following snippet into your `scanoss.json` file
|
|
|
375
391
|
input_file_name = os.path.join(script_dir, 'data', file_name)
|
|
376
392
|
i_license_summary = LicenseSummary(filepath=input_file_name)
|
|
377
393
|
license_summary = i_license_summary.run()
|
|
378
|
-
self.assertEqual(license_summary['
|
|
379
|
-
self.assertEqual(license_summary['
|
|
380
|
-
self.assertEqual(len(license_summary['licenses']), 2)
|
|
394
|
+
self.assertEqual(license_summary['detectedLicenses'], 3)
|
|
395
|
+
self.assertEqual(license_summary['detectedLicensesWithCopyleft'], 1)
|
|
381
396
|
|
|
382
397
|
def test_inspect_license_summary_with_empty_result(self):
|
|
383
398
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
@@ -385,8 +400,8 @@ Add the following snippet into your `scanoss.json` file
|
|
|
385
400
|
input_file_name = os.path.join(script_dir, 'data', file_name)
|
|
386
401
|
i_license_summary = LicenseSummary(filepath=input_file_name)
|
|
387
402
|
license_summary = i_license_summary.run()
|
|
388
|
-
self.assertEqual(license_summary['
|
|
389
|
-
self.assertEqual(license_summary['
|
|
403
|
+
self.assertEqual(license_summary['detectedLicenses'], 0)
|
|
404
|
+
self.assertEqual(license_summary['detectedLicensesWithCopyleft'], 0)
|
|
390
405
|
self.assertEqual(len(license_summary['licenses']), 0)
|
|
391
406
|
|
|
392
407
|
def test_inspect_component_summary(self):
|
|
@@ -396,9 +411,12 @@ Add the following snippet into your `scanoss.json` file
|
|
|
396
411
|
i_component_summary = ComponentSummary(filepath=input_file_name)
|
|
397
412
|
component_summary = i_component_summary.run()
|
|
398
413
|
print(component_summary)
|
|
399
|
-
self.assertEqual(component_summary['
|
|
400
|
-
self.assertEqual(component_summary['
|
|
401
|
-
self.assertEqual(component_summary['
|
|
414
|
+
self.assertEqual(component_summary['totalComponents'], 4)
|
|
415
|
+
self.assertEqual(component_summary['undeclaredComponents'], 3)
|
|
416
|
+
self.assertEqual(component_summary['declaredComponents'], 1)
|
|
417
|
+
self.assertEqual(component_summary['totalFilesDetected'], 8)
|
|
418
|
+
self.assertEqual(component_summary['totalFilesUndeclared'], 6)
|
|
419
|
+
self.assertEqual(component_summary['totalFilesDeclared'], 2)
|
|
402
420
|
|
|
403
421
|
def test_inspect_component_summary_empty_result(self):
|
|
404
422
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
@@ -406,10 +424,13 @@ Add the following snippet into your `scanoss.json` file
|
|
|
406
424
|
input_file_name = os.path.join(script_dir, 'data', file_name)
|
|
407
425
|
i_component_summary = ComponentSummary(filepath=input_file_name)
|
|
408
426
|
component_summary = i_component_summary.run()
|
|
409
|
-
self.assertEqual(component_summary['
|
|
410
|
-
self.assertEqual(component_summary['
|
|
411
|
-
self.assertEqual(component_summary['
|
|
427
|
+
self.assertEqual(component_summary['totalComponents'], 0)
|
|
428
|
+
self.assertEqual(component_summary['undeclaredComponents'], 0)
|
|
429
|
+
self.assertEqual(component_summary['declaredComponents'], 0)
|
|
412
430
|
self.assertEqual(len(component_summary['components']), 0)
|
|
431
|
+
self.assertEqual(component_summary['totalFilesDetected'], 0)
|
|
432
|
+
self.assertEqual(component_summary['totalFilesUndeclared'], 0)
|
|
433
|
+
self.assertEqual(component_summary['totalFilesDeclared'], 0)
|
|
413
434
|
|
|
414
435
|
if __name__ == '__main__':
|
|
415
436
|
unittest.main()
|
|
@@ -58,8 +58,8 @@ class MyTestCase(unittest.TestCase):
|
|
|
58
58
|
self.assertEqual(name, "SCANOSS-SBOM")
|
|
59
59
|
self.assertEqual(organization, "Organization: SCANOSS")
|
|
60
60
|
self.assertEqual(creation_info_comment, "SBOM Build information - SBOM Type: Build")
|
|
61
|
-
self.assertEqual(len(document_describes),
|
|
62
|
-
self.assertEqual(len(packages),
|
|
61
|
+
self.assertEqual(len(document_describes), 6)
|
|
62
|
+
self.assertEqual(len(packages), 6)
|
|
63
63
|
|
|
64
64
|
for package in packages:
|
|
65
65
|
for checksum in package.get("checksums", []):
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
date: 20250620142757, utime: 1750429677
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/components/v2/scanoss_components_pb2_grpc.py
RENAMED
|
File without changes
|
{scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/cryptography/v2/scanoss_cryptography_pb2.py
RENAMED
|
File without changes
|
{scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/cryptography/v2/scanoss_cryptography_pb2_grpc.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/dependencies/v2/scanoss_dependencies_pb2.py
RENAMED
|
File without changes
|
{scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/dependencies/v2/scanoss_dependencies_pb2_grpc.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/geoprovenance/v2/scanoss_geoprovenance_pb2.py
RENAMED
|
File without changes
|
{scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/geoprovenance/v2/scanoss_geoprovenance_pb2_grpc.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{scanoss-1.26.0 → scanoss-1.26.2}/src/scanoss/api/vulnerabilities/v2/scanoss_vulnerabilities_pb2.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|