From 791186b7c8119ada8887af34c6b01ff3e2a38a48 Mon Sep 17 00:00:00 2001 From: Thomas Graf Date: Thu, 28 May 2026 14:56:45 +0200 Subject: [PATCH 1/2] feat: new options `-client_id` and `-client_secret` to support dynamic token generation --- ChangeLog.md | 2 + capycli/bom/check_bom.py | 30 ++++++++++----- capycli/bom/check_bom_item_status.py | 23 +++++++++--- capycli/bom/create_components.py | 42 ++++++++++++++------- capycli/bom/findsources.py | 34 ++++++++++++----- capycli/bom/map_bom.py | 17 ++++++++- capycli/common/component_cache.py | 1 - capycli/common/purl_service.py | 2 +- capycli/common/script_base.py | 34 +++++++++++++---- capycli/main/options.py | 12 ++++++ capycli/project/check_prerequisites.py | 37 ++++++++++++++----- capycli/project/create_bom.py | 34 ++++++++++++----- capycli/project/create_project.py | 42 ++++++++++++++------- capycli/project/find_project.py | 30 +++++++++++---- capycli/project/get_license_info.py | 21 +++++++++-- capycli/project/project_component_check.py | 43 +++++++++++++++------- capycli/project/show_ecc.py | 35 +++++++++++++----- capycli/project/show_licenses.py | 31 ++++++++++++---- capycli/project/show_project.py | 40 ++++++++++++++------ capycli/project/show_vulnerabilities.py | 40 ++++++++++++++------ poetry.lock | 18 ++++----- pyproject.toml | 7 ++-- tests/test_base.py | 2 + tests/test_script_base.py | 2 +- tests/test_update_project.py | 2 +- tox.ini | 2 +- 26 files changed, 422 insertions(+), 161 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index fc60aa6f..2dac1700 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -11,6 +11,8 @@ Fixes CaPyCLI so that it doesn't crash. But the root cause cannot get fixed: SW360 releases may have multiple purls, CycloneDX components can have only one single purl. +* New options `-client_id` and `-client_secret` to support dynamic token generation + with the new SW360 backend >= 20. ## 2.11.1 diff --git a/capycli/bom/check_bom.py b/capycli/bom/check_bom.py index 6af5b206..81990181 100644 --- a/capycli/bom/check_bom.py +++ b/capycli/bom/check_bom.py @@ -1,5 +1,5 @@ # ------------------------------------------------------------------------------- -# Copyright (c) 2019-2024 Siemens +# Copyright (c) 2019-2026 Siemens # All Rights Reserved. # Author: thomas.graf@siemens.com # @@ -15,12 +15,12 @@ from colorama import Fore, Style from cyclonedx.model.bom import Bom from cyclonedx.model.component import Component -from sw360 import SW360Error import capycli.common.script_base from capycli.common.capycli_bom_support import CaPyCliBom, CycloneDxSupport from capycli.common.print import print_green, print_red, print_text, print_yellow from capycli.main.result_codes import ResultCode +from sw360 import SW360Error, SW360Keycloak LOG = capycli.get_logger(__name__) @@ -165,13 +165,14 @@ def run(self, args: Any) -> None: print("usage: CaPyCli bom check [-h] [-t SW360_TOKEN] [-oa] [-url SW360_URL] [-v] -i bomfile") print("") print("optional arguments:") - print(" -h, --help show this help message and exit") - print(" -t SW360_TOKEN, SW360_TOKEN") - print(" use this token for access to SW360") - print(" -oa, --oauth2 this is an oauth2 token") - print(" -url SW360_URL use this URL for access to SW360") - print(" -i INPUTFILE SBOM file to read from") - print(" -v be verbose") + print(" -h, --help show this help message and exit") + print(" -t SW360_TOKEN, SW360_TOKEN use this token for access to SW360") + print(" -oa, --oauth2 this is an oauth2 token") + print(" -url SW360_URL use this URL for access to SW360") + print(" -i INPUTFILE SBOM file to read from") + print(" -v be verbose") + print(" -client_id CLIENT_ID the SW360 client_id to be used for token generation") + print(" -client_secret CLIENT_SECRET the SW360 client_secret to be used for token generation") return if not args.inputfile: @@ -195,6 +196,17 @@ def run(self, args: Any) -> None: if self._bom_has_items_without_id(bom): print("There are SBOM items without Sw360 id - searching per name may take a little bit longer...") + if not args.sw360_token and args.client_id and args.client_secret: + print_text("Creating token using client id and secret...") + kc = SW360Keycloak(args.sw360_url) + args.sw360_token = kc.get_keycloak_token(args.client_id, args.client_secret, write_access=False) + if args.sw360_token: + args.oauth2 = True + print_text(" Got token.") + else: + print_red(" Failed to get token!") + sys.exit(ResultCode.RESULT_AUTH_ERROR) + if args.sw360_token and args.oauth2: self.analyze_token(args.sw360_token) diff --git a/capycli/bom/check_bom_item_status.py b/capycli/bom/check_bom_item_status.py index beade56b..8a312ef3 100644 --- a/capycli/bom/check_bom_item_status.py +++ b/capycli/bom/check_bom_item_status.py @@ -1,5 +1,5 @@ # ------------------------------------------------------------------------------- -# Copyright (c) 2019-2024 Siemens +# Copyright (c) 2019-2026 Siemens # All Rights Reserved. # Author: thomas.graf@siemens.com # @@ -15,12 +15,12 @@ from colorama import Fore, Style from cyclonedx.model.bom import Bom from cyclonedx.model.component import Component -from sw360 import SW360Error import capycli.common.script_base from capycli.common.capycli_bom_support import CaPyCliBom, CycloneDxSupport from capycli.common.print import print_red, print_text from capycli.main.result_codes import ResultCode +from sw360 import SW360Error, SW360Keycloak LOG = capycli.get_logger(__name__) @@ -183,9 +183,11 @@ def run(self, args: Any) -> None: print("usage: capycli bom CheckItemStatus [-h] [-all] -i bomfile") print("") print("optional arguments:") - print("-h, --help show this help message and exit") - print("-i INPUTFILE input file to read from") - print("-all show status of all versions of the component") + print("-h, --help show this help message and exit") + print("-i INPUTFILE input file to read from") + print("-all show status of all versions of the component") + print("-client_id CLIENT_ID the SW360 client_id to be used for token generation") + print("-client_secret CLIENT_SECRET the SW360 client_secret to be used for token generation") return if not args.inputfile: @@ -209,6 +211,17 @@ def run(self, args: Any) -> None: if self._bom_has_items_without_id(bom): print("There are SBOM items without Sw360 id - searching per name may take a little bit longer...") + if not args.sw360_token and args.client_id and args.client_secret: + print_text("Creating token using client id and secret...") + kc = SW360Keycloak(args.sw360_url) + args.sw360_token = kc.get_keycloak_token(args.client_id, args.client_secret, write_access=False) + if args.sw360_token: + args.oauth2 = True + print_text(" Got token.") + else: + print_red(" Failed to get token!") + sys.exit(ResultCode.RESULT_AUTH_ERROR) + if args.sw360_token and args.oauth2: self.analyze_token(args.sw360_token) diff --git a/capycli/bom/create_components.py b/capycli/bom/create_components.py index e6afb74d..6d76e77a 100644 --- a/capycli/bom/create_components.py +++ b/capycli/bom/create_components.py @@ -1,5 +1,5 @@ # ------------------------------------------------------------------------------- -# Copyright (c) 2019-2025 Siemens +# Copyright (c) 2019-2026 Siemens # All Rights Reserved. # Author: thomas.graf@siemens.com # @@ -20,7 +20,6 @@ from cyclonedx.model.bom import Bom from cyclonedx.model.component import Component from cyclonedx.model.license import DisjunctiveLicense, LicenseExpression -from sw360 import SW360Error import capycli.common.json_support import capycli.common.script_base @@ -30,6 +29,7 @@ from capycli.common.purl_utils import PurlUtils from capycli.common.script_support import ScriptSupport from capycli.main.result_codes import ResultCode +from sw360 import SW360Error, SW360Keycloak LOG = capycli.get_logger(__name__) @@ -43,17 +43,19 @@ class BomCreateComponents(capycli.common.script_base.ScriptBase): "usage: CaPyCLI bom {} -i bom.json -o bom_created.json [-source ]", "", "optional arguments:", - " -h, --help show this help message and exit", - " -i INPUTFILE, input file to read from (JSON)", - " -o OUTPUTFILE, output file to write to", - " -t SW360_TOKEN, use this token for access to SW360", - " -oa, --oauth2 this is an oauth2 token", - " -url SW360_URL use this URL for access to SW360", - " -o OUTPUT write updated BOM to a JSON file", - " -source SOURCE source folder or additional source file", - " --download enable automatic download of missing sources", - " --dbx relaxed Debian version handling: when checking for existing releases,", - " ignore prefixes like \"2:\" (epoch) and suffixes like \".debian\"", + " -h, --help show this help message and exit", + " -i INPUTFILE, input file to read from (JSON)", + " -o OUTPUTFILE, output file to write to", + " -t SW360_TOKEN, use this token for access to SW360", + " -oa, --oauth2 this is an oauth2 token", + " -url SW360_URL use this URL for access to SW360", + " -o OUTPUT write updated BOM to a JSON file", + " -source SOURCE source folder or additional source file", + " --download enable automatic download of missing sources", + " --dbx relaxed Debian version handling: when checking for existing releases,", + " ignore prefixes like \"2:\" (epoch) and suffixes like \".debian\"", + " -client_id CLIENT_ID the SW360 client_id to be used for token generation", + " -client_secret CLIENT_SECRET the SW360 client_secret to be used for token generation" ] def __init__(self, onlyCreateReleases: bool = False) -> None: @@ -771,6 +773,20 @@ def run(self, args: Any) -> None: print_text("Using relaxed debian version checks") self.relaxed_debian_parsing = True + if not args.sw360_token and args.client_id and args.client_secret: + print_text("Creating token using client id and secret...") + kc = SW360Keycloak(args.sw360_url) + args.sw360_token = kc.get_keycloak_token(args.client_id, args.client_secret, write_access=True) + if args.sw360_token: + args.oauth2 = True + print_text(" Got token.") + else: + print_red(" Failed to get token!") + sys.exit(ResultCode.RESULT_AUTH_ERROR) + + if args.sw360_token and args.oauth2: + self.analyze_token(args.sw360_token) + if not self.login(token=args.sw360_token, url=args.sw360_url, oauth2=args.oauth2): print_red("ERROR: login failed!") sys.exit(ResultCode.RESULT_AUTH_ERROR) diff --git a/capycli/bom/findsources.py b/capycli/bom/findsources.py index 267dc91e..ae653ec0 100644 --- a/capycli/bom/findsources.py +++ b/capycli/bom/findsources.py @@ -24,7 +24,6 @@ from cyclonedx.model import ExternalReferenceType, XsUri from cyclonedx.model.bom import Bom from cyclonedx.model.component import Component -from sw360 import SW360Error import capycli.common.script_base from capycli import get_logger @@ -32,6 +31,8 @@ from capycli.common.github_support import GitHubSupport from capycli.common.print import print_green, print_red, print_text, print_yellow from capycli.main.result_codes import ResultCode +from sw360 import SW360Error +from sw360.sw360keycloak import SW360Keycloak LOG = get_logger(__name__) @@ -744,15 +745,17 @@ def run(self, args: Any) -> None: print("usage: CaPyCli bom findsources [-h] [-v] [-o OUTPUTFILE] -i bomfile") print("") print("optional arguments:") - print(" -h, --help show this help message and exit") - print(" -i INPUTFILE SBOM file to read from (JSON)") - print(" -o OUTPUTFILE output file to write to") - print(" -t SW360_TOKEN (optional) use this token for access to SW360") - print(" -oa, --oauth2 (optional) this is an oauth2 token") - print(" -url SW360_URL (optional) use this URL for access to SW360") - print(" -name NAME (optional) GitHub name for login") - print(" -gt TOKEN (optional) GitHub token for login") - print(" -v be verbose") + print(" -h, --help show this help message and exit") + print(" -i INPUTFILE SBOM file to read from (JSON)") + print(" -o OUTPUTFILE output file to write to") + print(" -t SW360_TOKEN (opt.) use this token for access to SW360") + print(" -oa, --oauth2 (opt.) this is an oauth2 token") + print(" -url SW360_URL (opt.) use this URL for access to SW360") + print(" -name NAME (opt.) GitHub name for login") + print(" -gt TOKEN (opt.) GitHub token for login") + print(" -v be verbose") + print(" -client_id CLIENT_ID (opt.) the SW360 client_id to be used for token generation") + print(" -client_secret CLIENT_SECRET (opt.) the SW360 client_secret to be used for token generation") return if not args.inputfile: @@ -770,6 +773,17 @@ def run(self, args: Any) -> None: self.sw360_url = args.sw360_url if self.sw360_url: + if args.client_id and args.client_secret: + print_text("Creating token using client id and secret...") + kc = SW360Keycloak(args.sw360_url) + args.sw360_token = kc.get_keycloak_token(args.client_id, args.client_secret, write_access=False) + if args.sw360_token: + args.oauth2 = True + print_text(" Got token.") + else: + print_red(" Failed to get token!") + sys.exit(ResultCode.RESULT_AUTH_ERROR) + self.login( token=args.sw360_token, url=self.sw360_url, oauth2=args.oauth2) print("Using SW360 releases and components to detect GitHub url") diff --git a/capycli/bom/map_bom.py b/capycli/bom/map_bom.py index 5fbaef3e..54ef136f 100644 --- a/capycli/bom/map_bom.py +++ b/capycli/bom/map_bom.py @@ -21,7 +21,6 @@ from cyclonedx.model.bom import Bom from cyclonedx.model.component import Component from packageurl import PackageURL -from sw360 import SW360 import capycli.common.file_support import capycli.common.script_base @@ -35,6 +34,7 @@ from capycli.common.purl_service import PurlService from capycli.common.purl_utils import PurlUtils from capycli.main.result_codes import ResultCode +from sw360 import SW360, SW360Keycloak LOG = get_logger(__name__) @@ -865,6 +865,10 @@ def show_help(self) -> None: print(" version 3.1 will match SW360 version 3.1-3.debian") print(" -all deprecated, please use --matchmode all-versions") print(" --dbx deprecated, please use --matchmode ignore-debian") + print(" -client_id CLIENT_ID ") + print(" the SW360 client_id to be used for token generation") + print(" -client_secret CLIENT_SECRET") + print(" the SW360 client_secret to be used for token generation") def run(self, args: Any) -> None: """Main method()""" @@ -928,6 +932,17 @@ def run(self, args: Any) -> None: if self.verbosity > 1: print_text(" ", self.get_comp_count_text(sbom), "read from SBOM") + if not args.sw360_token and args.client_id and args.client_secret: + print_text("Creating token using client id and secret...") + kc = SW360Keycloak(args.sw360_url) + args.sw360_token = kc.get_keycloak_token(args.client_id, args.client_secret, write_access=False) + if args.sw360_token: + args.oauth2 = True + print_text(" Got token.") + else: + print_red(" Failed to get token!") + sys.exit(ResultCode.RESULT_AUTH_ERROR) + if args.sw360_token and args.oauth2: self.analyze_token(args.sw360_token) diff --git a/capycli/common/component_cache.py b/capycli/common/component_cache.py index 780e7ffe..883ad12a 100644 --- a/capycli/common/component_cache.py +++ b/capycli/common/component_cache.py @@ -12,7 +12,6 @@ from typing import Any, Dict, List, Optional import sw360 - from capycli import get_logger from capycli.common.print import print_red, print_text, print_yellow from capycli.common.script_support import ScriptSupport diff --git a/capycli/common/purl_service.py b/capycli/common/purl_service.py index b1415545..a2b79510 100644 --- a/capycli/common/purl_service.py +++ b/capycli/common/purl_service.py @@ -9,11 +9,11 @@ from typing import Any, Dict, List, Optional import packageurl -from sw360 import SW360 from capycli.common.print import print_green, print_text, print_yellow from capycli.common.purl_store import PurlStore from capycli.common.purl_utils import PurlUtils +from sw360 import SW360 class PurlService: diff --git a/capycli/common/script_base.py b/capycli/common/script_base.py index 0dc35b72..ebd3d89e 100644 --- a/capycli/common/script_base.py +++ b/capycli/common/script_base.py @@ -1,5 +1,5 @@ # ------------------------------------------------------------------------------- -# Copyright (c) 2019-2024 Siemens +# Copyright (c) 2019-2026 Siemens # All Rights Reserved. # Author: thomas.graf@siemens.com # @@ -19,10 +19,10 @@ import jwt import requests from cyclonedx.model.bom import Bom -from sw360 import SW360, SW360Error from capycli.common.print import print_red, print_text, print_yellow from capycli.main.result_codes import ResultCode +from sw360 import SW360, SW360Error class ScriptBase: @@ -78,6 +78,13 @@ def analyze_token(self, token: str) -> None: try: # alg = RS256 decoded = jwt.decode(token, algorithms=["HS256"], options={"verify_signature": False}) # type: ignore + if "scope" in decoded: + scope = decoded["scope"] + if scope.lower().find("write") >= 0: + print_text(" Token has write permissions") + else: + print_text(" Token has read permissions") + if "exp" in decoded: exp_seconds = int(decoded["exp"]) exp = datetime.fromtimestamp(exp_seconds) @@ -85,12 +92,23 @@ def analyze_token(self, token: str) -> None: # print(decoded) # { - # 'aud': ['sw360-REST-API'], - # 'user_name': 'thomas.graf@siemens.com', - # 'scope': ['READ', 'WRITE'], - # 'exp': 1581754268, - # 'authorities': ['READ', 'WRITE'], - # 'jti': 'bddc8951-bfae-475d-b2fd-04059b86598e', + # 'exp': 1776699510, + # 'iat': 1702769910, + # 'jti': 'trrtcc:6f1d3934-b319-1183-a059-8b7606f0a647', + # 'iss': 'https://stage.sw360.siemens.com/kc/realms/sw360', + # 'aud': 'account', + # 'sub': 'cf3fb608-4dba-42e0-bb89-7e13f995b931', + # 'typ': 'Bearer', + # 'azp': '7f75885d309970833f4187295d9babb8', + # 'acr': '1', + # 'realm_access': {'roles': ['default-roles-sw360', 'offline_access', 'uma_authorization']}, + # 'resource_access': {'account': {'roles': ['manage-account', 'manage-account-links', 'view-profile']}}, + # 'scope': ''READ profile email'', + # 'clientHost': '139.21.146.160' + # 'email_verified': False, + # 'preferred_username': 'service-account-7f75885d309970833f4187295d9babb8', + # 'clientAddress': '139.21.146.160', + # 'email': 'thomas.graf@siemens.com', # 'client_id': 'xxx' # } except Exception as ex: diff --git a/capycli/main/options.py b/capycli/main/options.py index 38eda844..c27cffbd 100644 --- a/capycli/main/options.py +++ b/capycli/main/options.py @@ -444,6 +444,18 @@ def register_options(self) -> None: help="copy the project with the given id and the update it", ) + # used by all commands that need to access SW360 + self.parser.add_argument( + "-client_id", + dest="client_id", + help="the SW360 client_id to be used for token generation") + + # used by all commands that need to access SW360 + self.parser.add_argument( + "-client_secret", + dest="client_secret", + help="the SW360 client_secret to be used for token generation") + def read_config(self, filename: str = "", config_string: str = "") -> Dict[str, Any]: """ Read configuration from string or config file. diff --git a/capycli/project/check_prerequisites.py b/capycli/project/check_prerequisites.py index a6c37e9c..500c81d3 100644 --- a/capycli/project/check_prerequisites.py +++ b/capycli/project/check_prerequisites.py @@ -1,5 +1,5 @@ # ------------------------------------------------------------------------------- -# Copyright (c) 2019-2025 Siemens +# Copyright (c) 2019-2026 Siemens # All Rights Reserved. # Author: thomas.graf@siemens.com # @@ -19,6 +19,7 @@ from capycli.common.capycli_bom_support import CaPyCliBom, CycloneDxSupport from capycli.common.print import print_green, print_red, print_text, print_yellow from capycli.main.result_codes import ResultCode +from sw360 import SW360Keycloak LOG = get_logger(__name__) @@ -301,17 +302,33 @@ def run(self, args: Any) -> None: print("Checks whether all prerequisites for a successful software clearing are fulfilled") print("") print("Options:") - print(" -h, --help show this help message and exit") - print(" -n NAME, --name NAME name of the project") - print(" -v VERSION, version of the project") - print(" -id PROJECT_ID SW360 id of the project, supersedes name and version parameters") - print(" -i INPUTFILE SBOM input file to read from (JSON)") - print(" -t SW360_TOKEN, use this token for access to SW360") - print(" -oa, --oauth2 this is an oauth2 token") - print(" -url SW360_URL use this URL for access to SW360") - print(" --forceerror force an error exit code in case of prerequisite errors") + print(" -h, --help show this help message and exit") + print(" -n NAME, --name NAME name of the project") + print(" -v VERSION, version of the project") + print(" -id PROJECT_ID SW360 id of the project, supersedes name and version parameters") + print(" -i INPUTFILE SBOM input file to read from (JSON)") + print(" -t SW360_TOKEN, use this token for access to SW360") + print(" -oa, --oauth2 this is an oauth2 token") + print(" -url SW360_URL use this URL for access to SW360") + print(" --forceerror force an error exit code in case of prerequisite errors") + print(" -client_id CLIENT_ID the SW360 client_id to be used for token generation") + print(" -client_secret CLIENT_SECRET the SW360 client_secret to be used for token generation") return + if not args.sw360_token and args.client_id and args.client_secret: + print_text("Creating token using client id and secret...") + kc = SW360Keycloak(args.sw360_url) + args.sw360_token = kc.get_keycloak_token(args.client_id, args.client_secret, write_access=False) + if args.sw360_token: + args.oauth2 = True + print_text(" Got token.") + else: + print_red(" Failed to get token!") + sys.exit(ResultCode.RESULT_AUTH_ERROR) + + if args.sw360_token and args.oauth2: + self.analyze_token(args.sw360_token) + if not self.login(token=args.sw360_token, url=args.sw360_url, oauth2=args.oauth2): print_red("ERROR: login failed!") sys.exit(ResultCode.RESULT_AUTH_ERROR) diff --git a/capycli/project/create_bom.py b/capycli/project/create_bom.py index 40491e93..e3da3413 100644 --- a/capycli/project/create_bom.py +++ b/capycli/project/create_bom.py @@ -1,5 +1,5 @@ # ------------------------------------------------------------------------------- -# Copyright (c) 2021-2024 Siemens +# Copyright (c) 2021-2026 Siemens # All Rights Reserved. # Author: thomas.graf@siemens.com # @@ -14,7 +14,6 @@ from cyclonedx.model.bom import Bom from cyclonedx.model.component import Component from packageurl import PackageURL -from sw360 import SW360Error import capycli.common.script_base from capycli import get_logger @@ -22,6 +21,7 @@ from capycli.common.print import print_red, print_text, print_yellow from capycli.common.purl_utils import PurlUtils from capycli.main.result_codes import ResultCode +from sw360 import SW360Error, SW360Keycloak LOG = get_logger(__name__) @@ -159,13 +159,15 @@ def show_command_help(self) -> None: print("\nusage: CaPyCli project createbom [options]") print("Options:") print(""" - -id ID SW360 id of the project - -t SW360_TOKEN use this token for access to SW360 - -oa, this is an oauth2 token - -url SW360_URL use this URL for access to SW360 - -name name of the project, component or release - -version version of the project, component or release - -o OUTPUTFILE output file to write to + -id ID SW360 id of the project + -t SW360_TOKEN use this token for access to SW360 + -oa, this is an oauth2 token + -url SW360_URL use this URL for access to SW360 + -name name of the project, component or release + -version version of the project, component or release + -o OUTPUTFILE output file to write to + -client_id CLIENT_ID the SW360 client_id to be used for token generation + -client_secret CLIENT_SECRET the SW360 client_secret to be used for token generation """) print() @@ -189,6 +191,20 @@ def run(self, args: Any) -> None: self.show_command_help() return + if not args.sw360_token and args.client_id and args.client_secret: + print_text("Creating token using client id and secret...") + kc = SW360Keycloak(args.sw360_url) + args.sw360_token = kc.get_keycloak_token(args.client_id, args.client_secret, write_access=False) + if args.sw360_token: + args.oauth2 = True + print_text(" Got token.") + else: + print_red(" Failed to get token!") + sys.exit(ResultCode.RESULT_AUTH_ERROR) + + if args.sw360_token and args.oauth2: + self.analyze_token(args.sw360_token) + if not self.login(token=args.sw360_token, url=args.sw360_url, oauth2=args.oauth2): print_red("ERROR: login failed!") sys.exit(ResultCode.RESULT_AUTH_ERROR) diff --git a/capycli/project/create_project.py b/capycli/project/create_project.py index 61d45ce6..1e15a196 100644 --- a/capycli/project/create_project.py +++ b/capycli/project/create_project.py @@ -1,5 +1,5 @@ # ------------------------------------------------------------------------------- -# Copyright (c) 2019-2025 Siemens +# Copyright (c) 2019-2026 Siemens # All Rights Reserved. # Author: thomas.graf@siemens.com # @@ -13,13 +13,13 @@ import requests from cyclonedx.model.bom import Bom -from sw360 import SW360Error import capycli.common.script_base from capycli import get_logger from capycli.common.capycli_bom_support import CaPyCliBom, CycloneDxSupport from capycli.common.print import print_red, print_text, print_yellow from capycli.main.result_codes import ResultCode +from sw360 import SW360Error, SW360Keycloak LOG = get_logger(__name__) @@ -322,17 +322,19 @@ def run(self, args: Any) -> None: print("usage: CaPyCli project create -i bom.json -o bom_created.json [-source ]") print("") print("optional arguments:") - print(" -i INPUTFILE, bom file to read from (JSON)") - print(" -t SW360_TOKEN, use this token for access to SW360") - print(" -oa, --oauth2 this is an oauth2 token") - print(" -url SW360_URL use this URL for access to SW360") - print(" -name NAME name of the project") - print(" -version VERSION, version of the project") - print(" -id PROJECT_ID SW360 id of the project, supersedes name and version parameters") - print(" -old-version previous version") - print(" -source projectinfo.json additional information about the project to be created") - print(" -pms project mainline state for releases in a newly created project") - print(" --copy_from PROJECT_ID copy the project with the given id and the update it") + print(" -i INPUTFILE, bom file to read from (JSON)") + print(" -t SW360_TOKEN, use this token for access to SW360") + print(" -oa, --oauth2 this is an oauth2 token") + print(" -url SW360_URL use this URL for access to SW360") + print(" -name NAME name of the project") + print(" -version VERSION, version of the project") + print(" -id PROJECT_ID SW360 id of the project, supersedes name and version parameters") + print(" -old-version previous version") + print(" -source projectinfo.json additional information about the project to be created") + print(" -pms project mainline state for releases in a newly created project") + print(" --copy_from PROJECT_ID copy the project with the given id and the update it") + print(" -client_id CLIENT_ID the SW360 client_id to be used for token generation") + print(" -client_secret CLIENT_SECRET the SW360 client_secret to be used for token generation") return if not args.inputfile: @@ -371,6 +373,20 @@ def run(self, args: Any) -> None: print_text("Project version will be updated with version: " + args.old_version) is_update_version = True + if not args.sw360_token and args.client_id and args.client_secret: + print_text("Creating token using client id and secret...") + kc = SW360Keycloak(args.sw360_url) + args.sw360_token = kc.get_keycloak_token(args.client_id, args.client_secret, write_access=True) + if args.sw360_token: + args.oauth2 = True + print_text(" Got token.") + else: + print_red(" Failed to get token!") + sys.exit(ResultCode.RESULT_AUTH_ERROR) + + if args.sw360_token and args.oauth2: + self.analyze_token(args.sw360_token) + if not self.login(token=args.sw360_token, url=args.sw360_url, oauth2=args.oauth2): print_red("ERROR: login failed!") sys.exit(ResultCode.RESULT_AUTH_ERROR) diff --git a/capycli/project/find_project.py b/capycli/project/find_project.py index 23aab8c9..4ca5430b 100644 --- a/capycli/project/find_project.py +++ b/capycli/project/find_project.py @@ -1,5 +1,5 @@ # ------------------------------------------------------------------------------- -# Copyright (c) 2019-23 Siemens +# Copyright (c) 2019-2026 Siemens # All Rights Reserved. # Author: thomas.graf@siemens.com # @@ -12,11 +12,11 @@ from typing import Any, Dict, Optional import requests -import sw360 import capycli.common.script_base from capycli.common.print import print_red, print_text, print_yellow from capycli.main.result_codes import ResultCode +from sw360 import SW360Error, SW360Keycloak LOG = capycli.get_logger(__name__) @@ -82,7 +82,7 @@ def check_project_id(self, project_id: str) -> None: print_text( "Project found, name = " + project["name"] + ", version = " + project["version"]) - except sw360.SW360Error as swex: + except SW360Error as swex: if swex.response is None: print_red("Unknown error: " + swex.message) elif swex.response.status_code == requests.codes['not_found']: @@ -114,12 +114,28 @@ def run(self, args: Any) -> None: print("usage: CaPyCli project find [-h] -t TOKEN -name NAME -version VERSION [-id PROJECT_ID]") print("") print("optional arguments:") - print(" -h, --help show this help message and exit") - print(" -name NAME name of the project") - print(" -version VERSION version of the project") - print(" -id PROJECT_ID SW360 id of the project, supersedes name and version parameters") + print(" -h, --help show this help message and exit") + print(" -name NAME name of the project") + print(" -version VERSION version of the project") + print(" -id PROJECT_ID SW360 id of the project, supersedes name and version parameters") + print(" -client_id CLIENT_ID the SW360 client_id to be used for token generation") + print(" -client_secret CLIENT_SECRET the SW360 client_secret to be used for token generation") return + if not args.sw360_token and args.client_id and args.client_secret: + print_text("Creating token using client id and secret...") + kc = SW360Keycloak(args.sw360_url) + args.sw360_token = kc.get_keycloak_token(args.client_id, args.client_secret, write_access=False) + if args.sw360_token: + args.oauth2 = True + print_text(" Got token.") + else: + print_red(" Failed to get token!") + sys.exit(ResultCode.RESULT_AUTH_ERROR) + + if args.sw360_token and args.oauth2: + self.analyze_token(args.sw360_token) + if not self.login(token=args.sw360_token, url=args.sw360_url, oauth2=args.oauth2): print_red("ERROR: login failed!") sys.exit(ResultCode.RESULT_AUTH_ERROR) diff --git a/capycli/project/get_license_info.py b/capycli/project/get_license_info.py index 2b1ccfa4..a5ab0891 100644 --- a/capycli/project/get_license_info.py +++ b/capycli/project/get_license_info.py @@ -1,5 +1,5 @@ # ------------------------------------------------------------------------------- -# Copyright (c) 2019-2024 Siemens +# Copyright (c) 2019-2026 Siemens # All Rights Reserved. # Author: thomas.graf@siemens.com # @@ -12,13 +12,12 @@ import sys from typing import Any, Dict, List -from sw360 import SW360Error - import capycli.common.script_base from capycli.common.json_support import load_json_file from capycli.common.print import print_red, print_text, print_yellow from capycli.common.script_support import ScriptSupport from capycli.main.result_codes import ResultCode +from sw360 import SW360Error, SW360Keycloak LOG = capycli.get_logger(__name__) @@ -197,6 +196,8 @@ def show_command_help(self) -> None: -nconf, --no-overwrite-config do not overwrite an existing configuration file -all add all available CLI files of a component --forceerror force an error exit code in case of missing information + -client_id CLIENT_ID the SW360 client_id to be used for token generation + -client_secret CLIENT_SECRET the SW360 client_secret to be used for token generation """) print() @@ -239,6 +240,20 @@ def run(self, args: Any) -> None: print_red("Input file not found!") sys.exit(ResultCode.RESULT_FILE_NOT_FOUND) + if not args.sw360_token and args.client_id and args.client_secret: + print_text("Creating token using client id and secret...") + kc = SW360Keycloak(args.sw360_url) + args.sw360_token = kc.get_keycloak_token(args.client_id, args.client_secret, write_access=False) + if args.sw360_token: + args.oauth2 = True + print_text(" Got token.") + else: + print_red(" Failed to get token!") + sys.exit(ResultCode.RESULT_AUTH_ERROR) + + if args.sw360_token and args.oauth2: + self.analyze_token(args.sw360_token) + if not self.login(token=args.sw360_token, url=args.sw360_url, oauth2=args.oauth2): print_red("ERROR: login failed!") sys.exit(ResultCode.RESULT_AUTH_ERROR) diff --git a/capycli/project/project_component_check.py b/capycli/project/project_component_check.py index fceb3e6a..88441a70 100644 --- a/capycli/project/project_component_check.py +++ b/capycli/project/project_component_check.py @@ -10,12 +10,11 @@ import sys from typing import Any -import sw360 - import capycli.common.script_base from capycli.bom.component_check import ComponentCheck from capycli.common.print import print_red, print_text, print_yellow from capycli.main.result_codes import ResultCode +from sw360 import SW360Error, SW360Keycloak LOG = capycli.get_logger(__name__) @@ -71,7 +70,7 @@ def check_bom_items(self, project_id: str) -> int: try: self.project = self.client.get_project(project_id) - except sw360.SW360Error as swex: + except SW360Error as swex: print_red(" ERROR: unable to access project: " + repr(swex)) sys.exit(ResultCode.RESULT_ERROR_ACCESSING_SW360) @@ -120,17 +119,19 @@ def run(self, args: Any) -> None: "[-v] [-id PROJECT_ID] [-rcl URL] [-lcl FILE]") print_text("") print_text("optional arguments:") - print_text(" -h, --help show this help message and exit") - print_text(" -name NAME name of the project") - print_text(" -version VERSION version of the project") - print_text(" -id PROJECT_ID SW360 id of the project, supersedes name and version parameters") - print_text(" -t SW360_TOKEN use this token for access to SW360") - print_text(" -oa, this is an oauth2 token") - print_text(" -url SW360_URL use this URL for access to SW360") - print_text(" -v be verbose") - print_text(" -rcl read the component check list file from the URL specified") - print_text(" -lcl read the component check list file from local") - print_text(" --forceerror force an error exit code in case of validation errors or warnings") + print_text(" -h, --help show this help message and exit") + print_text(" -name NAME name of the project") + print_text(" -version VERSION version of the project") + print_text(" -id PROJECT_ID SW360 id of the project, supersedes name and version parameters") + print_text(" -t SW360_TOKEN use this token for access to SW360") + print_text(" -oa, this is an oauth2 token") + print_text(" -url SW360_URL use this URL for access to SW360") + print_text(" -v be verbose") + print_text(" -rcl read the component check list file from the URL specified") + print_text(" -lcl read the component check list file from local") + print_text(" --forceerror force an error exit code in case of validation errors or warnings") + print_text(" -client_id CLIENT_ID the SW360 client_id to be used for token generation") + print_text(" -client_secret CLIENT_SECRET the SW360 client_secret to be used for token generation") return self.verbose = args.verbose @@ -148,6 +149,20 @@ def run(self, args: Any) -> None: self.component_check.files_to_ignore = self.component_check.component_check_list.get("files_to_ignore", []) print_text(f" {len(self.component_check.files_to_ignore)} components will be ignored.") + if not args.sw360_token and args.client_id and args.client_secret: + print_text("Creating token using client id and secret...") + kc = SW360Keycloak(args.sw360_url) + args.sw360_token = kc.get_keycloak_token(args.client_id, args.client_secret, write_access=False) + if args.sw360_token: + args.oauth2 = True + print_text(" Got token.") + else: + print_red(" Failed to get token!") + sys.exit(ResultCode.RESULT_AUTH_ERROR) + + if args.sw360_token and args.oauth2: + self.analyze_token(args.sw360_token) + if not self.login(token=args.sw360_token, url=args.sw360_url, oauth2=args.oauth2): print_red("ERROR: login failed!") sys.exit(ResultCode.RESULT_AUTH_ERROR) diff --git a/capycli/project/show_ecc.py b/capycli/project/show_ecc.py index a45d996d..71d826c1 100644 --- a/capycli/project/show_ecc.py +++ b/capycli/project/show_ecc.py @@ -1,5 +1,5 @@ # ------------------------------------------------------------------------------- -# Copyright (c) 2022-2024 Siemens +# Copyright (c) 2022-2026 Siemens # All Rights Reserved. # Author: thomas.graf@siemens.com # @@ -10,12 +10,11 @@ import sys from typing import Any, Dict -import sw360 - import capycli.common.script_base from capycli.common.json_support import write_json_to_file from capycli.common.print import print_green, print_red, print_text, print_yellow from capycli.main.result_codes import ResultCode +from sw360 import SW360Error, SW360Keycloak LOG = capycli.get_logger(__name__) @@ -82,7 +81,7 @@ def get_project_status(self, project_id: str) -> Dict[str, Any]: try: self.project = self.client.get_project(project_id) - except sw360.SW360Error as swex: + except SW360Error as swex: print_red(" ERROR: unable to access project: " + repr(swex)) sys.exit(ResultCode.RESULT_ERROR_ACCESSING_SW360) @@ -133,7 +132,7 @@ def get_project_status(self, project_id: str) -> Dict[str, Any]: rel_item["EccStatus"] = eccinfo.get("eccStatus", "UNKNOWN") rel_item["AL"] = eccinfo.get("al", "UNKNOWN") rel_item["ECCN"] = eccinfo.get("eccn", "UNKNOWN") - except sw360.SW360Error as swex: + except SW360Error as swex: print_red(" ERROR: unable to access project:" + repr(swex)) sys.exit(ResultCode.RESULT_ERROR_ACCESSING_SW360) @@ -171,13 +170,29 @@ def run(self, args: Any) -> None: print("usage: CaPyCli project ecc [-h] -name NAME -version VERSION [-id PROJECT_ID] [-o OUTPUTFILE]") print("") print("optional arguments:") - print(" -h, --help show this help message and exit") - print(" -n NAME, --name NAME name of the project") - print(" -v VERSION, version of the project") - print(" -id PROJECT_ID SW360 id of the project, supersedes name and version parameters") - print(" -o OUTPUTFILE output file to write project details to") + print(" -h, --help show this help message and exit") + print(" -n NAME, --name NAME name of the project") + print(" -v VERSION, version of the project") + print(" -id PROJECT_ID SW360 id of the project, supersedes name and version parameters") + print(" -o OUTPUTFILE output file to write project details to") + print(" -client_id CLIENT_ID the SW360 client_id to be used for token generation") + print(" -client_secret CLIENT_SECRET the SW360 client_secret to be used for token generation") return + if not args.sw360_token and args.client_id and args.client_secret: + print_text("Creating token using client id and secret...") + kc = SW360Keycloak(args.sw360_url) + args.sw360_token = kc.get_keycloak_token(args.client_id, args.client_secret, write_access=False) + if args.sw360_token: + args.oauth2 = True + print_text(" Got token.") + else: + print_red(" Failed to get token!") + sys.exit(ResultCode.RESULT_AUTH_ERROR) + + if args.sw360_token and args.oauth2: + self.analyze_token(args.sw360_token) + if not self.login(token=args.sw360_token, url=args.sw360_url, oauth2=args.oauth2): print_red("ERROR: login failed!") sys.exit(ResultCode.RESULT_AUTH_ERROR) diff --git a/capycli/project/show_licenses.py b/capycli/project/show_licenses.py index 6d146f38..e00f68db 100644 --- a/capycli/project/show_licenses.py +++ b/capycli/project/show_licenses.py @@ -1,5 +1,5 @@ # ------------------------------------------------------------------------------- -# Copyright (c) 2019-24 Siemens +# Copyright (c) 2019-2026 Siemens # All Rights Reserved. # Author: thomas.graf@siemens.com # @@ -19,6 +19,7 @@ import capycli.common.script_base from capycli.common.print import print_red, print_text, print_yellow from capycli.main.result_codes import ResultCode +from sw360 import SW360Keycloak LOG = capycli.get_logger(__name__) @@ -176,12 +177,14 @@ def show_command_help(self) -> None: print("\nusage: CaPyCli project licenses [options]") print("Options:") print(""" - -id ID SW360 id of the project - -t SW360_TOKEN use this token for access to SW360 - -oa, this is an oauth2 token - -url SW360_URL use this URL for access to SW360 - -name name of the project, component or release - -version version of the project, component or release + -id ID SW360 id of the project + -t SW360_TOKEN use this token for access to SW360 + -oa, this is an oauth2 token + -url SW360_URL use this URL for access to SW360 + -name name of the project, component or release + -version version of the project, component or release + -client_id CLIENT_ID the SW360 client_id to be used for token generation + -client_secret CLIENT_SECRET the SW360 client_secret to be used for token generation """) print() @@ -205,6 +208,20 @@ def run(self, args: Any) -> None: self.show_command_help() return + if not args.sw360_token and args.client_id and args.client_secret: + print_text("Creating token using client id and secret...") + kc = SW360Keycloak(args.sw360_url) + args.sw360_token = kc.get_keycloak_token(args.client_id, args.client_secret, write_access=False) + if args.sw360_token: + args.oauth2 = True + print_text(" Got token.") + else: + print_red(" Failed to get token!") + sys.exit(ResultCode.RESULT_AUTH_ERROR) + + if args.sw360_token and args.oauth2: + self.analyze_token(args.sw360_token) + if not self.login(token=args.sw360_token, url=args.sw360_url, oauth2=args.oauth2): print_red("ERROR: login failed!") sys.exit(ResultCode.RESULT_AUTH_ERROR) diff --git a/capycli/project/show_project.py b/capycli/project/show_project.py index 5ad0d1d5..8b212094 100644 --- a/capycli/project/show_project.py +++ b/capycli/project/show_project.py @@ -1,5 +1,5 @@ # ------------------------------------------------------------------------------- -# Copyright (c) 2019-24 Siemens +# Copyright (c) 2019-2026 Siemens # All Rights Reserved. # Author: thomas.graf@siemens.com # @@ -10,13 +10,13 @@ import sys from typing import Any, Dict, Optional -import sw360 from colorama import Fore import capycli.common.json_support import capycli.common.script_base from capycli.common.print import print_red, print_text, print_yellow from capycli.main.result_codes import ResultCode +from sw360 import SW360Error, SW360Keycloak LOG = capycli.get_logger(__name__) @@ -87,7 +87,7 @@ def get_project_status(self, project_id: str) -> Dict[str, Any]: try: self.project = self.client.get_project(project_id) - except sw360.SW360Error as swex: + except SW360Error as swex: print_red(" ERROR: unable to access project: " + repr(swex)) sys.exit(ResultCode.RESULT_ERROR_ACCESSING_SW360) @@ -149,7 +149,7 @@ def get_project_status(self, project_id: str) -> Dict[str, Any]: for key in att: if key.get("attachmentType", "") == "SOURCE": rel_item["SourceAvailable"] = "True" - except sw360.SW360Error as swex: + except SW360Error as swex: print_red(" ERROR: unable to access project:" + repr(swex)) sys.exit(ResultCode.RESULT_ERROR_ACCESSING_SW360) @@ -188,16 +188,32 @@ def run(self, args: Any) -> None: "[-id PROJECT_ID] [-o OUTPUTFILE]") print("") print("optional arguments:") - print(" -h, --help show this help message and exit") - print(" -name NAME name of the project") - print(" -version VERSION version of the project") - print(" -id PROJECT_ID SW360 id of the project, supersedes name and version parameters") - print(" -t SW360_TOKEN use this token for access to SW360") - print(" -oa, this is an oauth2 token") - print(" -url SW360_URL use this URL for access to SW360") - print(" -o OUTPUTFILE output file to write project details to") + print(" -h, --help show this help message and exit") + print(" -name NAME name of the project") + print(" -version VERSION version of the project") + print(" -id PROJECT_ID SW360 id of the project, supersedes name and version parameters") + print(" -t SW360_TOKEN use this token for access to SW360") + print(" -oa, this is an oauth2 token") + print(" -url SW360_URL use this URL for access to SW360") + print(" -o OUTPUTFILE output file to write project details to") + print(" -client_id CLIENT_ID the SW360 client_id to be used for token generation") + print(" -client_secret CLIENT_SECRET the SW360 client_secret to be used for token generation") return + if not args.sw360_token and args.client_id and args.client_secret: + print_text("Creating token using client id and secret...") + kc = SW360Keycloak(args.sw360_url) + args.sw360_token = kc.get_keycloak_token(args.client_id, args.client_secret, write_access=False) + if args.sw360_token: + args.oauth2 = True + print_text(" Got token.") + else: + print_red(" Failed to get token!") + sys.exit(ResultCode.RESULT_AUTH_ERROR) + + if args.sw360_token and args.oauth2: + self.analyze_token(args.sw360_token) + if not self.login(token=args.sw360_token, url=args.sw360_url, oauth2=args.oauth2): print_red("ERROR: login failed!") sys.exit(ResultCode.RESULT_AUTH_ERROR) diff --git a/capycli/project/show_vulnerabilities.py b/capycli/project/show_vulnerabilities.py index 65125731..00e7c55f 100644 --- a/capycli/project/show_vulnerabilities.py +++ b/capycli/project/show_vulnerabilities.py @@ -1,5 +1,5 @@ # ------------------------------------------------------------------------------- -# Copyright (c) 2020-2024 Siemens +# Copyright (c) 2020-2026 Siemens # All Rights Reserved. # Author: thomas.graf@siemens.com # @@ -12,12 +12,12 @@ import requests from colorama import Fore, Style -from sw360 import SW360Error import capycli.common.json_support import capycli.common.script_base from capycli.common.print import print_green, print_red, print_text, print_yellow from capycli.main.result_codes import ResultCode +from sw360 import SW360Error, SW360Keycloak LOG = capycli.get_logger(__name__) @@ -206,16 +206,18 @@ def show_command_help(self) -> None: print("\nusage: CaPyCli project vulnerabilities [options]") print("Options:") print(""" - -id ID SW360 id of the project - -t SW360_TOKEN use this token for access to SW360 - -oa, this is an oauth2 token - -url SW360_URL use this URL for access to SW360 - -name name of the project, component or release - -version version of the project, component or release - -v be verbose - -format FMT output format, one of [text, json], default is text - -fe PRIO minimum vulnerability priority to force exit code != 0 - """) + -id ID SW360 id of the project + -t SW360_TOKEN use this token for access to SW360 + -oa, this is an oauth2 token + -url SW360_URL use this URL for access to SW360 + -name name of the project, component or release + -version version of the project, component or release + -v be verbose + -format FMT output format, one of [text, json], default is text + -fe PRIO minimum vulnerability priority to force exit code != 0 + -client_id CLIENT_ID the SW360 client_id to be used for token generation + -client_secret CLIENT_SECRET the SW360 client_secret to be used for token generation + """) print() @@ -245,6 +247,20 @@ def run(self, args: Any) -> None: if args.verbose: print("Output format is", self.format) + if not args.sw360_token and args.client_id and args.client_secret: + print_text("Creating token using client id and secret...") + kc = SW360Keycloak(args.sw360_url) + args.sw360_token = kc.get_keycloak_token(args.client_id, args.client_secret, write_access=False) + if args.sw360_token: + args.oauth2 = True + print_text(" Got token.") + else: + print_red(" Failed to get token!") + sys.exit(ResultCode.RESULT_AUTH_ERROR) + + if args.sw360_token and args.oauth2: + self.analyze_token(args.sw360_token) + if not self.login(token=args.sw360_token, url=args.sw360_url, oauth2=args.oauth2): print_red("ERROR: login failed!") sys.exit(ResultCode.RESULT_AUTH_ERROR) diff --git a/poetry.lock b/poetry.lock index 6e1654f0..a17ea750 100644 --- a/poetry.lock +++ b/poetry.lock @@ -978,21 +978,18 @@ setuptools = ">=42.0.0" [[package]] name = "pyjwt" -version = "2.12.1" +version = "2.13.0" description = "JSON Web Token implementation in Python" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "pyjwt-2.12.1-py3-none-any.whl", hash = "sha256:28ca37c070cad8ba8cd9790cd940535d40274d22f80ab87f3ac6a713e6e8454c"}, - {file = "pyjwt-2.12.1.tar.gz", hash = "sha256:c74a7a2adf861c04d002db713dd85f84beb242228e671280bf709d765b03672b"}, + {file = "pyjwt-2.13.0-py3-none-any.whl", hash = "sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728"}, + {file = "pyjwt-2.13.0.tar.gz", hash = "sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423"}, ] [package.extras] crypto = ["cryptography (>=3.4.0)"] -dev = ["coverage[toml] (==7.10.7)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=8.4.2,<9.0.0)", "sphinx", "sphinx-rtd-theme", "zope.interface"] -docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"] -tests = ["coverage[toml] (==7.10.7)", "pytest (>=8.4.2,<9.0.0)"] [[package]] name = "pytest" @@ -1545,17 +1542,18 @@ files = [ [[package]] name = "sw360" -version = "1.11.1" +version = "1.12.0.dev1" description = "Python interface to the SW360 software component catalogue" optional = false python-versions = "<4.0,>=3.11" groups = ["main"] files = [ - {file = "sw360-1.11.1-py3-none-any.whl", hash = "sha256:7321459349f273ac70ce90b7bd89d176149133202f300a6bc996cc45ba69122e"}, - {file = "sw360-1.11.1.tar.gz", hash = "sha256:2cb67812ef02a83b53646ae92de15858381b31b7d00bebe0edc5bf84bd8670e2"}, + {file = "sw360-1.12.0.dev1-py3-none-any.whl", hash = "sha256:351764e55734c9db12604428117460da6a1805fdc52101b5b49011e4563e89b6"}, + {file = "sw360-1.12.0.dev1.tar.gz", hash = "sha256:a2441ba5e1bcd94a5bba188fae92259767866f6dbe1cb2f0e421cae2be3e8d6f"}, ] [package.dependencies] +pyjwt = ">=2.13.0,<3.0.0" requests = ">=2.33,<3.0" [[package]] @@ -1787,4 +1785,4 @@ test = ["pytest (>=3.0.0)"] [metadata] lock-version = "2.1" python-versions = ">=3.11,<3.15" -content-hash = "c03c6b02d812176181a43a48f76aa7f2dc8e4fa606ec9f5b17e0bdafed0c7ea3" +content-hash = "a00a733565eb1dcea643a16e09027ba1d4133d9867c8e485ddaa22a21e4e0452" diff --git a/pyproject.toml b/pyproject.toml index 11329e71..6a5b6875 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,9 +1,9 @@ -# SPDX-FileCopyrightText: (c) 2018-2025 Siemens +# SPDX-FileCopyrightText: (c) 2018-2026 Siemens # SPDX-License-Identifier: MIT [project] name = "capycli" -version = "2.11.1" +version = "2.12.0.dev1" description = "CaPyCli - Clearing Automation Python Command Line Interface for SW360" readme="Readme.md" requires-python = ">=3.11,<3.15" @@ -33,7 +33,7 @@ dependencies = [ "pyjwt (>=2.4.0,<3.0.0)", "openpyxl (>=3.0.3,<4.0.0)", "requirements-parser (==0.11.0)", - "sw360 (>=1.11.1)", + "sw360 (==1.12.0.dev1)", "wheel (>=0.38.4,<0.39.0)", "cli-support (==2.0.1)", "chardet (==5.2.0)", @@ -80,6 +80,7 @@ codespell = "^2.2.6" pyinstaller = "^6.17.0" flake8 = "^7.3.0" + [build-system] requires = ["poetry>=0.12"] build-backend = "poetry.masonry.api" diff --git a/tests/test_base.py b/tests/test_base.py index 390248db..2b549df4 100644 --- a/tests/test_base.py +++ b/tests/test_base.py @@ -71,6 +71,8 @@ def __init__(self) -> None: self.copy_from = "" self.remote_check_list: str = "" self.local_checklist_list: str = "" + self.client_id: str = "" + self.client_secret: str = "" class TestBasePytest: diff --git a/tests/test_script_base.py b/tests/test_script_base.py index 46ea1412..0e231c09 100644 --- a/tests/test_script_base.py +++ b/tests/test_script_base.py @@ -10,10 +10,10 @@ import pytest import responses -from sw360.sw360error import SW360Error from capycli.common.script_base import ScriptBase from capycli.main.result_codes import ResultCode +from sw360.sw360error import SW360Error from tests.test_base import TestBase diff --git a/tests/test_update_project.py b/tests/test_update_project.py index 28877316..596112f9 100644 --- a/tests/test_update_project.py +++ b/tests/test_update_project.py @@ -4,10 +4,10 @@ from cyclonedx.model.bom import Bom from pytest import fixture, raises -from sw360 import SW360Error from capycli.main.result_codes import ResultCode from capycli.project.create_project import CreateProject +from sw360 import SW360Error IRRELEVANT_STR = "irrelevant" IRRELEVANT_DICT = {"irrelevant": "data"} diff --git a/tox.ini b/tox.ini index 494a822d..702019a5 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ # note: encoding must be UTF-8 without any BOM [flake8] -max-line-length = 120 +max-line-length = 130 ignore = W504, W503 exclude = Legacy, xxcli, xxsw360, demo extend-exclude = .venv From 5f15c83d830cbd0cf155581edfbd9260ea2abc74 Mon Sep 17 00:00:00 2001 From: Thomas Graf Date: Thu, 20 Aug 2026 16:17:06 +0200 Subject: [PATCH 2/2] style: fix isort issues --- capycli/bom/check_bom.py | 2 +- capycli/bom/check_bom_item_status.py | 2 +- capycli/bom/create_components.py | 2 +- capycli/bom/findsources.py | 4 ++-- capycli/bom/map_bom.py | 2 +- capycli/common/component_cache.py | 1 + capycli/common/purl_service.py | 2 +- capycli/common/script_base.py | 2 +- capycli/project/check_prerequisites.py | 2 +- capycli/project/create_bom.py | 2 +- capycli/project/create_project.py | 2 +- capycli/project/find_project.py | 2 +- capycli/project/get_license_info.py | 3 ++- capycli/project/project_component_check.py | 3 ++- capycli/project/show_ecc.py | 3 ++- capycli/project/show_licenses.py | 2 +- capycli/project/show_project.py | 2 +- capycli/project/show_vulnerabilities.py | 2 +- tests/test_script_base.py | 2 +- tests/test_update_project.py | 2 +- 20 files changed, 24 insertions(+), 20 deletions(-) diff --git a/capycli/bom/check_bom.py b/capycli/bom/check_bom.py index 81990181..53b98e10 100644 --- a/capycli/bom/check_bom.py +++ b/capycli/bom/check_bom.py @@ -15,12 +15,12 @@ from colorama import Fore, Style from cyclonedx.model.bom import Bom from cyclonedx.model.component import Component +from sw360 import SW360Error, SW360Keycloak import capycli.common.script_base from capycli.common.capycli_bom_support import CaPyCliBom, CycloneDxSupport from capycli.common.print import print_green, print_red, print_text, print_yellow from capycli.main.result_codes import ResultCode -from sw360 import SW360Error, SW360Keycloak LOG = capycli.get_logger(__name__) diff --git a/capycli/bom/check_bom_item_status.py b/capycli/bom/check_bom_item_status.py index 8a312ef3..f262c95d 100644 --- a/capycli/bom/check_bom_item_status.py +++ b/capycli/bom/check_bom_item_status.py @@ -15,12 +15,12 @@ from colorama import Fore, Style from cyclonedx.model.bom import Bom from cyclonedx.model.component import Component +from sw360 import SW360Error, SW360Keycloak import capycli.common.script_base from capycli.common.capycli_bom_support import CaPyCliBom, CycloneDxSupport from capycli.common.print import print_red, print_text from capycli.main.result_codes import ResultCode -from sw360 import SW360Error, SW360Keycloak LOG = capycli.get_logger(__name__) diff --git a/capycli/bom/create_components.py b/capycli/bom/create_components.py index 54608fc9..eab2b660 100644 --- a/capycli/bom/create_components.py +++ b/capycli/bom/create_components.py @@ -20,6 +20,7 @@ from cyclonedx.model.bom import Bom from cyclonedx.model.component import Component from cyclonedx.model.license import DisjunctiveLicense, LicenseExpression +from sw360 import SW360Error, SW360Keycloak import capycli.common.json_support import capycli.common.script_base @@ -29,7 +30,6 @@ from capycli.common.purl_utils import PurlUtils from capycli.common.script_support import ScriptSupport from capycli.main.result_codes import ResultCode -from sw360 import SW360Error, SW360Keycloak LOG = capycli.get_logger(__name__) diff --git a/capycli/bom/findsources.py b/capycli/bom/findsources.py index 13e66df3..caf709ea 100644 --- a/capycli/bom/findsources.py +++ b/capycli/bom/findsources.py @@ -24,6 +24,8 @@ from cyclonedx.model import ExternalReferenceType, XsUri from cyclonedx.model.bom import Bom from cyclonedx.model.component import Component +from sw360 import SW360Error +from sw360.sw360keycloak import SW360Keycloak import capycli.common.script_base from capycli import get_logger @@ -31,8 +33,6 @@ from capycli.common.github_support import GitHubSupport from capycli.common.print import print_green, print_red, print_text, print_yellow from capycli.main.result_codes import ResultCode -from sw360 import SW360Error -from sw360.sw360keycloak import SW360Keycloak LOG = get_logger(__name__) diff --git a/capycli/bom/map_bom.py b/capycli/bom/map_bom.py index be5ff7c4..313436a1 100644 --- a/capycli/bom/map_bom.py +++ b/capycli/bom/map_bom.py @@ -21,6 +21,7 @@ from cyclonedx.model.bom import Bom from cyclonedx.model.component import Component from packageurl import PackageURL +from sw360 import SW360, SW360Keycloak import capycli.common.file_support import capycli.common.script_base @@ -34,7 +35,6 @@ from capycli.common.purl_service import PurlService from capycli.common.purl_utils import PurlUtils from capycli.main.result_codes import ResultCode -from sw360 import SW360, SW360Keycloak LOG = get_logger(__name__) diff --git a/capycli/common/component_cache.py b/capycli/common/component_cache.py index 883ad12a..780e7ffe 100644 --- a/capycli/common/component_cache.py +++ b/capycli/common/component_cache.py @@ -12,6 +12,7 @@ from typing import Any, Dict, List, Optional import sw360 + from capycli import get_logger from capycli.common.print import print_red, print_text, print_yellow from capycli.common.script_support import ScriptSupport diff --git a/capycli/common/purl_service.py b/capycli/common/purl_service.py index a2b79510..b1415545 100644 --- a/capycli/common/purl_service.py +++ b/capycli/common/purl_service.py @@ -9,11 +9,11 @@ from typing import Any, Dict, List, Optional import packageurl +from sw360 import SW360 from capycli.common.print import print_green, print_text, print_yellow from capycli.common.purl_store import PurlStore from capycli.common.purl_utils import PurlUtils -from sw360 import SW360 class PurlService: diff --git a/capycli/common/script_base.py b/capycli/common/script_base.py index ebd3d89e..e535f08f 100644 --- a/capycli/common/script_base.py +++ b/capycli/common/script_base.py @@ -19,10 +19,10 @@ import jwt import requests from cyclonedx.model.bom import Bom +from sw360 import SW360, SW360Error from capycli.common.print import print_red, print_text, print_yellow from capycli.main.result_codes import ResultCode -from sw360 import SW360, SW360Error class ScriptBase: diff --git a/capycli/project/check_prerequisites.py b/capycli/project/check_prerequisites.py index 500c81d3..8ac68cc7 100644 --- a/capycli/project/check_prerequisites.py +++ b/capycli/project/check_prerequisites.py @@ -13,13 +13,13 @@ from colorama import Fore from cyclonedx.model.bom import Bom +from sw360 import SW360Keycloak import capycli.common.script_base from capycli import get_logger from capycli.common.capycli_bom_support import CaPyCliBom, CycloneDxSupport from capycli.common.print import print_green, print_red, print_text, print_yellow from capycli.main.result_codes import ResultCode -from sw360 import SW360Keycloak LOG = get_logger(__name__) diff --git a/capycli/project/create_bom.py b/capycli/project/create_bom.py index 904121cc..dd7bb0db 100644 --- a/capycli/project/create_bom.py +++ b/capycli/project/create_bom.py @@ -14,6 +14,7 @@ from cyclonedx.model.bom import Bom from cyclonedx.model.component import Component from packageurl import PackageURL +from sw360 import SW360Error, SW360Keycloak import capycli.common.script_base from capycli import get_logger @@ -21,7 +22,6 @@ from capycli.common.print import print_red, print_text, print_yellow from capycli.common.purl_utils import PurlUtils from capycli.main.result_codes import ResultCode -from sw360 import SW360Error, SW360Keycloak LOG = get_logger(__name__) diff --git a/capycli/project/create_project.py b/capycli/project/create_project.py index 1e15a196..bb5b7ce5 100644 --- a/capycli/project/create_project.py +++ b/capycli/project/create_project.py @@ -13,13 +13,13 @@ import requests from cyclonedx.model.bom import Bom +from sw360 import SW360Error, SW360Keycloak import capycli.common.script_base from capycli import get_logger from capycli.common.capycli_bom_support import CaPyCliBom, CycloneDxSupport from capycli.common.print import print_red, print_text, print_yellow from capycli.main.result_codes import ResultCode -from sw360 import SW360Error, SW360Keycloak LOG = get_logger(__name__) diff --git a/capycli/project/find_project.py b/capycli/project/find_project.py index 4ca5430b..8033bfef 100644 --- a/capycli/project/find_project.py +++ b/capycli/project/find_project.py @@ -12,11 +12,11 @@ from typing import Any, Dict, Optional import requests +from sw360 import SW360Error, SW360Keycloak import capycli.common.script_base from capycli.common.print import print_red, print_text, print_yellow from capycli.main.result_codes import ResultCode -from sw360 import SW360Error, SW360Keycloak LOG = capycli.get_logger(__name__) diff --git a/capycli/project/get_license_info.py b/capycli/project/get_license_info.py index a5ab0891..890ad77a 100644 --- a/capycli/project/get_license_info.py +++ b/capycli/project/get_license_info.py @@ -12,12 +12,13 @@ import sys from typing import Any, Dict, List +from sw360 import SW360Error, SW360Keycloak + import capycli.common.script_base from capycli.common.json_support import load_json_file from capycli.common.print import print_red, print_text, print_yellow from capycli.common.script_support import ScriptSupport from capycli.main.result_codes import ResultCode -from sw360 import SW360Error, SW360Keycloak LOG = capycli.get_logger(__name__) diff --git a/capycli/project/project_component_check.py b/capycli/project/project_component_check.py index 88441a70..f1a31221 100644 --- a/capycli/project/project_component_check.py +++ b/capycli/project/project_component_check.py @@ -10,11 +10,12 @@ import sys from typing import Any +from sw360 import SW360Error, SW360Keycloak + import capycli.common.script_base from capycli.bom.component_check import ComponentCheck from capycli.common.print import print_red, print_text, print_yellow from capycli.main.result_codes import ResultCode -from sw360 import SW360Error, SW360Keycloak LOG = capycli.get_logger(__name__) diff --git a/capycli/project/show_ecc.py b/capycli/project/show_ecc.py index 71d826c1..5d2e60f8 100644 --- a/capycli/project/show_ecc.py +++ b/capycli/project/show_ecc.py @@ -10,11 +10,12 @@ import sys from typing import Any, Dict +from sw360 import SW360Error, SW360Keycloak + import capycli.common.script_base from capycli.common.json_support import write_json_to_file from capycli.common.print import print_green, print_red, print_text, print_yellow from capycli.main.result_codes import ResultCode -from sw360 import SW360Error, SW360Keycloak LOG = capycli.get_logger(__name__) diff --git a/capycli/project/show_licenses.py b/capycli/project/show_licenses.py index e00f68db..06e1697a 100644 --- a/capycli/project/show_licenses.py +++ b/capycli/project/show_licenses.py @@ -15,11 +15,11 @@ from cli_support import CliFile from colorama import Fore, Style +from sw360 import SW360Keycloak import capycli.common.script_base from capycli.common.print import print_red, print_text, print_yellow from capycli.main.result_codes import ResultCode -from sw360 import SW360Keycloak LOG = capycli.get_logger(__name__) diff --git a/capycli/project/show_project.py b/capycli/project/show_project.py index 8b212094..03e75aa7 100644 --- a/capycli/project/show_project.py +++ b/capycli/project/show_project.py @@ -11,12 +11,12 @@ from typing import Any, Dict, Optional from colorama import Fore +from sw360 import SW360Error, SW360Keycloak import capycli.common.json_support import capycli.common.script_base from capycli.common.print import print_red, print_text, print_yellow from capycli.main.result_codes import ResultCode -from sw360 import SW360Error, SW360Keycloak LOG = capycli.get_logger(__name__) diff --git a/capycli/project/show_vulnerabilities.py b/capycli/project/show_vulnerabilities.py index 00e7c55f..e8078351 100644 --- a/capycli/project/show_vulnerabilities.py +++ b/capycli/project/show_vulnerabilities.py @@ -12,12 +12,12 @@ import requests from colorama import Fore, Style +from sw360 import SW360Error, SW360Keycloak import capycli.common.json_support import capycli.common.script_base from capycli.common.print import print_green, print_red, print_text, print_yellow from capycli.main.result_codes import ResultCode -from sw360 import SW360Error, SW360Keycloak LOG = capycli.get_logger(__name__) diff --git a/tests/test_script_base.py b/tests/test_script_base.py index 0e231c09..46ea1412 100644 --- a/tests/test_script_base.py +++ b/tests/test_script_base.py @@ -10,10 +10,10 @@ import pytest import responses +from sw360.sw360error import SW360Error from capycli.common.script_base import ScriptBase from capycli.main.result_codes import ResultCode -from sw360.sw360error import SW360Error from tests.test_base import TestBase diff --git a/tests/test_update_project.py b/tests/test_update_project.py index 596112f9..28877316 100644 --- a/tests/test_update_project.py +++ b/tests/test_update_project.py @@ -4,10 +4,10 @@ from cyclonedx.model.bom import Bom from pytest import fixture, raises +from sw360 import SW360Error from capycli.main.result_codes import ResultCode from capycli.project.create_project import CreateProject -from sw360 import SW360Error IRRELEVANT_STR = "irrelevant" IRRELEVANT_DICT = {"irrelevant": "data"}