diff --git a/.github/workflows/dev-release.yaml b/.github/workflows/dev-release.yaml new file mode 100644 index 00000000..08998350 --- /dev/null +++ b/.github/workflows/dev-release.yaml @@ -0,0 +1,67 @@ +name: Zowe SDK Release + +on: + pull_request_target: + types: + - closed + branches: + - main + +jobs: + release: + if: ${{ github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'release-dev') }} + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + with: + ref: main + token: ${{ secrets.ZOWE_ROBOT_TOKEN }} + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.10" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + + - name: Increment dev version + id: update-version + shell: python + run: | + import sys + sys.path.append("src") + from _version import __version__ + prerelease_tag = "dev" + tag_end_index = __version__.index(prerelease_tag) + len(prerelease_tag) + new_version = __version__[:tag_end_index] + str(int(__version__[tag_end_index:]) + 1) + with open("src/_version.py", 'w') as f: + f.write("__version__ = \"" + new_version + "\"\n") + print("::set-output name=version::" + new_version) + print("::set-output name=cargo-version::" + "-".join(new_version.rsplit(".", 1))) + + - name: Increment dev version (cargo) + run: cargo install cargo-edit && cargo set-version ${{ steps.update-version.outputs.cargo-version }} + working-directory: src/secrets + + - name: Build dist wheels + run: bash build.sh + + - name: Commit version update + uses: stefanzweifel/git-auto-commit-action@v4 + with: + branch: main + commit_message: "Bump version to ${{ steps.update-version.outputs.version }} [ci skip]" + commit_options: "--signoff" + commit_user_name: ${{ secrets.ZOWE_ROBOT_USER }} + commit_user_email: ${{ secrets.ZOWE_ROBOT_EMAIL }} + file_pattern: "src/_version.py src/secrets/Cargo.*" + tagging_message: v${{ steps.update-version.outputs.version }} + + - name: Publish packages to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.PYPI_ROBOT_TOKEN }} diff --git a/.github/workflows/dev-release.yaml.old b/.github/workflows/dev-release.yaml.old deleted file mode 100644 index 512f5846..00000000 --- a/.github/workflows/dev-release.yaml.old +++ /dev/null @@ -1,69 +0,0 @@ -name: Zowe SDK Release - -on: - pull_request_target: - types: - - closed - branches: - - main - -jobs: - release: - if: ${{ github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'release-dev') }} - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - with: - ref: main - token: ${{ secrets.ZOWE_ROBOT_TOKEN }} - - - name: Set up Python 3.7 - uses: actions/setup-python@v4 - with: - python-version: 3.7 - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - - - name: Increment dev version - id: update-version - shell: python - run: | - import sys - sys.path.append("src") - from _version import __version__ - prerelease_tag = "dev" - tag_end_index = __version__.index(prerelease_tag) + len(prerelease_tag) - new_version = __version__[:tag_end_index] + str(int(__version__[tag_end_index:]) + 1) - with open("src/_version.py", 'w', encoding="utf-8") as f: - f.write("__version__ = \"" + new_version + "\"\n") - print("::set-output name=version::" + new_version) - - - name: Build dist wheels - run: bash build.sh - - - name: Create zip bundle - working-directory: dist - run: | - pip download -f . --no-binary=pyyaml zowe-${{ steps.update-version.outputs.version }}-py3-none-any.whl - zip zowe-python-sdk-${{ steps.update-version.outputs.version }}.zip PyYAML*.tar.gz *.whl - - - name: Commit version update - uses: stefanzweifel/git-auto-commit-action@v4 - with: - branch: main - commit_message: 'Bump version to ${{ steps.update-version.outputs.version }} [ci skip]' - commit_options: '--signoff' - commit_user_name: ${{ secrets.ZOWE_ROBOT_USER }} - commit_user_email: ${{ secrets.ZOWE_ROBOT_EMAIL }} - file_pattern: 'src/_version.py' - tagging_message: v${{ steps.update-version.outputs.version }} - - - name: Create GitHub release - uses: softprops/action-gh-release@v1 - with: - files: 'dist/zowe-python-sdk-${{ steps.update-version.outputs.version }}.zip' - tag_name: v${{ steps.update-version.outputs.version }} diff --git a/.github/workflows/sdk-build.yml b/.github/workflows/sdk-build.yml index 8077ae66..b90cf85f 100644 --- a/.github/workflows/sdk-build.yml +++ b/.github/workflows/sdk-build.yml @@ -13,41 +13,41 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: ['3.7', '3.8', '3.9', '3.10', '3.11'] + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] env: OS: ${{ matrix.os }} PYTHON: ${{ matrix.python-version }} steps: - - uses: actions/checkout@v3 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -r requirements.txt - - name: Lint with flake8 - run: | - # stop the build if there are Python syntax errors or undefined names - flake8 ./src --count --select=E9,F63,F7,F82 --show-source --statistics - # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 ./src --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - - name: Test with pytest - run: | - coverage run -m pytest ./tests/unit - - name: Generate a coverage xml file - run: | - coverage xml - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3 - if: ${{ matrix.python-version == '3.11' }} - with: - directory: ./ - env_vars: OS,PYTHON - fail_ci_if_error: true - files: ./coverage.xml - flags: unittests - name: codecov-umbrella - verbose: true + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + - name: Lint with flake8 + run: | + # stop the build if there are Python syntax errors or undefined names + flake8 ./src --count --select=E9,F63,F7,F82 --show-source --statistics + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + flake8 ./src --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + - name: Test with pytest + run: | + coverage run -m pytest ./tests/unit + - name: Generate a coverage xml file + run: | + coverage xml + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 + if: ${{ matrix.python-version == '3.12' }} + with: + directory: ./ + env_vars: OS,PYTHON + fail_ci_if_error: true + files: ./coverage.xml + flags: unittests + name: codecov-umbrella + verbose: true diff --git a/.github/workflows/secrets-sdk.yml b/.github/workflows/secrets-sdk.yml new file mode 100644 index 00000000..8db3c6ac --- /dev/null +++ b/.github/workflows/secrets-sdk.yml @@ -0,0 +1,144 @@ +# This file is autogenerated by maturin v1.3.1 +# To update, run +# +# maturin generate-ci github +# +name: Secrets SDK CI + +on: + push: + paths: + - "src/secrets/**" + - ".github/workflows/secrets-sdk.yml" + pull_request: + paths: + - "src/secrets/**" +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + linux: + runs-on: ubuntu-latest + strategy: + matrix: + target: [x86_64, x86, aarch64, armv7, s390x] + fail-fast: false + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: "3.10" + - name: Set environment variables + run: src/secrets/scripts/configure-cross.sh ${{ matrix.target }} + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + target: ${{ matrix.target }} + args: --release --out dist --skip-auditwheel + sccache: "true" + manylinux: auto + docker-options: -e PKG_CONFIG_SYSROOT_DIR -e PKG_CONFIG_PATH + working-directory: src/secrets + before-script-linux: | + if command -v yum &> /dev/null; then + yum update && yum install -y libsecret-devel.${{ env.CROSS_DEB_ARCH }} pkgconfig + else + dpkg --add-architecture ${{ env.CROSS_DEB_ARCH }} + sed -i "s/deb /deb [arch=amd64] /g" /etc/apt/sources.list + echo "deb [arch=${{ env.CROSS_DEB_ARCH }}] http://ports.ubuntu.com/ubuntu-ports/ jammy main universe" >> /etc/apt/sources.list + echo "deb [arch=${{ env.CROSS_DEB_ARCH }}] http://ports.ubuntu.com/ubuntu-ports/ jammy-updates main universe" >> /etc/apt/sources.list + apt-get update && apt-get install -y libsecret-1-dev:${{ env.CROSS_DEB_ARCH }} pkg-config + fi + - name: Upload wheels + uses: actions/upload-artifact@v3 + with: + name: wheels + path: src/secrets/dist + + windows: + runs-on: windows-latest + strategy: + matrix: + target: [x64, x86, aarch64] + fail-fast: false + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + if: ${{ matrix.target != 'aarch64' }} + with: + python-version: "3.10" + architecture: ${{ matrix.target }} + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + target: ${{ matrix.target }} + args: --release --out dist + sccache: "true" + working-directory: src/secrets + - name: Upload wheels + uses: actions/upload-artifact@v3 + with: + name: wheels + path: src/secrets/dist + + macos: + runs-on: macos-latest + strategy: + matrix: + target: [x86_64, aarch64] + fail-fast: false + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: "3.10" + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + target: ${{ matrix.target }} + args: --release --out dist + sccache: "true" + working-directory: src/secrets + - name: Upload wheels + uses: actions/upload-artifact@v3 + with: + name: wheels + path: src/secrets/dist + + sdist: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Build sdist + uses: PyO3/maturin-action@v1 + with: + command: sdist + args: --out dist + working-directory: src/secrets + - name: Upload sdist + uses: actions/upload-artifact@v3 + with: + name: wheels + path: src/secrets/dist + + release: + name: Release + runs-on: ubuntu-latest + if: ${{ startsWith(github.ref, 'refs/tags/') }} + needs: [linux, windows, macos, sdist] + steps: + - uses: actions/download-artifact@v3 + with: + name: wheels + - name: Publish to PyPI + uses: PyO3/maturin-action@v1 + env: + MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_ROBOT_TOKEN }} + with: + command: upload + args: --non-interactive --skip-existing * + working-directory: src/secrets diff --git a/.vscode/settings.json b/.vscode/settings.json index baf3b191..1233617b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,7 @@ { "editor.formatOnSave": true, "python.formatting.provider": "black", + "rust-analyzer.linkedProjects": ["./src/secrets/Cargo.toml"], "[python]": { "editor.codeActionsOnSave": { "source.organizeImports": true diff --git a/CHANGELOG.md b/CHANGELOG.md index bae3ed1b..3a979b62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,12 +4,17 @@ All notable changes to the Zowe Client Python SDK will be documented in this fil ## Recent Changes -- Bug: Fixed profile merge order to match Node.js SDK -- Feature: Added method to load profile properties from environment variables -- Bugfix: Fixed exception handling in session.py [#213] (https://github.com/zowe/zowe-client-python-sdk/issues/213) -- Feature: Added a CredentialManager class to securely retrieve values from credentials and manage multiple credential entries on Windows [#134](https://github.com/zowe/zowe-client-python-sdk/issues/134) -- Feature: Added method to Save profile properties to zowe.config.json file [#73](https://github.com/zowe/zowe-client-python-sdk/issues/73) -- Feature: Added method to Save secure profile properties to vault [#72](https://github.com/zowe/zowe-client-python-sdk/issues/72) -- Bugfix: Fixed issue for datasets and jobs with special characters in URL [#211] (https://github.com/zowe/zowe-client-python-sdk/issues/211) -- Feature: Added method to load profile properties from environment variables -- BugFix: Validation of zowe.config.json file matching the schema [#192](https://github.com/zowe/zowe-client-python-sdk/issues/192) +### Enhancements + +- Added method to save secure profile properties to vault [#72](https://github.com/zowe/zowe-client-python-sdk/issues/72) +- Added method to save profile properties to zowe.config.json file [#73](https://github.com/zowe/zowe-client-python-sdk/issues/73) +- Added CredentialManager class to securely retrieve values from credentials and manage multiple credential entries on Windows [#134](https://github.com/zowe/zowe-client-python-sdk/issues/134) +- Added method to load profile properties from environment variables [#136](https://github.com/zowe/zowe-client-python-sdk/issues/136) +- Added validation of zowe.config.json file matching the schema [#192](https://github.com/zowe/zowe-client-python-sdk/issues/192) +- Added Secrets SDK for storing client secrets in OS keyring [#208](https://github.com/zowe/zowe-client-python-sdk/issues/208) + +### Bug Fixes + +- Fixed profile merge order to match Node.js SDK [#190](https://github.com/zowe/zowe-client-python-sdk/issues/190) +- Fixed issue for datasets and jobs with special characters in URL [#211](https://github.com/zowe/zowe-client-python-sdk/issues/211) +- Fixed exception handling in session.py [#213](https://github.com/zowe/zowe-client-python-sdk/issues/213) diff --git a/docs/requirements.txt b/docs/requirements.txt index 96f30abe..5601d12c 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,5 +1,5 @@ sphinx_rtd_theme>=0.5.1 -sphinxcontrib-spelling==5.4.0 +sphinxcontrib-spelling==8.0.0 -e ./src/core -e ./src/zos_console -e ./src/zos_files diff --git a/requirements.txt b/requirements.txt index c685de39..a5a442e4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,19 +5,19 @@ colorama==0.4.4 commentjson==0.9.0 coverage==5.4 deepmerge==1.1.0 -flake8==3.8.4 +flake8==5.0.0 idna==2.10 -importlib-metadata==3.6.0 +importlib-metadata==3.6.0;python_version<"3.8" isort jsonschema==4.17.3 -keyring lxml==4.9.3 -mccabe==0.6.1 +maturin +mccabe==0.7.0 nose2==0.10.0 -pycodestyle==2.6.0 +pycodestyle==2.9.0 pydocstyle==5.1.1 pyfakefs -pyflakes==2.2.0 +pyflakes==2.5.0 pylama==7.7.1 pytest==7.1.2 python-decouple==3.4 @@ -30,7 +30,8 @@ Unidecode==1.2.0 urllib3==1.26.18 wheel zipp==3.4.0 --e ./src/core +-e ./src/core[secrets] +-e ./src/secrets -e ./src/zos_console -e ./src/zos_files -e ./src/zos_jobs diff --git a/src/core/setup.py b/src/core/setup.py index aed4a73a..5919f70f 100644 --- a/src/core/setup.py +++ b/src/core/setup.py @@ -2,8 +2,9 @@ from setuptools import find_namespace_packages, setup -sys.path.append("..") +sys.path.insert(0, "..") from _version import __version__ +from setup import resolve_sdk_dep setup( name="zowe_core_for_zowe_sdk", @@ -18,6 +19,16 @@ "Programming Language :: Python :: 3.7", "License :: OSI Approved :: Eclipse Public License 2.0 (EPL-2.0)", ], - install_requires=["requests", "urllib3", "pyyaml", "commentjson"], + install_requires=[ + "commentjson", + "deepmerge", + "jsonschema", + "pyyaml", + "requests", + "urllib3", + ], + extras_require={ + "secrets": [resolve_sdk_dep("secrets", "~=" + __version__)] + }, packages=find_namespace_packages(include=["zowe.*"]), ) diff --git a/src/core/zowe/core_for_zowe_sdk/credential_manager.py b/src/core/zowe/core_for_zowe_sdk/credential_manager.py index 7e46137d..3459619c 100644 --- a/src/core/zowe/core_for_zowe_sdk/credential_manager.py +++ b/src/core/zowe/core_for_zowe_sdk/credential_manager.py @@ -10,7 +10,6 @@ Copyright Contributors to the Zowe Project. """ import base64 -import logging import sys from typing import Optional @@ -21,8 +20,7 @@ HAS_KEYRING = True try: - import keyring - + from zowe.secrets_for_zowe_sdk import keyring except ImportError: HAS_KEYRING = False @@ -45,8 +43,7 @@ def load_secure_props() -> None: return try: - service_name = constants["ZoweServiceName"] - secret_value = CredentialManager._retrieve_credential(service_name) + secret_value = CredentialManager._get_credential(constants["ZoweServiceName"], constants["ZoweAccountName"]) # Handle the case when secret_value is None if secret_value is None: return @@ -61,7 +58,27 @@ def load_secure_props() -> None: CredentialManager.secure_props = secure_config_json @staticmethod - def _retrieve_credential(service_name: str) -> Optional[str]: + def save_secure_props() -> None: + """ + Set secure_props for the given config file + Returns + ------- + None + """ + if not HAS_KEYRING: + return + + credential = CredentialManager.secure_props + # Check if credential is a non-empty string + if credential: + encoded_credential = base64.b64encode(commentjson.dumps(credential).encode()).decode() + if sys.platform == "win32": + # Delete the existing credential + CredentialManager._delete_credential(constants["ZoweServiceName"], constants["ZoweAccountName"]) + CredentialManager._set_credential(constants["ZoweServiceName"], constants["ZoweAccountName"], encoded_credential) + + @staticmethod + def _get_credential(service_name: str, account_name: str) -> Optional[str]: """ Retrieve the credential from the keyring or storage. If the credential exceeds the maximum length, retrieve it in parts. @@ -74,30 +91,18 @@ def _retrieve_credential(service_name: str) -> Optional[str]: str The retrieved encoded credential """ - # Configure the logger to ignore warning messages - logging.getLogger().setLevel(logging.ERROR) - is_win32 = sys.platform == "win32" - if is_win32: - service_name += "/" + constants["ZoweAccountName"] - encoded_credential = keyring.get_password(service_name, constants["ZoweAccountName"]) - if encoded_credential is None and is_win32: + encoded_credential = keyring.get_password(service_name, account_name) + if encoded_credential is None and sys.platform == "win32": # Retrieve the secure value with an index index = 1 - temp_value = keyring.get_password(f"{service_name}-{index}", f"{constants['ZoweAccountName']}-{index}") + temp_value = keyring.get_password(service_name, f"{account_name}-{index}") while temp_value is not None: if encoded_credential is None: encoded_credential = temp_value else: encoded_credential += temp_value index += 1 - temp_value = keyring.get_password(f"{service_name}-{index}", f"{constants['ZoweAccountName']}-{index}") - - if is_win32: - try: - encoded_credential = encoded_credential.encode("utf-16le").decode() - except (UnicodeDecodeError, AttributeError): - # The credential is not encoded in UTF-16 - pass + temp_value = keyring.get_password(service_name, f"{account_name}-{index}") if encoded_credential is not None and encoded_credential.endswith("\0"): encoded_credential = encoded_credential[:-1] @@ -105,7 +110,24 @@ def _retrieve_credential(service_name: str) -> Optional[str]: return encoded_credential @staticmethod - def delete_credential(service_name: str, account_name: str) -> None: + def _set_credential(service_name: str, account_name: str, encoded_credential: str) -> None: + # Check if the encoded credential exceeds the maximum length for win32 + if sys.platform == "win32" and len(encoded_credential) > constants["WIN32_CRED_MAX_STRING_LENGTH"]: + # Split the encoded credential string into chunks of maximum length + chunk_size = constants["WIN32_CRED_MAX_STRING_LENGTH"] + encoded_credential += "\0" + chunks = [encoded_credential[i : i + chunk_size] for i in range(0, len(encoded_credential), chunk_size)] + # Set the individual chunks as separate keyring entries + for index, chunk in enumerate(chunks, start=1): + field_name = f"{account_name}-{index}" + keyring.set_password(service_name, field_name, chunk) + + else: + # Credential length is within the maximum limit or not on win32, set it as a single keyring entry + keyring.set_password(service_name, account_name, encoded_credential) + + @staticmethod + def _delete_credential(service_name: str, account_name: str) -> None: """ Delete the credential from the keyring or storage. If the keyring.delete_password function is not available, iterate through and delete credentials. @@ -120,57 +142,13 @@ def delete_credential(service_name: str, account_name: str) -> None: None """ - try: - keyring.delete_password(service_name, account_name) - except keyring.errors.PasswordDeleteError: - pass + keyring.delete_password(service_name, account_name) # Handling multiple credentials stored when the operating system is Windows if sys.platform == "win32": index = 1 while True: field_name = f"{account_name}-{index}" - service_name = f"{service_name}-{index}" - try: - keyring.delete_password(service_name, field_name) - except keyring.errors.PasswordDeleteError: + if not keyring.delete_password(service_name, field_name): break index += 1 - - @staticmethod - def save_secure_props() -> None: - """ - Set secure_props for the given config file - Returns - ------- - None - """ - if not HAS_KEYRING: - return - - service_name = constants["ZoweServiceName"] - credential = CredentialManager.secure_props - # Check if credential is a non-empty string - if credential: - is_win32 = sys.platform == "win32" - - encoded_credential = base64.b64encode(commentjson.dumps(credential).encode()).decode() - if is_win32: - service_name += "/" + constants["ZoweAccountName"] - # Delete the existing credential - CredentialManager.delete_credential(service_name, constants["ZoweAccountName"]) - # Check if the encoded credential exceeds the maximum length for win32 - if is_win32 and len(encoded_credential) > constants["WIN32_CRED_MAX_STRING_LENGTH"]: - # Split the encoded credential string into chunks of maximum length - chunk_size = constants["WIN32_CRED_MAX_STRING_LENGTH"] - encoded_credential += "\0" - chunks = [encoded_credential[i : i + chunk_size] for i in range(0, len(encoded_credential), chunk_size)] - # Set the individual chunks as separate keyring entries - for index, chunk in enumerate(chunks, start=1): - password = (chunk + "\0" * (len(chunk) % 2)).encode().decode("utf-16le") - field_name = f"{constants['ZoweAccountName']}-{index}" - keyring.set_password(f"{service_name}-{index}", field_name, password) - - else: - # Credential length is within the maximum limit or not on win32, set it as a single keyring entry - keyring.set_password(service_name, constants["ZoweAccountName"], encoded_credential) diff --git a/src/core/zowe/core_for_zowe_sdk/zosmf_profile.py b/src/core/zowe/core_for_zowe_sdk/zosmf_profile.py index 5bfea1b8..5ab3ea41 100644 --- a/src/core/zowe/core_for_zowe_sdk/zosmf_profile.py +++ b/src/core/zowe/core_for_zowe_sdk/zosmf_profile.py @@ -22,7 +22,7 @@ HAS_KEYRING = True try: - import keyring + from zowe.secrets_for_zowe_sdk import keyring except ImportError: HAS_KEYRING = False @@ -94,18 +94,12 @@ def __get_secure_value(self, name): service_name = constants["ZoweCredentialKey"] account_name = "zosmf_{}_{}".format(self.profile_name, name) - if sys.platform == "win32": - service_name += "/" + account_name - secret_value = keyring.get_password(service_name, account_name) # Handle the case when secret_value is None if secret_value is None: secret_value = "" - if sys.platform == "win32": - secret_value = secret_value.encode("utf-16") - secret_value = base64.b64decode(secret_value).decode().strip('"') return secret_value @@ -122,38 +116,3 @@ def __load_secure_credentials(self): raise SecureProfileLoadFailed(self.profile_name, e) else: return (zosmf_user, zosmf_password) - - -if HAS_KEYRING and sys.platform.startswith("linux"): - from contextlib import closing - - from keyring.backends import SecretService - - class KeyringBackend(SecretService.Keyring): - """ - Class used to handle secured profiles. - - Methods - ------- - get_password(service, username) - Get the decoded password - """ - - def __get_password(self, service, username, collection): - items = collection.search_items({"account": username, "service": service}) - for item in items: - if hasattr(item, "unlock"): - if item.is_locked() and item.unlock()[0]: - raise keyring.errors.InitError("failed to unlock item") - return item.get_secret().decode("utf-8") - - def get_password(self, service, username): - """Get password of the username for the service.""" - collection = self.get_preferred_collection() - if hasattr(collection, "connection"): - with closing(collection.connection): - return self.__get_password(service, username, collection) - else: - return self.__get_password(service, username, collection) - - keyring.set_keyring(KeyringBackend()) diff --git a/src/secrets/.gitignore b/src/secrets/.gitignore new file mode 100644 index 00000000..c8f04429 --- /dev/null +++ b/src/secrets/.gitignore @@ -0,0 +1,72 @@ +/target + +# Byte-compiled / optimized / DLL files +__pycache__/ +.pytest_cache/ +*.py[cod] + +# C extensions +*.so + +# Distribution / packaging +.Python +.venv/ +env/ +bin/ +build/ +develop-eggs/ +dist/ +eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +include/ +man/ +venv/ +*.egg-info/ +.installed.cfg +*.egg + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt +pip-selfcheck.json + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.cache +nosetests.xml +coverage.xml + +# Translations +*.mo + +# Mr Developer +.mr.developer.cfg +.project +.pydevproject + +# Rope +.ropeproject + +# Django stuff: +*.log +*.pot + +.DS_Store + +# Sphinx documentation +docs/_build/ + +# PyCharm +.idea/ + +# VSCode +.vscode/ + +# Pyenv +.python-version diff --git a/src/secrets/Cargo.lock b/src/secrets/Cargo.lock new file mode 100644 index 00000000..8d9fbdee --- /dev/null +++ b/src/secrets/Cargo.lock @@ -0,0 +1,767 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" + +[[package]] +name = "cfg-expr" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03915af431787e6ffdcc74c645077518c6b6e01f80b761e0fbbfa288536311b3" +dependencies = [ + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "futures-channel" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" + +[[package]] +name = "futures-executor" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" + +[[package]] +name = "futures-macro" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "futures-task" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" + +[[package]] +name = "futures-util" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" +dependencies = [ + "futures-core", + "futures-macro", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "gio" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57052f84e8e5999b258e8adf8f5f2af0ac69033864936b8b6838321db2f759b1" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "gio-sys", + "glib", + "libc", + "once_cell", + "pin-project-lite", + "smallvec", + "thiserror", +] + +[[package]] +name = "gio-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", + "winapi", +] + +[[package]] +name = "glib" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c316afb01ce8067c5eaab1fc4f2cd47dc21ce7b6296358605e2ffab23ccbd19" +dependencies = [ + "bitflags 2.4.1", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "gio-sys", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "memchr", + "once_cell", + "smallvec", + "thiserror", +] + +[[package]] +name = "glib-macros" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8da903822b136d42360518653fcf154455defc437d3e7a81475bf9a95ff1e47" +dependencies = [ + "heck", + "proc-macro-crate", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "glib-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" +dependencies = [ + "libc", + "system-deps", +] + +[[package]] +name = "gobject-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "hashbrown" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "indexmap" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "indoc" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8" + +[[package]] +name = "keyring" +version = "1.0.0-dev10" +dependencies = [ + "pyo3", + "secrets_core", +] + +[[package]] +name = "libc" +version = "0.2.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" + +[[package]] +name = "libsecret" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac6fae6ebe590e06ef9d01b125e46b7d4c05ccbd5961f12b4aefe2ecd010220f" +dependencies = [ + "gio", + "glib", + "libc", + "libsecret-sys", +] + +[[package]] +name = "libsecret-sys" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b716fc5e1c82eb0d28665882628382ab0e0a156a6d73580e33f0ac6ac8d2540" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pkg-config", + "system-deps", +] + +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "memchr" +version = "2.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" + +[[package]] +name = "memoffset" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "pyo3" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04e8453b658fe480c3e70c8ed4e3d3ec33eb74988bd186561b0cc66b85c3bc4b" +dependencies = [ + "cfg-if", + "indoc", + "libc", + "memoffset", + "parking_lot", + "pyo3-build-config", + "pyo3-ffi", + "pyo3-macros", + "unindent", +] + +[[package]] +name = "pyo3-build-config" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96fe70b176a89cff78f2fa7b3c930081e163d5379b4dcdf993e3ae29ca662e5" +dependencies = [ + "once_cell", + "target-lexicon", +] + +[[package]] +name = "pyo3-ffi" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "214929900fd25e6604661ed9cf349727c8920d47deff196c4e28165a6ef2a96b" +dependencies = [ + "libc", + "pyo3-build-config", +] + +[[package]] +name = "pyo3-macros" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dac53072f717aa1bfa4db832b39de8c875b7c7af4f4a6fe93cdbf9264cf8383b" +dependencies = [ + "proc-macro2", + "pyo3-macros-backend", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7774b5a8282bd4f25f803b1f0d945120be959a36c72e08e7cd031c792fdfd424" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "secrets_core" +version = "0.1.0" +source = "git+https://github.com/zowe/zowe-cli.git?branch=master#1b4d492d35ab44145e823821245a7e08de7d9192" +dependencies = [ + "cfg-if", + "core-foundation", + "core-foundation-sys", + "gio", + "glib", + "glib-sys", + "libsecret", + "libsecret-sys", + "thiserror", + "windows-sys", +] + +[[package]] +name = "serde" +version = "1.0.190" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.190" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "serde_spanned" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12022b835073e5b11e90a14f86838ceb1c8fb0325b72416845c487ac0fa95e80" +dependencies = [ + "serde", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "system-deps" +version = "6.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94af52f9402f94aac4948a2518b43359be8d9ce6cd9efc1c4de3b2f7b7e897d6" +dependencies = [ + "cfg-expr", + "heck", + "pkg-config", + "toml", + "version-compare", +] + +[[package]] +name = "target-lexicon" +version = "0.12.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c39fd04924ca3a864207c66fc2cd7d22d7c016007f9ce846cbb9326331930a" + +[[package]] +name = "thiserror" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "toml" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ff9e3abce27ee2c9a37f9ad37238c1bdd4e789c84ba37df76aa4d528f5072cc" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.20.7", +] + +[[package]] +name = "toml_datetime" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "toml_edit" +version = "0.20.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unindent" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" + +[[package]] +name = "version-compare" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "winnow" +version = "0.5.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3b801d0e0a6726477cc207f60162da452f3a95adb368399bef20a946e06f65c" +dependencies = [ + "memchr", +] diff --git a/src/secrets/Cargo.toml b/src/secrets/Cargo.toml new file mode 100644 index 00000000..3003a8af --- /dev/null +++ b/src/secrets/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "keyring" +version = "1.0.0-dev10" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +name = "keyring" +crate-type = ["cdylib"] + +[dependencies] +pyo3 = { version = "0.20.0", features = ["abi3-py37"] } +secrets_core = { git = "https://github.com/zowe/zowe-cli.git", branch = "master" } diff --git a/src/secrets/pyproject.toml b/src/secrets/pyproject.toml new file mode 100644 index 00000000..131125da --- /dev/null +++ b/src/secrets/pyproject.toml @@ -0,0 +1,18 @@ +[build-system] +requires = ["maturin>=1.3,<2.0"] +build-backend = "maturin" + +[project] +name = "zowe_secrets_for_zowe_sdk" +description = "Zowe Python SDK - Client Secrets package" +license = "EPL-2.0" +requires-python = ">=3.7" +classifiers = [ + "Programming Language :: Rust", + "Programming Language :: Python :: Implementation :: CPython", +] +dynamic = ["version"] + +[tool.maturin] +features = ["pyo3/extension-module"] +module-name = "zowe.secrets_for_zowe_sdk.keyring" diff --git a/src/secrets/scripts/configure-cross.sh b/src/secrets/scripts/configure-cross.sh new file mode 100755 index 00000000..78caf762 --- /dev/null +++ b/src/secrets/scripts/configure-cross.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +# Set environment variables needed for cross-compilation in GITHUB_ENV +set_env() { + echo "CROSS_DEB_ARCH=$1" >> $GITHUB_ENV + echo "PKG_CONFIG_SYSROOT_DIR=/" >> $GITHUB_ENV + echo "RUSTFLAGS=-L $2 $RUSTFLAGS" >> $GITHUB_ENV + echo "PKG_CONFIG_PATH=$2/pkgconfig" >> $GITHUB_ENV +} + +case "$1" in + "aarch64") + set_env arm64 "/usr/lib/aarch64-linux-gnu" + ;; + "armv7") + set_env armhf "/usr/lib/arm-linux-gnueabihf" + ;; + "ppc64le") + set_env ppc64el "/usr/lib/powerpc64le-linux-gnu" + ;; + "s390x") + set_env s390x "/usr/lib/s390x-linux-gnu" + ;; + "x86") + set_env i686 "/usr/lib/i386-linux-gnu" + ;; + "x86_64") + set_env x86_64 "/usr/lib/x86_64-linux-gnu" + ;; + *) + ;; +esac diff --git a/src/secrets/src/lib.rs b/src/secrets/src/lib.rs new file mode 100644 index 00000000..aa012451 --- /dev/null +++ b/src/secrets/src/lib.rs @@ -0,0 +1,57 @@ +use pyo3::exceptions::PyValueError; +use pyo3::prelude::*; + +extern crate secrets_core; +use secrets_core::*; + +#[pyfunction] +fn set_password(service: String, account: String, password: String) -> PyResult<()> { + match os::set_password(&service, &account, &password) { + Ok(_) => Ok(()), + Err(e) => Err(PyValueError::new_err(format!("{:#?}", e))), + } +} + +#[pyfunction] +fn get_password(service: String, account: String) -> PyResult> { + match os::get_password(&service, &account) { + Ok(pw) => Ok(pw), + Err(e) => Err(PyValueError::new_err(format!("{:#?}", e))), + } +} + +#[pyfunction] +fn delete_password(service: String, account: String) -> PyResult { + match os::delete_password(&service, &account) { + Ok(res) => Ok(res), + Err(e) => Err(PyValueError::new_err(format!("{:#?}", e))), + } +} + +#[pyfunction] +fn find_password(service: String) -> PyResult> { + match os::find_password(&service) { + Ok(res) => Ok(res), + Err(e) => Err(PyValueError::new_err(format!("{:#?}", e))), + } +} + +#[pyfunction] +fn find_credentials(service: String) -> PyResult> { + let mut creds: Vec<(String, String)> = vec![]; + match os::find_credentials(&service, &mut creds) { + Ok(res) => Ok(creds), + Err(e) => Err(PyValueError::new_err(format!("{:#?}", e))), + } +} + +/// A Python module implemented in Rust. +#[pymodule] +fn keyring(_py: Python, m: &PyModule) -> PyResult<()> { + m.add_function(wrap_pyfunction!(get_password, m)?)?; + m.add_function(wrap_pyfunction!(set_password, m)?)?; + m.add_function(wrap_pyfunction!(delete_password, m)?)?; + m.add_function(wrap_pyfunction!(find_password, m)?)?; + m.add_function(wrap_pyfunction!(find_credentials, m)?)?; + Ok(()) +} diff --git a/src/secrets/zowe/secrets_for_zowe_sdk/__init__.py b/src/secrets/zowe/secrets_for_zowe_sdk/__init__.py new file mode 100644 index 00000000..f80fb08b --- /dev/null +++ b/src/secrets/zowe/secrets_for_zowe_sdk/__init__.py @@ -0,0 +1,5 @@ +""" +Zowe Python SDK - Client Secrets package +""" + +from . import keyring diff --git a/tests/unit/test_zowe_core.py b/tests/unit/test_zowe_core.py index 5763cf51..202fab0e 100644 --- a/tests/unit/test_zowe_core.py +++ b/tests/unit/test_zowe_core.py @@ -10,7 +10,6 @@ from unittest import mock import commentjson -import keyring from jsonschema import SchemaError, ValidationError, validate from pyfakefs.fake_filesystem_unittest import TestCase from zowe.core_for_zowe_sdk import ( @@ -27,6 +26,7 @@ session_constants, ) from zowe.core_for_zowe_sdk.validators import validate_config_json +from zowe.secrets_for_zowe_sdk import keyring FIXTURES_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "fixtures") CWD = os.getcwd() @@ -222,7 +222,7 @@ def setUpCreds(self, file_path, secure_props): global SECURE_CONFIG_PROPS SECURE_CONFIG_PROPS = base64.b64encode((json.dumps(CRED_DICT)).encode()).decode() - @mock.patch("keyring.get_password", side_effect=keyring_get_password) + @mock.patch("zowe.secrets_for_zowe_sdk.keyring.get_password", side_effect=keyring_get_password) def test_autodiscovery_and_base_profile_loading(self, get_pass_func): """ Test loading of correct file by autodiscovering from current working directory @@ -257,7 +257,7 @@ def test_autodiscovery_and_base_profile_loading(self, get_pass_func): } self.assertEqual(props, expected_props) - @mock.patch("keyring.get_password", side_effect=keyring_get_password) + @mock.patch("zowe.secrets_for_zowe_sdk.keyring.get_password", side_effect=keyring_get_password) def test_custom_file_and_custom_profile_loading(self, get_pass_func): """ Test loading of correct file given a filename and directory, @@ -291,7 +291,7 @@ def test_custom_file_and_custom_profile_loading(self, get_pass_func): } self.assertEqual(props, expected_props) - @mock.patch("keyring.get_password", side_effect=keyring_get_password) + @mock.patch("zowe.secrets_for_zowe_sdk.keyring.get_password", side_effect=keyring_get_password) def test_custom_file_and_custom_profile_loading_with_nested_profile(self, get_pass_func): """ Test loading of correct file given a filename and directory, @@ -319,7 +319,7 @@ def test_custom_file_and_custom_profile_loading_with_nested_profile(self, get_pa expected_props = {"host": "example1.com", "rejectUnauthorized": True, "port": 443} self.assertEqual(props, expected_props) - @mock.patch("keyring.get_password", side_effect=keyring_get_password) + @mock.patch("zowe.secrets_for_zowe_sdk.keyring.get_password", side_effect=keyring_get_password) def test_profile_loading_with_user_overridden_properties(self, get_pass_func): """ Test overriding of properties from user config, @@ -355,7 +355,7 @@ def test_profile_loading_with_user_overridden_properties(self, get_pass_func): } self.assertEqual(props, expected_props) - @mock.patch("keyring.get_password", side_effect=keyring_get_password) + @mock.patch("zowe.secrets_for_zowe_sdk.keyring.get_password", side_effect=keyring_get_password) def test_profile_loading_exception(self, get_pass_func): """ Test correct exceptions are being thrown when a profile is @@ -374,7 +374,7 @@ def test_profile_loading_exception(self, get_pass_func): config_file = ConfigFile(name=self.custom_appname, type="team_config") props: dict = config_file.get_profile(profile_name="non_existent_profile", validate_schema=False) - @mock.patch("keyring.get_password", side_effect=keyring_get_password_exception) + @mock.patch("zowe.secrets_for_zowe_sdk.keyring.get_password", side_effect=keyring_get_password_exception) def test_secure_props_loading_warning(self, get_pass_func): """ Test correct warnings are being thrown when secure properties @@ -392,7 +392,7 @@ def test_secure_props_loading_warning(self, get_pass_func): prof_manager.config_dir = self.custom_dir props: dict = prof_manager.load("base", validate_schema=False) - @mock.patch("keyring.get_password", side_effect=keyring_get_password) + @mock.patch("zowe.secrets_for_zowe_sdk.keyring.get_password", side_effect=keyring_get_password) def test_profile_not_found_warning(self, get_pass_func): """ Test correct warnings are being thrown when profile is not found @@ -411,12 +411,13 @@ def test_profile_not_found_warning(self, get_pass_func): props: dict = prof_manager.load("non_existent_profile", validate_schema=False) @mock.patch("sys.platform", "win32") - @mock.patch("zowe.core_for_zowe_sdk.CredentialManager._retrieve_credential") + @mock.patch("zowe.core_for_zowe_sdk.CredentialManager._get_credential") def test_load_secure_props(self, retrieve_cred_func): """ Test loading secure_props from keyring or storage. """ service_name = constants["ZoweServiceName"] + account_name = constants["ZoweAccountName"] # Setup - copy profile to fake filesystem created by pyfakefs cwd_up_dir_path = os.path.dirname(CWD) cwd_up_file_path = os.path.join(cwd_up_dir_path, "zowe.config.json") @@ -426,20 +427,19 @@ def test_load_secure_props(self, retrieve_cred_func): cwd_up_file_path: {"profiles.base.properties.user": "user", "profiles.base.properties.password": "password"} } self.setUpCreds(cwd_up_file_path, credential) - base64_encoded_credential = base64.b64encode(commentjson.dumps(credential).encode()).decode() - encoded_credential = base64_encoded_credential.encode("utf-16le").decode() + encoded_credential = base64.b64encode(commentjson.dumps(credential).encode()).decode() retrieve_cred_func.return_value = encoded_credential # call the load_secure_props method credential_manager = CredentialManager() credential_manager.load_secure_props() - retrieve_cred_func.assert_called_once_with(service_name) + retrieve_cred_func.assert_called_once_with(service_name, account_name) # Verify the secure_props expected_secure_props = credential self.assertEqual(credential_manager.secure_props, expected_secure_props) @mock.patch("sys.platform", "win32") - @mock.patch("keyring.delete_password") + @mock.patch("zowe.secrets_for_zowe_sdk.keyring.delete_password") def test_delete_credential(self, delete_pass_func): """ Test the delete_credential method for deleting credentials from keyring. @@ -448,9 +448,9 @@ def test_delete_credential(self, delete_pass_func): def side_effect(*args, **kwargs): if side_effect.counter < 2: side_effect.counter += 1 - raise keyring.errors.PasswordDeleteError + return False else: - return None + return True side_effect.counter = 0 @@ -460,60 +460,54 @@ def side_effect(*args, **kwargs): service_name = constants["ZoweServiceName"] account_name = constants["ZoweAccountName"] # Delete the credential - credential_manager.delete_credential(service_name, account_name) + credential_manager._delete_credential(service_name, account_name) expected_calls = [ mock.call(service_name, account_name), - mock.call(f"{service_name}-1", f"{account_name}-1"), + mock.call(service_name, f"{account_name}-1"), ] delete_pass_func.assert_has_calls(expected_calls) @mock.patch("sys.platform", "win32") - @mock.patch("keyring.get_password", side_effect=["password", None, "part1", "part2\0", None]) + @mock.patch("zowe.secrets_for_zowe_sdk.keyring.get_password", side_effect=["password", None, "part1", "part2\0", None]) def test_retrieve_credential(self, get_pass_func): """ Test the _retrieve_credential method for retrieving credentials from keyring. """ credential_manager = CredentialManager() - service_name = f"{constants['ZoweServiceName']}/{constants['ZoweAccountName']}" # Scenario 1: Retrieve password directly - expected_password1 = "password".encode("utf-16le").decode() - expected_password1 = expected_password1[:-1] - retrieve_credential1 = credential_manager._retrieve_credential(constants["ZoweServiceName"]) + expected_password1 = "password" + retrieve_credential1 = credential_manager._get_credential(constants["ZoweServiceName"], constants["ZoweAccountName"]) self.assertEqual(retrieve_credential1, expected_password1) - get_pass_func.assert_called_with(service_name, constants["ZoweAccountName"]) + get_pass_func.assert_called_with(constants["ZoweServiceName"], constants["ZoweAccountName"]) # Scenario 2: Retrieve password in parts - expected_password2 = "part1part2".encode("utf-16le").decode() - retrieve_credential2 = credential_manager._retrieve_credential(constants["ZoweServiceName"]) - retrieve_credential2 = retrieve_credential2[:-1] + expected_password2 = "part1part2" + retrieve_credential2 = credential_manager._get_credential(constants["ZoweServiceName"], constants["ZoweAccountName"]) self.assertEqual(retrieve_credential2, expected_password2) - get_pass_func.assert_any_call(service_name, constants["ZoweAccountName"]) - get_pass_func.assert_any_call(f"{service_name}-1", f"{constants['ZoweAccountName']}-1") - get_pass_func.assert_any_call(f"{service_name}-2", f"{constants['ZoweAccountName']}-2") + get_pass_func.assert_any_call(constants["ZoweServiceName"], constants["ZoweAccountName"]) + get_pass_func.assert_any_call(constants["ZoweServiceName"], f"{constants['ZoweAccountName']}-1") + get_pass_func.assert_any_call(constants["ZoweServiceName"], f"{constants['ZoweAccountName']}-2") @mock.patch("sys.platform", "win32") - @mock.patch("keyring.get_password", side_effect=[None, None]) + @mock.patch("zowe.secrets_for_zowe_sdk.keyring.get_password", side_effect=[None, None]) def test_retrieve_credential_encoding_errors(self, get_pass_func): """ Test the _retrieve_credential method for handling encoding errors and None values. """ - service_name = f"{constants['ZoweServiceName']}/{constants['ZoweAccountName']}" - result = CredentialManager._retrieve_credential(constants["ZoweServiceName"]) + result = CredentialManager._get_credential(constants["ZoweServiceName"], constants["ZoweAccountName"]) self.assertIsNone(result) - get_pass_func.assert_called_with(f"{service_name}-1", f"{constants['ZoweAccountName']}-1") + get_pass_func.assert_called_with(constants["ZoweServiceName"], f"{constants['ZoweAccountName']}-1") @mock.patch("sys.platform", "win32") - @mock.patch("keyring.set_password") - @mock.patch("zowe.core_for_zowe_sdk.CredentialManager._retrieve_credential") - @mock.patch("zowe.core_for_zowe_sdk.CredentialManager.delete_credential") + @mock.patch("zowe.core_for_zowe_sdk.CredentialManager._set_credential") + @mock.patch("zowe.core_for_zowe_sdk.CredentialManager._get_credential") + @mock.patch("zowe.core_for_zowe_sdk.CredentialManager._delete_credential") def test_save_secure_props_normal_credential(self, delete_pass_func, retrieve_cred_func, set_pass_func): """ Test the save_secure_props method for saving credentials to keyring. """ - # Set up mock values and expected results - service_name = constants["ZoweServiceName"] + "/" + constants["ZoweAccountName"] # Setup - copy profile to fake filesystem created by pyfakefs cwd_up_dir_path = os.path.dirname(CWD) cwd_up_file_path = os.path.join(cwd_up_dir_path, "zowe.config.json") @@ -534,15 +528,13 @@ def test_save_secure_props_normal_credential(self, delete_pass_func, retrieve_cr # delete the existing credential delete_pass_func.return_value = None # Verify the keyring function call - set_pass_func.assert_called_once_with(service_name, constants["ZoweAccountName"], encoded_credential) + set_pass_func.assert_called_once_with(constants["ZoweServiceName"], constants["ZoweAccountName"], encoded_credential) @mock.patch("sys.platform", "win32") - @mock.patch("zowe.core_for_zowe_sdk.CredentialManager._retrieve_credential") - @mock.patch("keyring.set_password") - @mock.patch("zowe.core_for_zowe_sdk.CredentialManager.delete_credential") + @mock.patch("zowe.core_for_zowe_sdk.CredentialManager._get_credential") + @mock.patch("zowe.secrets_for_zowe_sdk.keyring.set_password") + @mock.patch("zowe.core_for_zowe_sdk.CredentialManager._delete_credential") def test_save_secure_props_exceed_limit(self, delete_pass_func, set_pass_func, retrieve_cred_func): - # Set up mock values and expected results - service_name = constants["ZoweServiceName"] + "/" + constants["ZoweAccountName"] # Setup - copy profile to fake filesystem created by pyfakefs cwd_up_dir_path = os.path.dirname(CWD) cwd_up_file_path = os.path.join(cwd_up_dir_path, "zowe.config.json") @@ -555,9 +547,8 @@ def test_save_secure_props_exceed_limit(self, delete_pass_func, set_pass_func, r } } self.setUpCreds(cwd_up_file_path, credential) - base64_encoded_credential = base64.b64encode(commentjson.dumps(credential).encode()).decode() - base64_encoded_credential += "\0" - encoded_credential = base64_encoded_credential.encode("utf-16le").decode() + encoded_credential = base64.b64encode(commentjson.dumps(credential).encode()).decode() + encoded_credential += "\0" retrieve_cred_func.return_value = encoded_credential CredentialManager.secure_props = credential @@ -569,16 +560,14 @@ def test_save_secure_props_exceed_limit(self, delete_pass_func, set_pass_func, r expected_calls = [] chunk_size = constants["WIN32_CRED_MAX_STRING_LENGTH"] chunks = [ - base64_encoded_credential[i : i + chunk_size] for i in range(0, len(base64_encoded_credential), chunk_size) + encoded_credential[i : i + chunk_size] for i in range(0, len(encoded_credential), chunk_size) ] for index, chunk in enumerate(chunks, start=1): field_name = f"{constants['ZoweAccountName']}-{index}" - service_names = f"{service_name}-{index}" - password = (chunk + "\0" * (len(chunk) % 2)).encode().decode("utf-16le") - expected_calls.append(mock.call(service_names, field_name, password)) + expected_calls.append(mock.call(constants["ZoweServiceName"], field_name, chunk)) set_pass_func.assert_has_calls(expected_calls) - @mock.patch("keyring.get_password", side_effect=keyring_get_password) + @mock.patch("zowe.secrets_for_zowe_sdk.keyring.get_password", side_effect=keyring_get_password) def test_profile_loading_with_valid_schema(self, get_pass_func): """ Test Validation, no error should be raised for valid schema @@ -602,7 +591,7 @@ def test_profile_loading_with_valid_schema(self, get_pass_func): prof_manager.config_dir = self.custom_dir props: dict = prof_manager.load(profile_name="zosmf") - @mock.patch("keyring.get_password", side_effect=keyring_get_password) + @mock.patch("zowe.secrets_for_zowe_sdk.keyring.get_password", side_effect=keyring_get_password) def test_profile_loading_with_invalid_schema(self, get_pass_func): """ Test Validation, no error should be raised for valid schema @@ -627,7 +616,7 @@ def test_profile_loading_with_invalid_schema(self, get_pass_func): prof_manager.config_dir = self.custom_dir props: dict = prof_manager.load(profile_name="zosmf", validate_schema=True) - @mock.patch("keyring.get_password", side_effect=keyring_get_password) + @mock.patch("zowe.secrets_for_zowe_sdk.keyring.get_password", side_effect=keyring_get_password) def test_profile_loading_with_invalid_schema_internet_URI(self, get_pass_func): """ Test Validation, no error should be raised for valid schema @@ -652,7 +641,7 @@ def test_profile_loading_with_invalid_schema_internet_URI(self, get_pass_func): prof_manager.config_dir = self.custom_dir props: dict = prof_manager.load(profile_name="zosmf", validate_schema=True) - @mock.patch("keyring.get_password", side_effect=keyring_get_password) + @mock.patch("zowe.secrets_for_zowe_sdk.keyring.get_password", side_effect=keyring_get_password) def test_profile_loading_with_env_variables(self, get_pass_func): """ Test loading of correct file given a filename and directory, @@ -786,7 +775,7 @@ def test_is_secure(self, mock_find_profile): @mock.patch("zowe.core_for_zowe_sdk.ConfigFile.get_profile_name_from_path") @mock.patch("zowe.core_for_zowe_sdk.ConfigFile.find_profile") @mock.patch("zowe.core_for_zowe_sdk.ConfigFile._ConfigFile__is_secure") - @mock.patch("keyring.get_password", side_effect=keyring_get_password) + @mock.patch("zowe.secrets_for_zowe_sdk.keyring.get_password", side_effect=keyring_get_password) def test_config_file_set_property(self, get_pass_func, mock_is_secure, mock_find_profile, mock_get_profile_name): """ Test that set_property calls the __is_secure, find_profile and get_profile_name_from_path methods. @@ -826,7 +815,7 @@ def test_get_profile_path_from_name(self): profile_path_1 = config_file.get_profile_path_from_name("lpar1.zosmf") self.assertEqual(profile_path_1, "profiles.lpar1.profiles.zosmf") - @mock.patch("keyring.get_password", side_effect=keyring_get_password) + @mock.patch("zowe.secrets_for_zowe_sdk.keyring.get_password", side_effect=keyring_get_password) def test_config_file_set_profile_and_save(self, get_pass_func): """ Test the set_profile method.