capycli 2.0.0.dev8__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.
- License.md +27 -0
- capycli/__init__.py +214 -0
- capycli/__main__.py +13 -0
- capycli/bom/__init__.py +10 -0
- capycli/bom/bom_convert.py +163 -0
- capycli/bom/check_bom.py +187 -0
- capycli/bom/check_bom_item_status.py +197 -0
- capycli/bom/check_granularity.py +244 -0
- capycli/bom/create_components.py +644 -0
- capycli/bom/csv.py +69 -0
- capycli/bom/diff_bom.py +279 -0
- capycli/bom/download_sources.py +227 -0
- capycli/bom/filter_bom.py +323 -0
- capycli/bom/findsources.py +278 -0
- capycli/bom/handle_bom.py +134 -0
- capycli/bom/html.py +67 -0
- capycli/bom/legacy.py +312 -0
- capycli/bom/legacy_cx.py +151 -0
- capycli/bom/map_bom.py +1039 -0
- capycli/bom/merge_bom.py +155 -0
- capycli/bom/plaintext.py +69 -0
- capycli/bom/show_bom.py +77 -0
- capycli/common/__init__.py +9 -0
- capycli/common/capycli_bom_support.py +629 -0
- capycli/common/comparable_version.py +161 -0
- capycli/common/component_cache.py +240 -0
- capycli/common/dependencies_base.py +48 -0
- capycli/common/file_support.py +28 -0
- capycli/common/html_support.py +119 -0
- capycli/common/json_support.py +36 -0
- capycli/common/map_result.py +116 -0
- capycli/common/print.py +55 -0
- capycli/common/purl_service.py +169 -0
- capycli/common/purl_store.py +100 -0
- capycli/common/purl_utils.py +85 -0
- capycli/common/script_base.py +165 -0
- capycli/common/script_support.py +78 -0
- capycli/data/__init__.py +9 -0
- capycli/data/granularity_list.csv +1338 -0
- capycli/dependencies/__init__.py +9 -0
- capycli/dependencies/handle_dependencies.py +70 -0
- capycli/dependencies/javascript.py +261 -0
- capycli/dependencies/maven_list.py +333 -0
- capycli/dependencies/maven_pom.py +150 -0
- capycli/dependencies/nuget.py +184 -0
- capycli/dependencies/python.py +345 -0
- capycli/main/__init__.py +9 -0
- capycli/main/application.py +165 -0
- capycli/main/argument_parser.py +101 -0
- capycli/main/cli.py +28 -0
- capycli/main/exceptions.py +14 -0
- capycli/main/options.py +424 -0
- capycli/main/result_codes.py +41 -0
- capycli/mapping/handle_mapping.py +46 -0
- capycli/mapping/mapping_to_html.py +182 -0
- capycli/mapping/mapping_to_xlsx.py +197 -0
- capycli/moverview/handle_moverview.py +46 -0
- capycli/moverview/moverview_to_html.py +122 -0
- capycli/moverview/moverview_to_xlsx.py +170 -0
- capycli/project/__init__.py +9 -0
- capycli/project/check_prerequisites.py +304 -0
- capycli/project/create_bom.py +190 -0
- capycli/project/create_project.py +335 -0
- capycli/project/create_readme.py +546 -0
- capycli/project/find_project.py +128 -0
- capycli/project/get_license_info.py +246 -0
- capycli/project/handle_project.py +118 -0
- capycli/project/show_ecc.py +200 -0
- capycli/project/show_licenses.py +211 -0
- capycli/project/show_project.py +215 -0
- capycli/project/show_vulnerabilities.py +238 -0
- capycli-2.0.0.dev8.dist-info/LICENSES/CC0-1.0.txt +121 -0
- capycli-2.0.0.dev8.dist-info/LICENSES/MIT.txt +27 -0
- capycli-2.0.0.dev8.dist-info/License.md +27 -0
- capycli-2.0.0.dev8.dist-info/METADATA +268 -0
- capycli-2.0.0.dev8.dist-info/RECORD +78 -0
- capycli-2.0.0.dev8.dist-info/WHEEL +4 -0
- capycli-2.0.0.dev8.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# -------------------------------------------------------------------------------
|
|
2
|
+
# Copyright (c) 2019-23 Siemens
|
|
3
|
+
# All Rights Reserved.
|
|
4
|
+
# Author: thomas.graf@siemens.com
|
|
5
|
+
#
|
|
6
|
+
# SPDX-License-Identifier: MIT
|
|
7
|
+
# -------------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
import os
|
|
11
|
+
import shutil
|
|
12
|
+
import sys
|
|
13
|
+
import traceback
|
|
14
|
+
|
|
15
|
+
import cli_support
|
|
16
|
+
from colorama import Fore, Style
|
|
17
|
+
|
|
18
|
+
import capycli.common.script_base
|
|
19
|
+
from capycli.common.print import print_red, print_text, print_yellow
|
|
20
|
+
from capycli.main.result_codes import ResultCode
|
|
21
|
+
|
|
22
|
+
LOG = capycli.get_logger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ShowLicenses(capycli.common.script_base.ScriptBase):
|
|
26
|
+
TEMPFOLDER = ".\\_cli_temp_"
|
|
27
|
+
|
|
28
|
+
"""Show licenses of all cleared compponents."""
|
|
29
|
+
def __init__(self):
|
|
30
|
+
self.nodelete = False
|
|
31
|
+
self.global_license_list = []
|
|
32
|
+
|
|
33
|
+
@classmethod
|
|
34
|
+
def ensure_dir(cls, folder_path):
|
|
35
|
+
"""Ensures that the given path exists"""
|
|
36
|
+
if not os.path.exists(folder_path):
|
|
37
|
+
os.makedirs(folder_path)
|
|
38
|
+
|
|
39
|
+
if not os.path.exists(folder_path):
|
|
40
|
+
print_red(" Unable to create temp folder!")
|
|
41
|
+
|
|
42
|
+
@classmethod
|
|
43
|
+
def print_license_list(cls, license_list):
|
|
44
|
+
"""Displays the licenses color-coded"""
|
|
45
|
+
for lic in license_list:
|
|
46
|
+
color = Fore.RESET
|
|
47
|
+
check = lic.upper()
|
|
48
|
+
if "GPL" in check:
|
|
49
|
+
color = Fore.LIGHTYELLOW_EX
|
|
50
|
+
|
|
51
|
+
if "EPL" in check:
|
|
52
|
+
color = Fore.LIGHTYELLOW_EX
|
|
53
|
+
|
|
54
|
+
if "MPL" in check:
|
|
55
|
+
color = Fore.LIGHTYELLOW_EX
|
|
56
|
+
|
|
57
|
+
if "CDDL" in check:
|
|
58
|
+
color = Fore.LIGHTYELLOW_EX
|
|
59
|
+
|
|
60
|
+
if "CPL" in check:
|
|
61
|
+
color = Fore.LIGHTYELLOW_EX
|
|
62
|
+
|
|
63
|
+
print(color + lic + " ", end="", flush=True)
|
|
64
|
+
|
|
65
|
+
print(Style.RESET_ALL)
|
|
66
|
+
|
|
67
|
+
def process_release(self, release, tempfolder):
|
|
68
|
+
"""Processes a single release"""
|
|
69
|
+
if "_embedded" not in release:
|
|
70
|
+
print_red(" No license information available!")
|
|
71
|
+
return
|
|
72
|
+
|
|
73
|
+
if "sw360:attachments" not in release["_embedded"]:
|
|
74
|
+
print_red(" No license information available!")
|
|
75
|
+
return
|
|
76
|
+
|
|
77
|
+
cli_filename = ""
|
|
78
|
+
attachment_infos = release["_embedded"]["sw360:attachments"]
|
|
79
|
+
for key in attachment_infos:
|
|
80
|
+
att_href = key["_links"]["self"]["href"]
|
|
81
|
+
attachment = self.client.get_attachment_by_url(att_href)
|
|
82
|
+
if attachment.get("attachmentType", "") != "COMPONENT_LICENSE_INFO_XML":
|
|
83
|
+
continue
|
|
84
|
+
|
|
85
|
+
filename = key["filename"]
|
|
86
|
+
filename = os.path.join(tempfolder, filename)
|
|
87
|
+
release_id = self.client.get_id_from_href(release["_links"]["self"]["href"])
|
|
88
|
+
attachment_id = self.client.get_id_from_href(att_href)
|
|
89
|
+
self.client.download_release_attachment(filename, release_id, attachment_id)
|
|
90
|
+
if os.path.isfile(filename):
|
|
91
|
+
cli_filename = filename
|
|
92
|
+
break
|
|
93
|
+
else:
|
|
94
|
+
print_red(" Error downloading CLI file!")
|
|
95
|
+
|
|
96
|
+
if not cli_filename:
|
|
97
|
+
print_yellow(" No CLI file found!")
|
|
98
|
+
return
|
|
99
|
+
|
|
100
|
+
clifile = cli_support.CLI.CliFile()
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
clifile.read_from_file(cli_filename)
|
|
104
|
+
except OSError as ex:
|
|
105
|
+
print_red(" Error reading CLI file: " + cli_filename)
|
|
106
|
+
print_red(" Error '{0}' occured. Arguments {1}.".format(ex.errno, ex.args))
|
|
107
|
+
|
|
108
|
+
license_list = []
|
|
109
|
+
for lic in clifile.licenses:
|
|
110
|
+
license_list.append(lic.name + " (" + lic.spdx_identifier + ")")
|
|
111
|
+
if lic.name not in self.global_license_list:
|
|
112
|
+
self.global_license_list.append(lic.name)
|
|
113
|
+
|
|
114
|
+
self.print_license_list(license_list)
|
|
115
|
+
|
|
116
|
+
def show_licenses(self, id):
|
|
117
|
+
tempfolder = self.TEMPFOLDER
|
|
118
|
+
self.ensure_dir(tempfolder)
|
|
119
|
+
|
|
120
|
+
try:
|
|
121
|
+
project = self.client.get_project(id)
|
|
122
|
+
except Exception as ex:
|
|
123
|
+
print_red(
|
|
124
|
+
"Error searching for project: \n" +
|
|
125
|
+
repr(ex) + "\n" +
|
|
126
|
+
str(traceback.format_exc()))
|
|
127
|
+
sys.exit(ResultCode.RESULT_ERROR_ACCESSING_SW360)
|
|
128
|
+
|
|
129
|
+
print_text(" Project name: " + project["name"] + ", " + project["version"])
|
|
130
|
+
print_text(" Project owner: " + project["projectOwner"])
|
|
131
|
+
print_text(" Clearing state: " + project["clearingState"])
|
|
132
|
+
if self.nodelete:
|
|
133
|
+
print_text(" Temp folder", tempfolder, "will not get deleted.")
|
|
134
|
+
|
|
135
|
+
self.global_license_list = []
|
|
136
|
+
if "sw360:releases" in project["_embedded"]:
|
|
137
|
+
print_text("\nComponents: ")
|
|
138
|
+
releases = project["_embedded"]["sw360:releases"]
|
|
139
|
+
print_text(" Scanning", len(releases), "releases.")
|
|
140
|
+
for key in sorted(releases, key=lambda item: item["name"]):
|
|
141
|
+
href = key["_links"]["self"]["href"]
|
|
142
|
+
print_text("\n " + key["name"] + ", " + key["version"])
|
|
143
|
+
release = self.client.get_release_by_url(href)
|
|
144
|
+
|
|
145
|
+
try:
|
|
146
|
+
self.process_release(release, tempfolder)
|
|
147
|
+
except Exception as ex:
|
|
148
|
+
print_red("Error processing release: \n" + repr(ex))
|
|
149
|
+
|
|
150
|
+
print_text("\nLicense summary:")
|
|
151
|
+
self.print_license_list(self.global_license_list)
|
|
152
|
+
else:
|
|
153
|
+
print_text("\n No linked releases")
|
|
154
|
+
|
|
155
|
+
if not self.nodelete:
|
|
156
|
+
shutil.rmtree(tempfolder)
|
|
157
|
+
|
|
158
|
+
def show_command_help(self):
|
|
159
|
+
print("\nusage: CaPyCli project licenses [options]")
|
|
160
|
+
print("Options:")
|
|
161
|
+
print("""
|
|
162
|
+
-id ID SW360 id of the project
|
|
163
|
+
-t SW360_TOKEN use this token for access to SW360
|
|
164
|
+
-oa, this is an oauth2 token
|
|
165
|
+
-url SW360_URL use this URL for access to SW360
|
|
166
|
+
-name name of the project, component or release
|
|
167
|
+
-version version of the project, component or release
|
|
168
|
+
""")
|
|
169
|
+
|
|
170
|
+
print()
|
|
171
|
+
|
|
172
|
+
def run(self, args):
|
|
173
|
+
"""Main method()"""
|
|
174
|
+
if args.debug:
|
|
175
|
+
global LOG
|
|
176
|
+
LOG = capycli.get_logger(__name__)
|
|
177
|
+
else:
|
|
178
|
+
# suppress (debug) log output from requests and urllib
|
|
179
|
+
logging.getLogger("requests").setLevel(logging.WARNING)
|
|
180
|
+
logging.getLogger("urllib3").setLevel(logging.WARNING)
|
|
181
|
+
logging.getLogger("urllib3.connectionpool").setLevel(logging.WARNING)
|
|
182
|
+
|
|
183
|
+
print_text(
|
|
184
|
+
"\n" + capycli.APP_NAME + ", " + capycli.get_app_version() +
|
|
185
|
+
" - Show licenses of all cleared components.")
|
|
186
|
+
|
|
187
|
+
if args.help:
|
|
188
|
+
self.show_command_help()
|
|
189
|
+
return
|
|
190
|
+
|
|
191
|
+
if not self.login(token=args.sw360_token, url=args.sw360_url, oauth2=args.oauth2):
|
|
192
|
+
print_red("ERROR: login failed!")
|
|
193
|
+
sys.exit(ResultCode.RESULT_AUTH_ERROR)
|
|
194
|
+
|
|
195
|
+
name = args.name
|
|
196
|
+
version = None
|
|
197
|
+
if args.version:
|
|
198
|
+
version = args.version
|
|
199
|
+
|
|
200
|
+
if args.id:
|
|
201
|
+
self.show_licenses(args.id)
|
|
202
|
+
elif (args.name and args.version):
|
|
203
|
+
# find_project() is part of script_base.py
|
|
204
|
+
pid = self.find_project(name, version)
|
|
205
|
+
if pid:
|
|
206
|
+
self.show_licenses(pid)
|
|
207
|
+
else:
|
|
208
|
+
print_yellow(" No matching project found")
|
|
209
|
+
else:
|
|
210
|
+
print_red("Neither name and version nor project id specified!")
|
|
211
|
+
sys.exit(ResultCode.RESULT_COMMAND_ERROR)
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# -------------------------------------------------------------------------------
|
|
2
|
+
# Copyright (c) 2019-23 Siemens
|
|
3
|
+
# All Rights Reserved.
|
|
4
|
+
# Author: thomas.graf@siemens.com
|
|
5
|
+
#
|
|
6
|
+
# SPDX-License-Identifier: MIT
|
|
7
|
+
# -------------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
import sys
|
|
11
|
+
|
|
12
|
+
import sw360
|
|
13
|
+
from colorama import Fore
|
|
14
|
+
|
|
15
|
+
import capycli.common.json_support
|
|
16
|
+
import capycli.common.script_base
|
|
17
|
+
from capycli.common.print import print_red, print_text, print_yellow
|
|
18
|
+
from capycli.main.result_codes import ResultCode
|
|
19
|
+
|
|
20
|
+
LOG = capycli.get_logger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ShowProject(capycli.common.script_base.ScriptBase):
|
|
24
|
+
"""Show project details."""
|
|
25
|
+
|
|
26
|
+
def get_clearing_state(self, proj: dict, href: str):
|
|
27
|
+
"""Returns the clearing state of the given component/release"""
|
|
28
|
+
rel = proj["linkedReleases"]
|
|
29
|
+
for key in rel:
|
|
30
|
+
if key["release"] == href:
|
|
31
|
+
return key["mainlineState"]
|
|
32
|
+
|
|
33
|
+
return None
|
|
34
|
+
|
|
35
|
+
def show_project_status(self, result: dict):
|
|
36
|
+
if not result:
|
|
37
|
+
return
|
|
38
|
+
|
|
39
|
+
print_text(" Project name: " + result["Name"] + ", " + result["Version"])
|
|
40
|
+
if "ProjectResponsible" in result:
|
|
41
|
+
print_text(" Project responsible: " + result["ProjectResponsible"])
|
|
42
|
+
print_text(" Project owner: " + result["ProjectOwner"])
|
|
43
|
+
print_text(" Clearing state: " + result["ClearingState"])
|
|
44
|
+
|
|
45
|
+
if len(result["Projects"]) > 0:
|
|
46
|
+
print_text("\n Linked projects: ")
|
|
47
|
+
for project in result["Projects"]:
|
|
48
|
+
print_text(" " + project["Name"] + ", " + project["Version"])
|
|
49
|
+
else:
|
|
50
|
+
print_text("\n No linked projects")
|
|
51
|
+
|
|
52
|
+
if len(result["Releases"]) > 0:
|
|
53
|
+
print_text("\n Components: ")
|
|
54
|
+
releases = result["Releases"]
|
|
55
|
+
releases.sort(key=lambda s: s["Name"].lower())
|
|
56
|
+
for release in releases:
|
|
57
|
+
state = self.get_clearing_state(self.project, release["Href"])
|
|
58
|
+
prereq = ""
|
|
59
|
+
if state == "OPEN":
|
|
60
|
+
print(Fore.LIGHTYELLOW_EX, end="", flush=True)
|
|
61
|
+
if release["SourceAvailable"] == "False":
|
|
62
|
+
print(Fore.LIGHTRED_EX, end="", flush=True)
|
|
63
|
+
prereq = "; No source provided"
|
|
64
|
+
else:
|
|
65
|
+
prereq = ""
|
|
66
|
+
|
|
67
|
+
print(
|
|
68
|
+
" " + release["Name"] +
|
|
69
|
+
", " + release["Version"] + " = " +
|
|
70
|
+
release.get("ProjectClearingState", "Unknown") + ", " +
|
|
71
|
+
release.get("ClearingState", "Unknown") +
|
|
72
|
+
prereq + Fore.RESET)
|
|
73
|
+
else:
|
|
74
|
+
print_text(" No linked releases")
|
|
75
|
+
|
|
76
|
+
def get_project_status(self, project_id: str) -> dict:
|
|
77
|
+
"""Get the project status for the project with the specified id"""
|
|
78
|
+
print_text("Retrieving project details...")
|
|
79
|
+
result = {}
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
self.project = self.client.get_project(project_id)
|
|
83
|
+
except sw360.SW360Error as swex:
|
|
84
|
+
print_red(" ERROR: unable to access project: " + repr(swex))
|
|
85
|
+
sys.exit(ResultCode.RESULT_ERROR_ACCESSING_SW360)
|
|
86
|
+
|
|
87
|
+
if not self.project:
|
|
88
|
+
print_red(" ERROR: unable to read project data!")
|
|
89
|
+
sys.exit(ResultCode.RESULT_ERROR_ACCESSING_SW360)
|
|
90
|
+
|
|
91
|
+
result["Name"] = self.project.get("name", "")
|
|
92
|
+
result["Version"] = self.project.get("version", "")
|
|
93
|
+
result["ProjectOwner"] = self.project.get("projectOwner", "")
|
|
94
|
+
result["ProjectResponsible"] = self.project.get("projectResponsible", "")
|
|
95
|
+
result["SecurityResponsibles"] = self.project.get("securityResponsibles", [])
|
|
96
|
+
result["BusinessUnit"] = self.project.get("businessUnit", "")
|
|
97
|
+
result["Tag"] = self.project.get("tag", "")
|
|
98
|
+
result["enableSvm"] = self.project.get("enableSvm", None)
|
|
99
|
+
result["EnableVulnerabilitiesDisplay"] = self.project.get("enableVulnerabilitiesDisplay", None)
|
|
100
|
+
result["ClearingState"] = self.project.get("clearingState", "OPEN")
|
|
101
|
+
if self.sw360_url:
|
|
102
|
+
result["ProjectLink"] = (
|
|
103
|
+
self.sw360_url + "group/guest/projects/-/project/detail/" + project_id
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
result["Releases"] = []
|
|
107
|
+
|
|
108
|
+
if "sw360:releases" in self.project["_embedded"]:
|
|
109
|
+
releases = self.project["_embedded"]["sw360:releases"]
|
|
110
|
+
releases.sort(key=lambda s: s["name"].lower())
|
|
111
|
+
for release in releases:
|
|
112
|
+
href = release["_links"]["self"]["href"]
|
|
113
|
+
state = self.get_clearing_state(self.project, href)
|
|
114
|
+
|
|
115
|
+
rel_item = {}
|
|
116
|
+
rel_item["Name"] = release["name"]
|
|
117
|
+
rel_item["Version"] = release["version"]
|
|
118
|
+
rel_item["ProjectClearingState"] = state
|
|
119
|
+
rel_item["Id"] = self.client.get_id_from_href(href)
|
|
120
|
+
rel_item["Sw360Id"] = rel_item["Id"]
|
|
121
|
+
rel_item["Href"] = href
|
|
122
|
+
rel_item["Url"] = (
|
|
123
|
+
self.sw360_url
|
|
124
|
+
+ "group/guest/components/-/component/release/detailRelease/"
|
|
125
|
+
+ self.client.get_id_from_href(href))
|
|
126
|
+
|
|
127
|
+
try:
|
|
128
|
+
release_details = self.client.get_release_by_url(href)
|
|
129
|
+
# capycli.common.json_support.print_json(release_details)
|
|
130
|
+
rel_item["ClearingState"] = release_details["clearingState"]
|
|
131
|
+
rel_item["ReleaseMainlineState"] = release_details.get("mainlineState", "")
|
|
132
|
+
rel_item["SourceAvailable"] = "False"
|
|
133
|
+
if "externalIds" in release_details:
|
|
134
|
+
rel_item["ExternalIds"] = release_details["externalIds"]
|
|
135
|
+
if "_embedded" in release_details:
|
|
136
|
+
if "sw360:attachments" in release_details["_embedded"]:
|
|
137
|
+
att = release_details["_embedded"]["sw360:attachments"]
|
|
138
|
+
for key in att:
|
|
139
|
+
if key["attachmentType"] == "SOURCE":
|
|
140
|
+
rel_item["SourceAvailable"] = "True"
|
|
141
|
+
except sw360.SW360Error as swex:
|
|
142
|
+
print_red(" ERROR: unable to access project:" + repr(swex))
|
|
143
|
+
sys.exit(ResultCode.RESULT_ERROR_ACCESSING_SW360)
|
|
144
|
+
|
|
145
|
+
result["Releases"].append(rel_item)
|
|
146
|
+
|
|
147
|
+
result["Projects"] = []
|
|
148
|
+
if "sw360:projects" in self.project["_embedded"]:
|
|
149
|
+
projects = self.project["_embedded"]["sw360:projects"]
|
|
150
|
+
projects.sort(key=lambda s: s["name"].lower())
|
|
151
|
+
for project in projects:
|
|
152
|
+
proj_item = {}
|
|
153
|
+
proj_item["Name"] = project["name"]
|
|
154
|
+
proj_item["Version"] = project["version"]
|
|
155
|
+
proj_item["Href"] = project["_links"]["self"]["href"]
|
|
156
|
+
result["Projects"].append(proj_item)
|
|
157
|
+
|
|
158
|
+
return result
|
|
159
|
+
|
|
160
|
+
def run(self, args):
|
|
161
|
+
"""Main method()"""
|
|
162
|
+
if args.debug:
|
|
163
|
+
global LOG
|
|
164
|
+
LOG = capycli.get_logger(__name__)
|
|
165
|
+
else:
|
|
166
|
+
# suppress (debug) log output from requests and urllib
|
|
167
|
+
logging.getLogger("requests").setLevel(logging.WARNING)
|
|
168
|
+
logging.getLogger("urllib3").setLevel(logging.WARNING)
|
|
169
|
+
logging.getLogger("urllib3.connectionpool").setLevel(logging.WARNING)
|
|
170
|
+
|
|
171
|
+
print_text(
|
|
172
|
+
"\n" + capycli.APP_NAME + ", " + capycli.get_app_version() +
|
|
173
|
+
" - Show project details\n")
|
|
174
|
+
|
|
175
|
+
if args.help:
|
|
176
|
+
print("usage: CaPyCli project show [-h] -t TOKEN -name NAME -version VERSION"
|
|
177
|
+
"[-id PROJECT_ID] [-o OUTPUTFILE]")
|
|
178
|
+
print("")
|
|
179
|
+
print("optional arguments:")
|
|
180
|
+
print(" -h, --help show this help message and exit")
|
|
181
|
+
print(" -name NAME name of the project")
|
|
182
|
+
print(" -version VERSION version of the project")
|
|
183
|
+
print(" -id PROJECT_ID SW360 id of the project, supersedes name and version parameters")
|
|
184
|
+
print(" -t SW360_TOKEN use this token for access to SW360")
|
|
185
|
+
print(" -oa, this is an oauth2 token")
|
|
186
|
+
print(" -url SW360_URL use this URL for access to SW360")
|
|
187
|
+
print(" -o OUTPUTFILE output file to write project details to")
|
|
188
|
+
return
|
|
189
|
+
|
|
190
|
+
if not self.login(token=args.sw360_token, url=args.sw360_url, oauth2=args.oauth2):
|
|
191
|
+
print_red("ERROR: login failed!")
|
|
192
|
+
sys.exit(ResultCode.RESULT_AUTH_ERROR)
|
|
193
|
+
|
|
194
|
+
name = args.name
|
|
195
|
+
version = None
|
|
196
|
+
pid = None
|
|
197
|
+
if args.version:
|
|
198
|
+
version = args.version
|
|
199
|
+
|
|
200
|
+
if args.id:
|
|
201
|
+
pid = args.id
|
|
202
|
+
elif (args.name and args.version):
|
|
203
|
+
# find_project() is part of script_base.py
|
|
204
|
+
pid = self.find_project(name, version)
|
|
205
|
+
else:
|
|
206
|
+
print_red("Neither name and version nor project id specified!")
|
|
207
|
+
sys.exit(ResultCode.RESULT_COMMAND_ERROR)
|
|
208
|
+
|
|
209
|
+
if pid:
|
|
210
|
+
status = self.get_project_status(pid)
|
|
211
|
+
self.show_project_status(status)
|
|
212
|
+
if args.outputfile:
|
|
213
|
+
capycli.common.json_support.write_json_to_file(status, args.outputfile)
|
|
214
|
+
else:
|
|
215
|
+
print_yellow(" No matching project found")
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
# -------------------------------------------------------------------------------
|
|
2
|
+
# Copyright (c) 2020-2023 Siemens
|
|
3
|
+
# All Rights Reserved.
|
|
4
|
+
# Author: thomas.graf@siemens.com
|
|
5
|
+
#
|
|
6
|
+
# SPDX-License-Identifier: MIT
|
|
7
|
+
# -------------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
import sys
|
|
11
|
+
|
|
12
|
+
import requests
|
|
13
|
+
import sw360
|
|
14
|
+
from colorama import Fore, Style
|
|
15
|
+
|
|
16
|
+
import capycli.common.json_support
|
|
17
|
+
import capycli.common.script_base
|
|
18
|
+
from capycli.common.print import print_green, print_red, print_text, print_yellow
|
|
19
|
+
from capycli.main.result_codes import ResultCode
|
|
20
|
+
|
|
21
|
+
LOG = capycli.get_logger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ShowSecurityVulnerability(capycli.common.script_base.ScriptBase):
|
|
25
|
+
"""Show security vulnerabilities of a project."""
|
|
26
|
+
|
|
27
|
+
def __init__(self):
|
|
28
|
+
"""Initialize."""
|
|
29
|
+
self.verbose = False
|
|
30
|
+
self.format = "text"
|
|
31
|
+
|
|
32
|
+
def list_projects(self, name, version):
|
|
33
|
+
try:
|
|
34
|
+
print_text(" Searching for projects by name (and version)")
|
|
35
|
+
projects = self.client.get_projects_by_name(name)
|
|
36
|
+
|
|
37
|
+
if not projects:
|
|
38
|
+
print_yellow(" No matching project found!")
|
|
39
|
+
sys.exit(ResultCode.RESULT_PROJECT_NOT_FOUND)
|
|
40
|
+
|
|
41
|
+
for project in projects:
|
|
42
|
+
if version:
|
|
43
|
+
proj_version = project.get("version", "")
|
|
44
|
+
if proj_version == version:
|
|
45
|
+
self.display_project(project)
|
|
46
|
+
else:
|
|
47
|
+
self.display_project(project)
|
|
48
|
+
|
|
49
|
+
except Exception as ex:
|
|
50
|
+
print_red("Error searching for project: \n" + repr(ex))
|
|
51
|
+
|
|
52
|
+
def show_project_by_id(self, project_id) -> dict:
|
|
53
|
+
"""
|
|
54
|
+
Show information about a single project by project id.
|
|
55
|
+
"""
|
|
56
|
+
try:
|
|
57
|
+
project = self.client.get_project(project_id)
|
|
58
|
+
if not project:
|
|
59
|
+
print_yellow("No project with given id found!")
|
|
60
|
+
sys.exit(ResultCode.RESULT_PROJECT_NOT_FOUND)
|
|
61
|
+
|
|
62
|
+
return self.display_project(project)
|
|
63
|
+
except sw360.sw360_api.SW360Error as swex:
|
|
64
|
+
if swex.response.status_code == requests.codes['not_found']:
|
|
65
|
+
print_yellow("Project not found!")
|
|
66
|
+
sys.exit(ResultCode.RESULT_PROJECT_NOT_FOUND)
|
|
67
|
+
else:
|
|
68
|
+
print_red("Error searching for project: \n")
|
|
69
|
+
print_red(" Status Code: " + str(swex.response.status_code))
|
|
70
|
+
if swex.message:
|
|
71
|
+
print_red(" Message: " + swex.message)
|
|
72
|
+
sys.exit(ResultCode.RESULT_ERROR_ACCESSING_SW360)
|
|
73
|
+
|
|
74
|
+
def display_project(self, project, pid=-1) -> dict:
|
|
75
|
+
"""
|
|
76
|
+
Show information about a single project.
|
|
77
|
+
"""
|
|
78
|
+
report = {}
|
|
79
|
+
href = None
|
|
80
|
+
if pid > -1:
|
|
81
|
+
project = self.client.get_project(pid)
|
|
82
|
+
else:
|
|
83
|
+
href = project["_links"]["self"]["href"]
|
|
84
|
+
project = self.client.get_project_by_url(href)
|
|
85
|
+
|
|
86
|
+
if not href:
|
|
87
|
+
href = project["_links"]["self"]["href"]
|
|
88
|
+
|
|
89
|
+
report["Name"] = project["name"]
|
|
90
|
+
report["Version"] = project.get("version", "")
|
|
91
|
+
report["Id"] = self.client.get_id_from_href(href)
|
|
92
|
+
report["Sw360Id"] = report["Id"]
|
|
93
|
+
report["Vulnerabilities"] = []
|
|
94
|
+
|
|
95
|
+
print_text("Project information:")
|
|
96
|
+
print_text(" Name:", project["name"])
|
|
97
|
+
print_text(" Version:", project.get("version", ""))
|
|
98
|
+
if self.verbose:
|
|
99
|
+
print_text(" Id:", self.client.get_id_from_href(href))
|
|
100
|
+
|
|
101
|
+
vuls = self.client.get_project_vulnerabilities(self.client.get_id_from_href(href))
|
|
102
|
+
# capycli.common.json_support.write_json_to_file(vuls, "vuls.json")
|
|
103
|
+
if "_embedded" not in vuls:
|
|
104
|
+
return report
|
|
105
|
+
|
|
106
|
+
# 2022-07-01: SW360 changed "sw360:vulnerabilityDToes" to "sw360:vulnerabilityDTOes" - arrgghhh
|
|
107
|
+
if "sw360:vulnerabilityDTOes" not in vuls["_embedded"]:
|
|
108
|
+
return report
|
|
109
|
+
|
|
110
|
+
report["Vulnerabilities"] = vuls["_embedded"]["sw360:vulnerabilityDTOes"]
|
|
111
|
+
|
|
112
|
+
print_text("\nVulnerabilities: ")
|
|
113
|
+
if len(report["Vulnerabilities"]) == 0:
|
|
114
|
+
print_green(" No security vulnerabilities known or feature not enabled\n")
|
|
115
|
+
|
|
116
|
+
for vu in report["Vulnerabilities"]:
|
|
117
|
+
relevance = vu.get("projectRelevance", "???")
|
|
118
|
+
prio = vu.get("priority", "???")
|
|
119
|
+
color = Style.RESET_ALL
|
|
120
|
+
if relevance == "RESOLVED":
|
|
121
|
+
color = Fore.LIGHTGREEN_EX
|
|
122
|
+
elif relevance == "IN_ANALYSIS":
|
|
123
|
+
color = Fore.LIGHTYELLOW_EX
|
|
124
|
+
elif ((prio == "1 - critical") or (prio == "2 - major")):
|
|
125
|
+
color = Fore.LIGHTRED_EX
|
|
126
|
+
|
|
127
|
+
print_text(color, " Priority: ", prio)
|
|
128
|
+
print_text(" Project Relevance:", relevance)
|
|
129
|
+
print_text(" Project Comment: ", vu.get("comment", "???"))
|
|
130
|
+
print_text(" Project Action: ", vu.get("projectAction", "???"))
|
|
131
|
+
print_text(" Component: ", vu.get("intReleaseName", "???"))
|
|
132
|
+
print(Style.RESET_ALL)
|
|
133
|
+
|
|
134
|
+
return report
|
|
135
|
+
|
|
136
|
+
def check_report_for_critical_findings(self, report: dict, prio_text: str) -> bool:
|
|
137
|
+
"""
|
|
138
|
+
Checks the report data for critical findings, i.e. a vulnerability
|
|
139
|
+
with priority less than or equal to prio and greater than 0.
|
|
140
|
+
1 - critical
|
|
141
|
+
2 - major
|
|
142
|
+
3 - minor
|
|
143
|
+
"""
|
|
144
|
+
|
|
145
|
+
try:
|
|
146
|
+
prio = int(prio_text)
|
|
147
|
+
except ValueError:
|
|
148
|
+
prio = 0
|
|
149
|
+
|
|
150
|
+
if prio < 0:
|
|
151
|
+
prio = 0
|
|
152
|
+
|
|
153
|
+
if prio > 5:
|
|
154
|
+
prio = 5
|
|
155
|
+
|
|
156
|
+
if prio == 0:
|
|
157
|
+
# no check requested
|
|
158
|
+
return False
|
|
159
|
+
|
|
160
|
+
for v in report.get("Vulnerabilities", []):
|
|
161
|
+
vprio_text = v.get("priority", "0")
|
|
162
|
+
if len(vprio_text) < 1:
|
|
163
|
+
continue
|
|
164
|
+
|
|
165
|
+
vprio = int(vprio_text[0])
|
|
166
|
+
if vprio > prio:
|
|
167
|
+
continue
|
|
168
|
+
|
|
169
|
+
if v.get("projectRelevance", "???") == "NOT_CHECKED":
|
|
170
|
+
return True
|
|
171
|
+
|
|
172
|
+
return False
|
|
173
|
+
|
|
174
|
+
def show_command_help(self):
|
|
175
|
+
print("\nusage: CaPyCli project vulnerabilities [options]")
|
|
176
|
+
print("Options:")
|
|
177
|
+
print("""
|
|
178
|
+
-id ID SW360 id of the project
|
|
179
|
+
-t SW360_TOKEN use this token for access to SW360
|
|
180
|
+
-oa, this is an oauth2 token
|
|
181
|
+
-url SW360_URL use this URL for access to SW360
|
|
182
|
+
-name name of the project, component or release
|
|
183
|
+
-version version of the project, component or release
|
|
184
|
+
-v be verbose
|
|
185
|
+
-format FMT output format, one of [text, json], default is text
|
|
186
|
+
-fe PRIO minimum vulnerability priority to force exit code != 0
|
|
187
|
+
""")
|
|
188
|
+
|
|
189
|
+
print()
|
|
190
|
+
|
|
191
|
+
def run(self, args):
|
|
192
|
+
"""Main method()"""
|
|
193
|
+
if args.debug:
|
|
194
|
+
global LOG
|
|
195
|
+
LOG = capycli.get_logger(__name__)
|
|
196
|
+
else:
|
|
197
|
+
# suppress (debug) log output from requests and urllib
|
|
198
|
+
logging.getLogger("requests").setLevel(logging.WARNING)
|
|
199
|
+
logging.getLogger("urllib3").setLevel(logging.WARNING)
|
|
200
|
+
logging.getLogger("urllib3.connectionpool").setLevel(logging.WARNING)
|
|
201
|
+
|
|
202
|
+
print_text(
|
|
203
|
+
"\n" + capycli.APP_NAME + ", " + capycli.get_app_version() +
|
|
204
|
+
" - Show security vulnerabilities of a project")
|
|
205
|
+
|
|
206
|
+
if args.help:
|
|
207
|
+
self.show_command_help()
|
|
208
|
+
return
|
|
209
|
+
|
|
210
|
+
if args.verbose:
|
|
211
|
+
self.verbose = args.verbose
|
|
212
|
+
|
|
213
|
+
self.format = args.format
|
|
214
|
+
if args.verbose:
|
|
215
|
+
print("Output format is", self.format)
|
|
216
|
+
|
|
217
|
+
if not self.login(token=args.sw360_token, url=args.sw360_url, oauth2=args.oauth2):
|
|
218
|
+
print_red("ERROR: login failed!")
|
|
219
|
+
sys.exit(ResultCode.RESULT_AUTH_ERROR)
|
|
220
|
+
|
|
221
|
+
report = None
|
|
222
|
+
if args.id:
|
|
223
|
+
report = self.show_project_by_id(args.id)
|
|
224
|
+
if args.outputfile and report:
|
|
225
|
+
capycli.common.json_support.write_json_to_file(report, args.outputfile)
|
|
226
|
+
else:
|
|
227
|
+
if args.name:
|
|
228
|
+
self.list_projects(args.name, args.version)
|
|
229
|
+
else:
|
|
230
|
+
print_red("Neither name nor external id specified!")
|
|
231
|
+
sys.exit(ResultCode.RESULT_COMMAND_ERROR)
|
|
232
|
+
|
|
233
|
+
# do we have to set an exit code?
|
|
234
|
+
if args.force_exit and report:
|
|
235
|
+
critical = self.check_report_for_critical_findings(report, args.force_exit)
|
|
236
|
+
if critical:
|
|
237
|
+
print_yellow("Unhandled security vulnerability found, exiting with code != 0")
|
|
238
|
+
sys.exit(ResultCode.RESULT_UNHANDLED_SECURITY_VULNERABILITY_FOUND)
|