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.
Files changed (78) hide show
  1. License.md +27 -0
  2. capycli/__init__.py +214 -0
  3. capycli/__main__.py +13 -0
  4. capycli/bom/__init__.py +10 -0
  5. capycli/bom/bom_convert.py +163 -0
  6. capycli/bom/check_bom.py +187 -0
  7. capycli/bom/check_bom_item_status.py +197 -0
  8. capycli/bom/check_granularity.py +244 -0
  9. capycli/bom/create_components.py +644 -0
  10. capycli/bom/csv.py +69 -0
  11. capycli/bom/diff_bom.py +279 -0
  12. capycli/bom/download_sources.py +227 -0
  13. capycli/bom/filter_bom.py +323 -0
  14. capycli/bom/findsources.py +278 -0
  15. capycli/bom/handle_bom.py +134 -0
  16. capycli/bom/html.py +67 -0
  17. capycli/bom/legacy.py +312 -0
  18. capycli/bom/legacy_cx.py +151 -0
  19. capycli/bom/map_bom.py +1039 -0
  20. capycli/bom/merge_bom.py +155 -0
  21. capycli/bom/plaintext.py +69 -0
  22. capycli/bom/show_bom.py +77 -0
  23. capycli/common/__init__.py +9 -0
  24. capycli/common/capycli_bom_support.py +629 -0
  25. capycli/common/comparable_version.py +161 -0
  26. capycli/common/component_cache.py +240 -0
  27. capycli/common/dependencies_base.py +48 -0
  28. capycli/common/file_support.py +28 -0
  29. capycli/common/html_support.py +119 -0
  30. capycli/common/json_support.py +36 -0
  31. capycli/common/map_result.py +116 -0
  32. capycli/common/print.py +55 -0
  33. capycli/common/purl_service.py +169 -0
  34. capycli/common/purl_store.py +100 -0
  35. capycli/common/purl_utils.py +85 -0
  36. capycli/common/script_base.py +165 -0
  37. capycli/common/script_support.py +78 -0
  38. capycli/data/__init__.py +9 -0
  39. capycli/data/granularity_list.csv +1338 -0
  40. capycli/dependencies/__init__.py +9 -0
  41. capycli/dependencies/handle_dependencies.py +70 -0
  42. capycli/dependencies/javascript.py +261 -0
  43. capycli/dependencies/maven_list.py +333 -0
  44. capycli/dependencies/maven_pom.py +150 -0
  45. capycli/dependencies/nuget.py +184 -0
  46. capycli/dependencies/python.py +345 -0
  47. capycli/main/__init__.py +9 -0
  48. capycli/main/application.py +165 -0
  49. capycli/main/argument_parser.py +101 -0
  50. capycli/main/cli.py +28 -0
  51. capycli/main/exceptions.py +14 -0
  52. capycli/main/options.py +424 -0
  53. capycli/main/result_codes.py +41 -0
  54. capycli/mapping/handle_mapping.py +46 -0
  55. capycli/mapping/mapping_to_html.py +182 -0
  56. capycli/mapping/mapping_to_xlsx.py +197 -0
  57. capycli/moverview/handle_moverview.py +46 -0
  58. capycli/moverview/moverview_to_html.py +122 -0
  59. capycli/moverview/moverview_to_xlsx.py +170 -0
  60. capycli/project/__init__.py +9 -0
  61. capycli/project/check_prerequisites.py +304 -0
  62. capycli/project/create_bom.py +190 -0
  63. capycli/project/create_project.py +335 -0
  64. capycli/project/create_readme.py +546 -0
  65. capycli/project/find_project.py +128 -0
  66. capycli/project/get_license_info.py +246 -0
  67. capycli/project/handle_project.py +118 -0
  68. capycli/project/show_ecc.py +200 -0
  69. capycli/project/show_licenses.py +211 -0
  70. capycli/project/show_project.py +215 -0
  71. capycli/project/show_vulnerabilities.py +238 -0
  72. capycli-2.0.0.dev8.dist-info/LICENSES/CC0-1.0.txt +121 -0
  73. capycli-2.0.0.dev8.dist-info/LICENSES/MIT.txt +27 -0
  74. capycli-2.0.0.dev8.dist-info/License.md +27 -0
  75. capycli-2.0.0.dev8.dist-info/METADATA +268 -0
  76. capycli-2.0.0.dev8.dist-info/RECORD +78 -0
  77. capycli-2.0.0.dev8.dist-info/WHEEL +4 -0
  78. 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)