From a85090cd3e6c767b104bcf7e8eaf0998ce5fcdf7 Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Mon, 30 Oct 2023 15:30:47 -0400 Subject: [PATCH 01/45] WIP Start adding secrets sdk Co-authored-by: Trae Yelovich Signed-off-by: Timothy Johnson --- .github/workflows/secrets-sdk.yml | 124 +++ src/secrets/CHANGELOG.md | 31 + src/secrets/LICENSE | 277 ++++++ src/secrets/OVERVIEW.md | 48 + src/secrets/README.md | 49 + src/secrets/package.json | 51 + src/secrets/scripts/configure-cross.sh | 30 + src/secrets/scripts/linux-test.sh | 4 + src/secrets/scripts/prebuildCheck.js | 22 + src/secrets/scripts/prebuildify.sh | 8 + src/secrets/src/index.ts | 12 + src/secrets/src/keyring-test/.gitignore | 72 ++ src/secrets/src/keyring-test/Cargo.lock | 905 ++++++++++++++++++ src/secrets/src/keyring-test/Cargo.toml | 13 + src/secrets/src/keyring-test/pyproject.toml | 16 + src/secrets/src/keyring-test/src/lib.rs | 56 ++ src/secrets/src/keyring/.cargo/config.toml | 2 + src/secrets/src/keyring/.npmignore | 13 + src/secrets/src/keyring/Cargo.lock | 737 ++++++++++++++ src/secrets/src/keyring/Cargo.toml | 47 + src/secrets/src/keyring/Cross.toml | 37 + src/secrets/src/keyring/EXTENDERS.md | 140 +++ src/secrets/src/keyring/README.md | 177 ++++ .../src/keyring/__test__/index.spec.mjs | 312 ++++++ src/secrets/src/keyring/build.rs | 5 + src/secrets/src/keyring/index.d.ts | 14 + src/secrets/src/keyring/index.js | 66 ++ src/secrets/src/keyring/napi.json | 21 + src/secrets/src/keyring/rustfmt.toml | 2 + src/secrets/src/keyring/src/lib.rs | 35 + src/secrets/src/keyring/src/os/error.rs | 41 + src/secrets/src/keyring/src/os/mac/error.rs | 66 ++ src/secrets/src/keyring/src/os/mac/ffi.rs | 118 +++ .../src/keyring/src/os/mac/keychain.rs | 114 +++ .../src/keyring/src/os/mac/keychain_item.rs | 56 ++ .../src/keyring/src/os/mac/keychain_search.rs | 249 +++++ src/secrets/src/keyring/src/os/mac/misc.rs | 21 + src/secrets/src/keyring/src/os/mac/mod.rs | 160 ++++ src/secrets/src/keyring/src/os/mod.rs | 14 + src/secrets/src/keyring/src/os/unix.rs | 221 +++++ src/secrets/src/keyring/src/os/win.rs | 340 +++++++ src/secrets/src/keyring/src/workers.rs | 153 +++ src/secrets/tsconfig.json | 15 + 43 files changed, 4894 insertions(+) create mode 100644 .github/workflows/secrets-sdk.yml create mode 100644 src/secrets/CHANGELOG.md create mode 100644 src/secrets/LICENSE create mode 100644 src/secrets/OVERVIEW.md create mode 100644 src/secrets/README.md create mode 100644 src/secrets/package.json create mode 100644 src/secrets/scripts/configure-cross.sh create mode 100644 src/secrets/scripts/linux-test.sh create mode 100644 src/secrets/scripts/prebuildCheck.js create mode 100644 src/secrets/scripts/prebuildify.sh create mode 100644 src/secrets/src/index.ts create mode 100644 src/secrets/src/keyring-test/.gitignore create mode 100644 src/secrets/src/keyring-test/Cargo.lock create mode 100644 src/secrets/src/keyring-test/Cargo.toml create mode 100644 src/secrets/src/keyring-test/pyproject.toml create mode 100644 src/secrets/src/keyring-test/src/lib.rs create mode 100644 src/secrets/src/keyring/.cargo/config.toml create mode 100644 src/secrets/src/keyring/.npmignore create mode 100644 src/secrets/src/keyring/Cargo.lock create mode 100644 src/secrets/src/keyring/Cargo.toml create mode 100644 src/secrets/src/keyring/Cross.toml create mode 100644 src/secrets/src/keyring/EXTENDERS.md create mode 100644 src/secrets/src/keyring/README.md create mode 100644 src/secrets/src/keyring/__test__/index.spec.mjs create mode 100644 src/secrets/src/keyring/build.rs create mode 100644 src/secrets/src/keyring/index.d.ts create mode 100644 src/secrets/src/keyring/index.js create mode 100644 src/secrets/src/keyring/napi.json create mode 100644 src/secrets/src/keyring/rustfmt.toml create mode 100644 src/secrets/src/keyring/src/lib.rs create mode 100644 src/secrets/src/keyring/src/os/error.rs create mode 100644 src/secrets/src/keyring/src/os/mac/error.rs create mode 100644 src/secrets/src/keyring/src/os/mac/ffi.rs create mode 100644 src/secrets/src/keyring/src/os/mac/keychain.rs create mode 100644 src/secrets/src/keyring/src/os/mac/keychain_item.rs create mode 100644 src/secrets/src/keyring/src/os/mac/keychain_search.rs create mode 100644 src/secrets/src/keyring/src/os/mac/misc.rs create mode 100644 src/secrets/src/keyring/src/os/mac/mod.rs create mode 100644 src/secrets/src/keyring/src/os/mod.rs create mode 100644 src/secrets/src/keyring/src/os/unix.rs create mode 100644 src/secrets/src/keyring/src/os/win.rs create mode 100644 src/secrets/src/keyring/src/workers.rs create mode 100644 src/secrets/tsconfig.json diff --git a/.github/workflows/secrets-sdk.yml b/.github/workflows/secrets-sdk.yml new file mode 100644 index 00000000..830638ed --- /dev/null +++ b/.github/workflows/secrets-sdk.yml @@ -0,0 +1,124 @@ +# This file is autogenerated by maturin v1.3.1 +# To update, run +# +# maturin generate-ci github +# +name: CI + +on: + push: + branches: + - main + - master + tags: + - "*" + pull_request: + workflow_dispatch: + +defaults: + run: + working-directory: packages/secrets + +permissions: + contents: read + +jobs: + linux: + runs-on: ubuntu-latest + strategy: + matrix: + target: [x86_64, x86, aarch64, armv7, s390x, ppc64le] + 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 --find-interpreter + sccache: "true" + manylinux: auto + - name: Upload wheels + uses: actions/upload-artifact@v3 + with: + name: wheels + path: dist + + windows: + runs-on: windows-latest + strategy: + matrix: + target: [x64, x86] + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: "3.10" + architecture: ${{ matrix.target }} + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + target: ${{ matrix.target }} + args: --release --out dist --find-interpreter + sccache: "true" + - name: Upload wheels + uses: actions/upload-artifact@v3 + with: + name: wheels + path: dist + + macos: + runs-on: macos-latest + strategy: + matrix: + target: [x86_64, aarch64] + 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 --find-interpreter + sccache: "true" + - name: Upload wheels + uses: actions/upload-artifact@v3 + with: + name: wheels + path: 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 + - name: Upload sdist + uses: actions/upload-artifact@v3 + with: + name: wheels + path: 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_API_TOKEN }} + with: + command: upload + args: --non-interactive --skip-existing * diff --git a/src/secrets/CHANGELOG.md b/src/secrets/CHANGELOG.md new file mode 100644 index 00000000..054df8ce --- /dev/null +++ b/src/secrets/CHANGELOG.md @@ -0,0 +1,31 @@ +# Change Log + +All notable changes to the Zowe Secrets SDK package will be documented in this file. + +## `7.18.6` + +- BugFix: Use `core-foundation-rs` instead of `security-framework` for macOS logic, as `security-framework` is now archived. [#1802](https://github.com/zowe/zowe-cli/issues/1802) +- BugFix: Resolve bug where `findCredentials` scenarios with one match causes a segmentation fault on Linux. + +## `7.18.5` + +- BugFix: Enable `KeyringError::Library` enum variant to fix building on FreeBSD targets. + +## `7.18.4` + +- BugFix: Separated module resolution logic during installation; added more error handling to provide a more graceful installation process. +- BugFix: Add static CRT when compiling Windows builds. +- Added OVERVIEW document to package: provides context on the Secrets SDK transition and how it affects Zowe CLI and Zowe Explorer. + +## `7.18.2` + +- BugFix: Adds logic to allow the `keyring` module to locate the current package directory for the `prebuilds/` folder. + +## `7.18.1` + +- Added README to package w/ description, instructions and examples of using the `keyring` module. + +## `7.18.0` + +- Initial release. +- `keyring` module added for interacting with OS-specific keyring/credential vaults. See [src/keyring](src/keyring/README.md) for information on this native module and how it can be used. \ No newline at end of file diff --git a/src/secrets/LICENSE b/src/secrets/LICENSE new file mode 100644 index 00000000..d3087e4c --- /dev/null +++ b/src/secrets/LICENSE @@ -0,0 +1,277 @@ +Eclipse Public License - v 2.0 + + THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE + PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION + OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + +1. DEFINITIONS + +"Contribution" means: + + a) in the case of the initial Contributor, the initial content + Distributed under this Agreement, and + + b) in the case of each subsequent Contributor: + i) changes to the Program, and + ii) additions to the Program; + where such changes and/or additions to the Program originate from + and are Distributed by that particular Contributor. A Contribution + "originates" from a Contributor if it was added to the Program by + such Contributor itself or anyone acting on such Contributor's behalf. + Contributions do not include changes or additions to the Program that + are not Modified Works. + +"Contributor" means any person or entity that Distributes the Program. + +"Licensed Patents" mean patent claims licensable by a Contributor which +are necessarily infringed by the use or sale of its Contribution alone +or when combined with the Program. + +"Program" means the Contributions Distributed in accordance with this +Agreement. + +"Recipient" means anyone who receives the Program under this Agreement +or any Secondary License (as applicable), including Contributors. + +"Derivative Works" shall mean any work, whether in Source Code or other +form, that is based on (or derived from) the Program and for which the +editorial revisions, annotations, elaborations, or other modifications +represent, as a whole, an original work of authorship. + +"Modified Works" shall mean any work in Source Code or other form that +results from an addition to, deletion from, or modification of the +contents of the Program, including, for purposes of clarity any new file +in Source Code form that contains any contents of the Program. Modified +Works shall not include works that contain only declarations, +interfaces, types, classes, structures, or files of the Program solely +in each case in order to link to, bind by name, or subclass the Program +or Modified Works thereof. + +"Distribute" means the acts of a) distributing or b) making available +in any manner that enables the transfer of a copy. + +"Source Code" means the form of a Program preferred for making +modifications, including but not limited to software source code, +documentation source, and configuration files. + +"Secondary License" means either the GNU General Public License, +Version 2.0, or any later versions of that license, including any +exceptions or additional permissions as identified by the initial +Contributor. + +2. GRANT OF RIGHTS + + a) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free copyright + license to reproduce, prepare Derivative Works of, publicly display, + publicly perform, Distribute and sublicense the Contribution of such + Contributor, if any, and such Derivative Works. + + b) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free patent + license under Licensed Patents to make, use, sell, offer to sell, + import and otherwise transfer the Contribution of such Contributor, + if any, in Source Code or other form. This patent license shall + apply to the combination of the Contribution and the Program if, at + the time the Contribution is added by the Contributor, such addition + of the Contribution causes such combination to be covered by the + Licensed Patents. The patent license shall not apply to any other + combinations which include the Contribution. No hardware per se is + licensed hereunder. + + c) Recipient understands that although each Contributor grants the + licenses to its Contributions set forth herein, no assurances are + provided by any Contributor that the Program does not infringe the + patent or other intellectual property rights of any other entity. + Each Contributor disclaims any liability to Recipient for claims + brought by any other entity based on infringement of intellectual + property rights or otherwise. As a condition to exercising the + rights and licenses granted hereunder, each Recipient hereby + assumes sole responsibility to secure any other intellectual + property rights needed, if any. For example, if a third party + patent license is required to allow Recipient to Distribute the + Program, it is Recipient's responsibility to acquire that license + before distributing the Program. + + d) Each Contributor represents that to its knowledge it has + sufficient copyright rights in its Contribution, if any, to grant + the copyright license set forth in this Agreement. + + e) Notwithstanding the terms of any Secondary License, no + Contributor makes additional grants to any Recipient (other than + those set forth in this Agreement) as a result of such Recipient's + receipt of the Program under the terms of a Secondary License + (if permitted under the terms of Section 3). + +3. REQUIREMENTS + +3.1 If a Contributor Distributes the Program in any form, then: + + a) the Program must also be made available as Source Code, in + accordance with section 3.2, and the Contributor must accompany + the Program with a statement that the Source Code for the Program + is available under this Agreement, and informs Recipients how to + obtain it in a reasonable manner on or through a medium customarily + used for software exchange; and + + b) the Contributor may Distribute the Program under a license + different than this Agreement, provided that such license: + i) effectively disclaims on behalf of all other Contributors all + warranties and conditions, express and implied, including + warranties or conditions of title and non-infringement, and + implied warranties or conditions of merchantability and fitness + for a particular purpose; + + ii) effectively excludes on behalf of all other Contributors all + liability for damages, including direct, indirect, special, + incidental and consequential damages, such as lost profits; + + iii) does not attempt to limit or alter the recipients' rights + in the Source Code under section 3.2; and + + iv) requires any subsequent distribution of the Program by any + party to be under a license that satisfies the requirements + of this section 3. + +3.2 When the Program is Distributed as Source Code: + + a) it must be made available under this Agreement, or if the + Program (i) is combined with other material in a separate file or + files made available under a Secondary License, and (ii) the initial + Contributor attached to the Source Code the notice described in + Exhibit A of this Agreement, then the Program may be made available + under the terms of such Secondary Licenses, and + + b) a copy of this Agreement must be included with each copy of + the Program. + +3.3 Contributors may not remove or alter any copyright, patent, +trademark, attribution notices, disclaimers of warranty, or limitations +of liability ("notices") contained within the Program from any copy of +the Program which they Distribute, provided that Contributors may add +their own appropriate notices. + +4. COMMERCIAL DISTRIBUTION + +Commercial distributors of software may accept certain responsibilities +with respect to end users, business partners and the like. While this +license is intended to facilitate the commercial use of the Program, +the Contributor who includes the Program in a commercial product +offering should do so in a manner which does not create potential +liability for other Contributors. Therefore, if a Contributor includes +the Program in a commercial product offering, such Contributor +("Commercial Contributor") hereby agrees to defend and indemnify every +other Contributor ("Indemnified Contributor") against any losses, +damages and costs (collectively "Losses") arising from claims, lawsuits +and other legal actions brought by a third party against the Indemnified +Contributor to the extent caused by the acts or omissions of such +Commercial Contributor in connection with its distribution of the Program +in a commercial product offering. The obligations in this section do not +apply to any claims or Losses relating to any actual or alleged +intellectual property infringement. In order to qualify, an Indemnified +Contributor must: a) promptly notify the Commercial Contributor in +writing of such claim, and b) allow the Commercial Contributor to control, +and cooperate with the Commercial Contributor in, the defense and any +related settlement negotiations. The Indemnified Contributor may +participate in any such claim at its own expense. + +For example, a Contributor might include the Program in a commercial +product offering, Product X. That Contributor is then a Commercial +Contributor. If that Commercial Contributor then makes performance +claims, or offers warranties related to Product X, those performance +claims and warranties are such Commercial Contributor's responsibility +alone. Under this section, the Commercial Contributor would have to +defend claims against the other Contributors related to those performance +claims and warranties, and if a court requires any other Contributor to +pay any damages as a result, the Commercial Contributor must pay +those damages. + +5. NO WARRANTY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT +PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS" +BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR +IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF +TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR +PURPOSE. Each Recipient is solely responsible for determining the +appropriateness of using and distributing the Program and assumes all +risks associated with its exercise of rights under this Agreement, +including but not limited to the risks and costs of program errors, +compliance with applicable laws, damage to or loss of data, programs +or equipment, and unavailability or interruption of operations. + +6. DISCLAIMER OF LIABILITY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT +PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS +SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST +PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE +EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + +7. GENERAL + +If any provision of this Agreement is invalid or unenforceable under +applicable law, it shall not affect the validity or enforceability of +the remainder of the terms of this Agreement, and without further +action by the parties hereto, such provision shall be reformed to the +minimum extent necessary to make such provision valid and enforceable. + +If Recipient institutes patent litigation against any entity +(including a cross-claim or counterclaim in a lawsuit) alleging that the +Program itself (excluding combinations of the Program with other software +or hardware) infringes such Recipient's patent(s), then such Recipient's +rights granted under Section 2(b) shall terminate as of the date such +litigation is filed. + +All Recipient's rights under this Agreement shall terminate if it +fails to comply with any of the material terms or conditions of this +Agreement and does not cure such failure in a reasonable period of +time after becoming aware of such noncompliance. If all Recipient's +rights under this Agreement terminate, Recipient agrees to cease use +and distribution of the Program as soon as reasonably practicable. +However, Recipient's obligations under this Agreement and any licenses +granted by Recipient relating to the Program shall continue and survive. + +Everyone is permitted to copy and distribute copies of this Agreement, +but in order to avoid inconsistency the Agreement is copyrighted and +may only be modified in the following manner. The Agreement Steward +reserves the right to publish new versions (including revisions) of +this Agreement from time to time. No one other than the Agreement +Steward has the right to modify this Agreement. The Eclipse Foundation +is the initial Agreement Steward. The Eclipse Foundation may assign the +responsibility to serve as the Agreement Steward to a suitable separate +entity. Each new version of the Agreement will be given a distinguishing +version number. The Program (including Contributions) may always be +Distributed subject to the version of the Agreement under which it was +received. In addition, after a new version of the Agreement is published, +Contributor may elect to Distribute the Program (including its +Contributions) under the new version. + +Except as expressly stated in Sections 2(a) and 2(b) above, Recipient +receives no rights or licenses to the intellectual property of any +Contributor under this Agreement, whether expressly, by implication, +estoppel or otherwise. All rights in the Program not expressly granted +under this Agreement are reserved. Nothing in this Agreement is intended +to be enforceable by any entity that is not a Contributor or Recipient. +No third-party beneficiary rights are created under this Agreement. + +Exhibit A - Form of Secondary Licenses Notice + +"This Source Code may also be made available under the following +Secondary Licenses when the conditions for such availability set forth +in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), +version(s), and exceptions or additional permissions here}." + + Simply including a copy of this Agreement, including this Exhibit A + is not sufficient to license the Source Code under Secondary Licenses. + + If it is not possible or desirable to put the notice in a particular + file, then You may include the notice in a location (such as a LICENSE + file in a relevant directory) where a recipient would be likely to + look for such a notice. + + You may add additional accurate notices of copyright ownership. diff --git a/src/secrets/OVERVIEW.md b/src/secrets/OVERVIEW.md new file mode 100644 index 00000000..33968e61 --- /dev/null +++ b/src/secrets/OVERVIEW.md @@ -0,0 +1,48 @@ +# Secrets SDK + +Secrets for Zowe SDK is a new development package used in the following Zowe releases: +- Zowe CLI + - v7.18.0 and above from the NPM package + - Bundle v2.11.0 and above from [Zowe.org](https://www.zowe.org/download#download-v2) +- Zowe Explorer and Zowe Explorer API + - v2.10.0 and above (v2 LTS) + - v1.22.5 and above (v1 LTS) + +The `keyring` module in this package replaces node-keytar. Node-keytar was developed by Atom, and is no longer maintained as of December 15th, 2022. + +## Impact on End Users + +### Zowe CLI + +CLI users should not be affected by this change. It is intended to be a drop-in replacement for node-keytar, so existing credentials will still be accessible. + +### Zowe Explorer + +- **Most** Zowe Explorer users should not be affected by this change. +- Some Zowe Explorer users may be affected by this change. Specifically, users connecting over remote environments with Zowe Explorer are required to re-enter credentials. + - Since keytar has been removed from VS Code for the next release, Zowe Explorer cannot access the local credential vault while the extension is running through a remote server. + - "Remote environments" include: + - Remote SSH + - Remote Tunnel + - Remote Docker + - [Click here](https://github.com/zowe/vscode-extension-for-zowe/wiki/Usage-in-Remote-Environments) for more information. + +## Impact on Extenders + +**CAUTION:** Regardless of whether you extend Zowe CLI or Zowe Explorer, developers that directly reference `keytar` as a dependency or dev dependency must use the new `keyring` module in `@zowe/secrets-for-zowe-sdk`. + +### Zowe CLI + +- Developers can continue using the credential manager utilities from Imperative. +- Developers that indirectly use Zowe CLI to store and load secure credentials are not affected. + +### Zowe Explorer + +- Extenders that leverage Zowe Explorer API and Webpack (or another bundler) are required to update their extensions to provide prebuilds for the Secrets SDK. + - This involves providing a folder named `prebuilds` with the Secrets SDK binaries alongside their extension root directory (same level as `package.json`). + - [Click here](https://github.com/zowe/zowe-cli/blob/master/packages/secrets/src/keyring/EXTENDERS.md#webpackingbundling-alongside-your-project) for more information on this process. +- Extenders that do not use a bundler can continue using the credential manager utilities from Zowe Explorer API. These extensions are not affected by this change. + +--- + +For more information on how to use the Secrets SDK, visit the [README for the `keyring` module](/packages/secrets/src/keyring/README.md). \ No newline at end of file diff --git a/src/secrets/README.md b/src/secrets/README.md new file mode 100644 index 00000000..70eac693 --- /dev/null +++ b/src/secrets/README.md @@ -0,0 +1,49 @@ +# Secrets Package + +Contains APIs for secure credential management. + +## `keyring` API Examples + +Developers that reference the dependency `keytar` directly in their code need to use the new `keyring` module from this package. + +Use the `keyring` module in the same fashion as `keytar`. + +### Storing and loading credentials + +```js +const { keyring } = require("@zowe/secrets-for-zowe-sdk"); +await keyring.setPassword("ServiceName", "AccountName", "SomePassword"); + +const password = await keyring.getPassword("ServiceName", "AccountName"); +// password should equal "SomePassword" +``` + +### Finding a credential + +```js +const { keyring } = require("@zowe/secrets-for-zowe-sdk"); +const password = await keyring.findPassword("ServiceName/AccountName"); +// password should equal "SomePassword" +``` + +### Finding all credentials matching service + +```js +const { keyring } = require("@zowe/secrets-for-zowe-sdk"); +const matchingCredentials = await keyring.findCredentials("ServiceName"); +// returns: +// [ +// { account: "AccountName", password: "SomePassword" }, +// ... +// ] +``` + +### Deleting a credential + +```js +const { keyring } = require("@zowe/secrets-for-zowe-sdk"); +const wasDeleted = await keyring.deletePassword("ServiceName", "AccountName"); +// wasDeleted should be true; ServiceName/AccountName removed from credential vault +``` + +For more detailed information, see [src/keyring/EXTENDERS.md](/packages/secrets/src/keyring/EXTENDERS.md). \ No newline at end of file diff --git a/src/secrets/package.json b/src/secrets/package.json new file mode 100644 index 00000000..d265ccde --- /dev/null +++ b/src/secrets/package.json @@ -0,0 +1,51 @@ +{ + "name": "@zowe/secrets-for-zowe-sdk", + "description": "Credential management facilities for Imperative, Zowe CLI, and extenders.", + "repository": "https://github.com/zowe/zowe-cli.git", + "author": "Zowe", + "version": "7.18.6", + "homepage": "https://github.com/zowe/zowe-cli/tree/master/packages/secrets#readme", + "bugs": { + "url": "https://github.com/zowe/zowe-cli/issues" + }, + "private": true, + "main": "lib/index.js", + "types": "lib/index.d.ts", + "files": [ + "lib", + "prebuilds/*.node", + "src/keyring", + "src/keyring/.cargo", + "index.d.ts", + "index.js", + "scripts/*.js" + ], + "publishConfig": { + "registry": "https://zowe.jfrog.io/zowe/api/npm/npm-local-release/" + }, + "license": "EPL-2.0", + "devDependencies": { + "@napi-rs/cli": "^2.16.2", + "@types/node": "^14.18.28", + "ava": "^4.3.3", + "typescript": "^4.0.0" + }, + "ava": { + "timeout": "3m" + }, + "engines": { + "node": ">= 14" + }, + "scripts": { + "artifacts": "napi artifacts", + "build": "npm run build:ts && cd src/keyring && napi build --config napi.json --js false --platform --release", + "build:debug": "cd src/keyring && napi build --config napi.json --js false --platform", + "build:ts": "tsc --pretty", + "install": "node scripts/prebuildCheck.js || npm run rebuild", + "prepack": "node ../../scripts/prepareLicenses.js", + "prepublishOnly": "bash scripts/prebuildify.sh", + "rebuild": "npx --yes --package=@napi-rs/cli@2.16.2 -- napi build --config src/keyring/napi.json --cargo-cwd src/keyring --platform --release --js=false src/keyring", + "test": "ava", + "version": "napi version" + } +} diff --git a/src/secrets/scripts/configure-cross.sh b/src/secrets/scripts/configure-cross.sh new file mode 100644 index 00000000..a1263a8b --- /dev/null +++ b/src/secrets/scripts/configure-cross.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +# Set environment variables needed for cross-compilation in current shell +set_env() { + export PKG_CONFIG_SYSROOT_DIR="${CHROOT:-/}" + export RUSTFLAGS="-L $CHROOT$1 $RUSTFLAGS" + export PKG_CONFIG_PATH="$CHROOT$1/pkgconfig" +} + +case "$1" in + "aarch64-unknown-linux-gnu") + set_env "/usr/lib/aarch64-linux-gnu" + ;; + "aarch64-unknown-linux-musl") + CHROOT="/aarch64-linux-musl-cross" + RUSTFLAGS="-C linker=$CHROOT/bin/aarch64-linux-musl-gcc" + set_env "/usr/lib" + ;; + "armv7-unknown-linux-gnueabihf") + set_env "/usr/lib/arm-linux-gnueabihf" + ;; + "i686-unknown-linux-gnu") + set_env "/usr/lib/i386-linux-gnu" + ;; + "x86_64-unknown-linux-gnu") + set_env "/usr/lib/x86_64-linux-gnu" + ;; + *) + ;; +esac diff --git a/src/secrets/scripts/linux-test.sh b/src/secrets/scripts/linux-test.sh new file mode 100644 index 00000000..3af4397f --- /dev/null +++ b/src/secrets/scripts/linux-test.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +rm -f $HOME/.local/share/keyrings/* +echo -n "test" | gnome-keyring-daemon --unlock +yarn test diff --git a/src/secrets/scripts/prebuildCheck.js b/src/secrets/scripts/prebuildCheck.js new file mode 100644 index 00000000..5410f65e --- /dev/null +++ b/src/secrets/scripts/prebuildCheck.js @@ -0,0 +1,22 @@ +/* +* This program and the accompanying materials are made available under the terms of the +* Eclipse Public License v2.0 which accompanies this distribution, and is available at +* https://www.eclipse.org/legal/epl-v20.html +* +* SPDX-License-Identifier: EPL-2.0 +* +* Copyright Contributors to the Zowe Project. +* +*/ + +const { join } = require("path"); + +try { + require(join("..", "lib", "index.js")); +} catch (err) { + if (err.code === "ERR_MODULE_NOT_FOUND" || err.code === "MODULE_NOT_FOUND") { + throw new Error(`Unable to find prebuilds for Secrets SDK keyring module: ${err.message}`); + } else { + console.error(err.message); + } +} \ No newline at end of file diff --git a/src/secrets/scripts/prebuildify.sh b/src/secrets/scripts/prebuildify.sh new file mode 100644 index 00000000..9342d770 --- /dev/null +++ b/src/secrets/scripts/prebuildify.sh @@ -0,0 +1,8 @@ +#!/bin/bash +set -ex +rm -rf prebuilds && mkdir -p prebuilds +SECRETS_BRANCH="${SECRETS_BRANCH:-master}" +SECRETS_WORKFLOW_ID=$(gh run list -b $SECRETS_BRANCH --limit 1 --status success --workflow "Secrets SDK CI" --json databaseId --jq ".[0].databaseId") +echo "Downloading Secrets SDK prebuilds from $SECRETS_BRANCH branch..." +gh run download $SECRETS_WORKFLOW_ID --dir prebuilds --pattern "bindings-*" +mv prebuilds/*/* prebuilds && rm -r prebuilds/*/ \ No newline at end of file diff --git a/src/secrets/src/index.ts b/src/secrets/src/index.ts new file mode 100644 index 00000000..a4287b3f --- /dev/null +++ b/src/secrets/src/index.ts @@ -0,0 +1,12 @@ +/* +* This program and the accompanying materials are made available under the terms of the +* Eclipse Public License v2.0 which accompanies this distribution, and is available at +* https://www.eclipse.org/legal/epl-v20.html +* +* SPDX-License-Identifier: EPL-2.0 +* +* Copyright Contributors to the Zowe Project. +* +*/ + +export * as keyring from "../src/keyring"; diff --git a/src/secrets/src/keyring-test/.gitignore b/src/secrets/src/keyring-test/.gitignore new file mode 100644 index 00000000..c8f04429 --- /dev/null +++ b/src/secrets/src/keyring-test/.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/src/keyring-test/Cargo.lock b/src/secrets/src/keyring-test/Cargo.lock new file mode 100644 index 00000000..2d2672ed --- /dev/null +++ b/src/secrets/src/keyring-test/Cargo.lock @@ -0,0 +1,905 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[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 = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[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 = "ctor" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37e366bff8cd32dd8754b0991fb66b279dc48f598c3a18914852a6673deef583" +dependencies = [ + "quote", + "syn 2.0.38", +] + +[[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 = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa799dd5ed20a7e349f3b4639aa80d74549c81716d9ec4f994c9b5815598306" + +[[package]] +name = "keyring" +version = "1.0.0" +dependencies = [ + "cfg-if", + "core-foundation", + "core-foundation-sys", + "gio", + "glib", + "glib-sys", + "libsecret", + "libsecret-sys", + "napi", + "napi-build", + "napi-derive", + "thiserror", + "windows-sys", +] + +[[package]] +name = "keyring-test" +version = "0.1.0" +dependencies = [ + "keyring", + "pyo3", +] + +[[package]] +name = "libc" +version = "0.2.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[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 = "napi" +version = "2.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd063c93b900149304e3ba96ce5bf210cd4f81ef5eb80ded0d100df3e85a3ac0" +dependencies = [ + "bitflags 2.4.1", + "ctor", + "napi-derive", + "napi-sys", + "once_cell", +] + +[[package]] +name = "napi-build" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "882a73d9ef23e8dc2ebbffb6a6ae2ef467c0f18ac10711e4cc59c5485d41df0e" + +[[package]] +name = "napi-derive" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da1c6a8fa84d549aa8708fcd062372bf8ec6e849de39016ab921067d21bde367" +dependencies = [ + "cfg-if", + "convert_case", + "napi-derive-backend", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "napi-derive-backend" +version = "1.0.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20bbc7c69168d06a848f925ec5f0e0997f98e8c8d4f2cc30157f0da51c009e17" +dependencies = [ + "convert_case", + "once_cell", + "proc-macro2", + "quote", + "regex", + "semver", + "syn 1.0.109", +] + +[[package]] +name = "napi-sys" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "166b5ef52a3ab5575047a9fe8d4a030cdd0f63c96f071cd6907674453b07bae3" +dependencies = [ + "libloading", +] + +[[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.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e681a6cfdc4adcc93b4d3cf993749a4552018ee0a9b65fc0ccfad74352c72a38" +dependencies = [ + "cfg-if", + "indoc", + "libc", + "memoffset", + "parking_lot", + "pyo3-build-config", + "pyo3-ffi", + "pyo3-macros", + "unindent", +] + +[[package]] +name = "pyo3-build-config" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "076c73d0bc438f7a4ef6fdd0c3bb4732149136abd952b110ac93e4edb13a6ba5" +dependencies = [ + "once_cell", + "target-lexicon", +] + +[[package]] +name = "pyo3-ffi" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e53cee42e77ebe256066ba8aa77eff722b3bb91f3419177cf4cd0f304d3284d9" +dependencies = [ + "libc", + "pyo3-build-config", +] + +[[package]] +name = "pyo3-macros" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfeb4c99597e136528c6dd7d5e3de5434d1ceaf487436a3f03b2d56b6fc9efd1" +dependencies = [ + "proc-macro2", + "pyo3-macros-backend", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "947dc12175c254889edc0c02e399476c2f652b4b9ebd123aa655c224de259536" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[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 = "regex" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" + +[[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", + "quote", + "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 = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + +[[package]] +name = "unindent" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1766d682d402817b5ac4490b3c3002d91dfa0d22812f341609f97b08757359c" + +[[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/src/keyring-test/Cargo.toml b/src/secrets/src/keyring-test/Cargo.toml new file mode 100644 index 00000000..65bbe461 --- /dev/null +++ b/src/secrets/src/keyring-test/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "keyring-test" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +name = "keyring_test" +crate-type = ["cdylib"] + +[dependencies] +pyo3 = "0.19.0" +keyring = { path = "../keyring" } diff --git a/src/secrets/src/keyring-test/pyproject.toml b/src/secrets/src/keyring-test/pyproject.toml new file mode 100644 index 00000000..6bb30ff7 --- /dev/null +++ b/src/secrets/src/keyring-test/pyproject.toml @@ -0,0 +1,16 @@ +[build-system] +requires = ["maturin>=1.3,<2.0"] +build-backend = "maturin" + +[project] +name = "keyring-test" +requires-python = ">=3.8" +classifiers = [ + "Programming Language :: Rust", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", +] +dynamic = ["version"] + +[tool.maturin] +features = ["pyo3/extension-module"] diff --git a/src/secrets/src/keyring-test/src/lib.rs b/src/secrets/src/keyring-test/src/lib.rs new file mode 100644 index 00000000..858ab70d --- /dev/null +++ b/src/secrets/src/keyring-test/src/lib.rs @@ -0,0 +1,56 @@ +use pyo3::prelude::*; + +extern crate keyring; +use keyring::os::*; + +#[pyfunction] +fn py_set_password(service: &str, account: &str, password: &str) -> PyResult<()> { + match set_password(service, account, password) { + Ok(_) => Ok(()), + Err(e) => Err(e), + } +} + +#[pyfunction] +fn py_get_password(service: &str, account: &str) -> PyResult { + match get_password(service, account) { + Ok(_) => Ok(()), + Err(e) => Err(e), + } +} + +#[pyfunction] +fn py_delete_password(service: &str, account: &str) -> PyResult { + match delete_password(service, account) { + Ok(res) => Ok(res), + Err(e) => Err(e), + } +} + +#[pyfunction] +fn py_find_password(service: &str) -> PyResult { + match find_password(service) { + Ok(res) => Ok(res), + Err(e) => Err(e), + } +} + +#[pyfunction] +fn py_find_credentials(service: &str) -> PyResult> { + let mut creds: Vec<(String, String)> = vec![]; + match find_credentials(service, &mut creds) { + Ok(res) => Ok(creds), + Err(e) => Err(e), + } +} + +/// A Python module implemented in Rust. +#[pymodule] +fn keyring_test(_py: Python, m: &PyModule) -> PyResult<()> { + m.add_function(wrap_pyfunction!(py_get_password, m)?)?; + m.add_function(wrap_pyfunction!(py_set_password, m)?)?; + m.add_function(wrap_pyfunction!(py_delete_password, m)?)?; + m.add_function(wrap_pyfunction!(py_find_password, m)?)?; + m.add_function(wrap_pyfunction!(py_find_credentials, m)?)?; + Ok(()) +} diff --git a/src/secrets/src/keyring/.cargo/config.toml b/src/secrets/src/keyring/.cargo/config.toml new file mode 100644 index 00000000..d6d7ccc3 --- /dev/null +++ b/src/secrets/src/keyring/.cargo/config.toml @@ -0,0 +1,2 @@ +[target.'cfg(all(windows, target_env = "msvc"))'] +rustflags = ["-Ctarget-feature=+crt-static"] \ No newline at end of file diff --git a/src/secrets/src/keyring/.npmignore b/src/secrets/src/keyring/.npmignore new file mode 100644 index 00000000..d0560b0c --- /dev/null +++ b/src/secrets/src/keyring/.npmignore @@ -0,0 +1,13 @@ +target +Cross.toml +.cargo +.github +npm +.eslintrc +.prettierignore +rustfmt.toml +yarn.lock +*.node +.yarn +__test__ +renovate.json diff --git a/src/secrets/src/keyring/Cargo.lock b/src/secrets/src/keyring/Cargo.lock new file mode 100644 index 00000000..ca9f2a10 --- /dev/null +++ b/src/secrets/src/keyring/Cargo.lock @@ -0,0 +1,737 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" +dependencies = [ + "memchr", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" + +[[package]] +name = "cfg-expr" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b40ccee03b5175c18cde8f37e7d2a33bcef6f8ec8f7cc0d81090d1bb380949c9" +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 = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[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 = "ctor" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f34ba9a9bcb8645379e9de8cb3ecfcf4d1c85ba66d90deb3259206fa5aa193b" +dependencies = [ + "quote", + "syn 2.0.28", +] + +[[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.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" + +[[package]] +name = "futures-executor" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" + +[[package]] +name = "futures-macro" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "futures-task" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" + +[[package]] +name = "futures-util" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +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", + "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.28", +] + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "keyring" +version = "1.0.0" +dependencies = [ + "cfg-if", + "core-foundation", + "core-foundation-sys", + "gio", + "glib", + "glib-sys", + "libsecret", + "libsecret-sys", + "napi", + "napi-build", + "napi-derive", + "thiserror", + "windows-sys", +] + +[[package]] +name = "libc" +version = "0.2.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[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 = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "napi" +version = "2.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ede2d12cd6fce44da537a4be1f5510c73be2506c2e32dfaaafd1f36968f3a0e" +dependencies = [ + "bitflags", + "ctor", + "napi-derive", + "napi-sys", + "once_cell", +] + +[[package]] +name = "napi-build" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "882a73d9ef23e8dc2ebbffb6a6ae2ef467c0f18ac10711e4cc59c5485d41df0e" + +[[package]] +name = "napi-derive" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da1c6a8fa84d549aa8708fcd062372bf8ec6e849de39016ab921067d21bde367" +dependencies = [ + "cfg-if", + "convert_case", + "napi-derive-backend", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "napi-derive-backend" +version = "1.0.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20bbc7c69168d06a848f925ec5f0e0997f98e8c8d4f2cc30157f0da51c009e17" +dependencies = [ + "convert_case", + "once_cell", + "proc-macro2", + "quote", + "regex", + "semver", + "syn 1.0.109", +] + +[[package]] +name = "napi-sys" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "166b5ef52a3ab5575047a9fe8d4a030cdd0f63c96f071cd6907674453b07bae3" +dependencies = [ + "libloading", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "pin-project-lite" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57" + +[[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", +] + +[[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.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7b6d6190b7594385f61bd3911cd1be99dfddcfc365a4160cc2ab5bff4aed294" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" + +[[package]] +name = "semver" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" + +[[package]] +name = "serde" +version = "1.0.179" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a5bf42b8d227d4abf38a1ddb08602e229108a517cd4e5bb28f9c7eaafdce5c0" + +[[package]] +name = "serde_spanned" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" +dependencies = [ + "serde", +] + +[[package]] +name = "slab" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "system-deps" +version = "6.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30c2de8a4d8f4b823d634affc9cd2a74ec98c53a756f317e529a48046cbf71f3" +dependencies = [ + "cfg-expr", + "heck", + "pkg-config", + "toml", + "version-compare", +] + +[[package]] +name = "target-lexicon" +version = "0.12.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2faeef5759ab89935255b1a4cd98e0baf99d1085e37d36599c625dac49ae8e" + +[[package]] +name = "thiserror" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "611040a08a0439f8248d1990b111c95baa9c704c805fa1f62104b39655fd7f90" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "toml" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "unicode-ident" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" + +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" +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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + +[[package]] +name = "winnow" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bd122eb777186e60c3fdf765a58ac76e41c582f1f535fbf3314434c6b58f3f7" +dependencies = [ + "memchr", +] diff --git a/src/secrets/src/keyring/Cargo.toml b/src/secrets/src/keyring/Cargo.toml new file mode 100644 index 00000000..e3e3ab56 --- /dev/null +++ b/src/secrets/src/keyring/Cargo.toml @@ -0,0 +1,47 @@ +[package] +edition = "2021" +name = "keyring" +version = "1.0.0" +authors = ["Zowe Project"] +license = "EPL-2.0" +repository = "https://github.com/zowe/zowe-cli" + +[lib] +name = "keyring" +crate-type = ["cdylib"] + +[dependencies] +cfg-if = "1.0" +# Default enable napi4 feature, see https://nodejs.org/api/n-api.html#node-api-version-matrix +napi = { version = "2", default-features = false, features = ["napi4"] } +napi-derive = "2" +thiserror = "1.0.38" + +[target.'cfg(target_os = "windows")'.dependencies.windows-sys] +features = [ + "Win32_Foundation", + "Win32_Security_Credentials", + "Win32_System_Diagnostics_Debug", + "Win32_System_Memory", + "Win32_System_WindowsProgramming", +] +version = "0.48.0" + +[target.'cfg(target_os = "macos")'.dependencies] +core-foundation = "0.9.3" +core-foundation-sys = "0.8.4" + +[target.'cfg(any(target_os = "freebsd", target_os = "linux"))'.dependencies] +glib = "0.18.2" +glib-sys = "0.18.1" +gio = "0.18.2" +libsecret = "0.4.0" +libsecret-sys = "0.4.0" + +[build-dependencies] +napi-build = "2" + +[profile.release] +lto = true +opt-level = "z" # Optimize for size. +strip = "symbols" diff --git a/src/secrets/src/keyring/Cross.toml b/src/secrets/src/keyring/Cross.toml new file mode 100644 index 00000000..106f07ec --- /dev/null +++ b/src/secrets/src/keyring/Cross.toml @@ -0,0 +1,37 @@ +[build] +pre-build = [ + "dpkg --add-architecture $CROSS_DEB_ARCH", + "apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y libsecret-1-dev:$CROSS_DEB_ARCH", +] + +[build.env] +passthrough = [ + "PKG_CONFIG_SYSROOT_DIR", + "PKG_CONFIG_PATH", + "RUSTFLAGS" +] + +[target.aarch64-unknown-linux-gnu] +image = "ghcr.io/cross-rs/aarch64-unknown-linux-gnu:main" + +[target.aarch64-unknown-linux-musl] +image = "rust:alpine" +pre-build = [ + "wget -qO- https://musl.cc/aarch64-linux-musl-cross.tgz | tar -xzC / && export PATH=\"/aarch64-linux-musl-cross/bin:$PATH\"", + "apk add --no-cache musl-dev pkgconfig", + "apk add -p /aarch64-linux-musl-cross --initdb --arch aarch64 --allow-untrusted -X \"https://dl-cdn.alpinelinux.org/alpine/latest-stable/main/\" --no-cache --no-scripts libsecret-dev", + "rustup target add aarch64-unknown-linux-musl" +] + +[target.armv7-unknown-linux-gnueabihf] +image = "ghcr.io/cross-rs/armv7-unknown-linux-gnueabihf:main" + +[target.i686-unknown-linux-gnu] +image = "ghcr.io/cross-rs/i686-unknown-linux-gnu:main" + +[target.x86_64-unknown-linux-gnu] +image = "ghcr.io/cross-rs/x86_64-unknown-linux-gnu:main" + +[target.x86_64-unknown-linux-musl] +image = "rust:alpine" +pre-build = ["apk add libsecret-dev musl-dev"] diff --git a/src/secrets/src/keyring/EXTENDERS.md b/src/secrets/src/keyring/EXTENDERS.md new file mode 100644 index 00000000..d2f55110 --- /dev/null +++ b/src/secrets/src/keyring/EXTENDERS.md @@ -0,0 +1,140 @@ +# Usage (Extenders) + +## What is `keyring`? + +`keyring` is a cross-platform module meant to interact with OS (operating system) credential storage. `keyring` is written in Rust, and uses other Rust libraries to interface with credential storage APIs (application programming interfaces). + +## Why switch to `keyring`? + +As `node-keytar` is now unmaintained, there was a demand for a replacement that can function identically to the original module. + +As `keyring` was modeled after `node-keytar`, the same operations can be performed in credential storage: + +- Storing credentials +- Retrieving credentials +- Searching for passwords based on a matching label +- Searching for matching credentials based on a prefix/query +- Deleting credentials + +**Currently, there are no breaking changes** between the use of `node-keytar` and `keyring`. This is intended by design. + +From a developer's perspective, one can simply update existing extenders or plug-ins to import the keyring module from `@zowe/secrets-for-zowe-sdk` instead of `node-keytar`, allowing for a straightforward transition. All functions previously exported in `node-keytar` will be available in `keyring`. Simply add `@zowe/secrets-for-zowe-sdk` to your project using `npm` or `yarn`. + +Importing a function from `keyring` is identical to the `node-keytar` import process: + +```ts +// Import all functions under a namespace... +import { keyring } from "@zowe/secrets-for-zowe-sdk"; +// Or, use require to import the keyring module. +const { keyring } = require("@zowe/secrets-for-zowe-sdk"); +``` + +After the desired functions are imported, feel free to use them in the same fashion as the `node-keytar` functions. For the examples below, `async/await` keywords are used, but the functions can also be used with `.then/.catch` promise blocks: + +```ts +getPassword("TestService", "AccountA") +.then((pw) => { + console.log("The password for TestService/AccountA is:", pw); +}) +.catch((err) => { + console.error("An error occurred!", err.message); +}); +``` + +**Examples:** + +```ts +// Set a password with a given service and account name +// Password will be stored under / +await keyring.setPassword("TestService", "AccountA", "Apassword"); + +// Get a password, given a service and account name +await keyring.getPassword("TestService", "AccountA"); + +// Find credentials based on a matching label +await keyring.findCredentials("TestService"); + +// Find password that matches a service and account +await keyring.findPassword("TestService/AccountA"); + +// Delete a credential w/ the provided service and account name +await keyring.deletePassword("TestService", "AccountA"); +``` + +## Webpacking/bundling alongside your project + +Some projects leverage a JavaScript bundler, such as Webpack or Vite, to minify and compress their Node.js packages. +While the Secrets SDK does support Webpack, developers who want to bundle the Secrets SDK alongside their package should set up a `prebuilds` folder alongside their package root. + +For example, if your package places build output in the `out` folder, your directory structure should look like this: + +``` +your-pkg/ +├── package.json +├── src/ +├── out/ +│ └── bundle.js +├── prebuilds/ +│ └── (node binaries for Secrets SDK) +``` + +If you are using ESbuild or Webpack, consider using a copy plugin to copy the `prebuilds` folder from the Secrets SDK *node_modules* folder: +- ESbuild: [esbuild-copy-static-files](https://www.npmjs.com/package/esbuild-copy-static-files) +- Webpack: [copy-webpack-plugin](https://www.npmjs.com/package/copy-webpack-plugin) + +Otherwise, use the Node.js script below (**requires Node 16.7.0 and above**). It creates a copy of the `prebuilds` folder containing the required Secrets SDK binaries. Run this script in the same directory as your extension's `package.json`: + +```js +const { cpSync } = require("fs"); +cpSync("/path/to/node_modules/@zowe/secrets-for-zowe-sdk/prebuilds", "prebuilds", {force: true, recursive: true}); +``` +**Note:** The first argument for `cpSync` will vary, depending on where the *node_modules* folder is located in your environment. + +We recommend that developers add this logic to a `prepare` script in their `package.json` to guarantee the binaries are up-to-date after installing dependencies. +Save the above script as a JavaScript file (e.g., `scripts/copyKeyringBinaries.js`), and execute the script: + +```js +{ + "scripts": { + "prepare": "node scripts/copyKeyringBinaries.js" + } +} +``` + +If you are bundling a VSCode extension, and are using a `.vscodeignore` file, you must allow the prebuilds folder to be packaged in the VSIX. +Add the following line to your `.vscodeignore` file: +``` +!prebuilds/** +``` + +### Updating imports + +Some extenders might import `keytar` directly as a dependency. In these cases, extenders should import the `keyring` module from this package instead. + +**Take caution when importing** as the import process is slightly different than `node-keytar`: + +Before: +```js +const keytar = require("node-keytar"); +// ES6 import: +import * as keytar from "node-keytar"; +``` + +After: +```js +const { keyring } = require("@zowe/secrets-for-zowe-sdk"); +// ES6 import: +import { keyring } from "@zowe/secrets-for-zowe-sdk"; +``` + +Notice that the keyring module must be accessed from the dependency imports before use. +To reduce the amount of code that needs updated, users can use an import alias to the phrase "keytar": + +```js +const { keyring: keytar } = require("@zowe/secrets-for-zowe-sdk"); +// ES6 import: +import { keyring as keytar } from "@zowe/secrets-for-zowe-sdk"; + +// Existing code, such as the example below, can remain unchanged with this import alias: +keytar.setPassword("Hello", "World", "ExamplePassword"); +``` \ No newline at end of file diff --git a/src/secrets/src/keyring/README.md b/src/secrets/src/keyring/README.md new file mode 100644 index 00000000..3a61337c --- /dev/null +++ b/src/secrets/src/keyring/README.md @@ -0,0 +1,177 @@ +# keyring + +keyring is a native Node.js module for accessing and managing OS credential storage. + +## Compatibility + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
OS / ArchitectureNode.js Version
v12v14v16v18
Windowsx64
x86
arm64
macOSx64
aarch64
Linux (gnu)x64
x86
aarch64
armv7l (gnueabihf)
Linux (musl)x64
aarch64
+ +## Features + +keyring supports the following operations within credential storage: + +- [x] **Set** a credential +- [x] **Retrieve** a credential +- [x] **Find all credentials** with matching attributes +- [x] **Find a password** with matching attributes + +Some benefits to using keyring: + +- [x] **Cross-platform support** makes for straight-forward secrets management +- [x] **Existing OS credentials are supported** out-of-the-box +- [x] **Avoids memory allocation** - memory only allocated as needed for OS-specific APIs + +## Node API documentation + +### deletePassword + +Deletes a password with matching `service` and `account` parameters. + +**Returns:** Whether the password was deleted successfully. + +```ts +function deletePassword(service: string, account: string) -> Promise +``` + +### findCredentials + +Finds all credentials with a matching `service` parameter. + +**Returns:** An array of `Credential` objects, containing the `account` and `password` for each credential that is found within `service`. + +```ts +interface Credential { + account: string; + password: string; +}; + +function findCredentials(service: string) -> Promise> +``` + +### findPassword + +Finds a password with a matching `service` and `account` parameter. + +**Returns:** The first password found in `/`, or `null` if not found. + +```ts +function findPassword(service: string, account: string) -> Promise +``` + +### getPassword + +Gets a password with a matching `service` and `account` parameter. + +**Returns:** The password stored under `/`, or `null` if not found. + +```ts +function getPassword(service: string, account: string) -> Promise +``` + +### setPassword + +Stores a password with the given `service`, `account`, and `password`. + +```ts +function setPassword(service: string, account: string, password: string) -> Promise +``` diff --git a/src/secrets/src/keyring/__test__/index.spec.mjs b/src/secrets/src/keyring/__test__/index.spec.mjs new file mode 100644 index 00000000..6ca2cd4c --- /dev/null +++ b/src/secrets/src/keyring/__test__/index.spec.mjs @@ -0,0 +1,312 @@ +import test from "ava"; +import { + deletePassword, + findCredentials, + findPassword, + getPassword, + setPassword, +} from "../index.js"; + +// generate a number in range [min, max) +const randomInt = (min, max) => { + return Math.floor(Math.random() * (max - min)) + min; +}; + +// generate random ASCII string of length "len" +// (could use constants instead, but this should better emulate real-world scenarios) +const randomAsciiString = (len) => { + let str = ""; + for (let i = 0; i < len; i++) { + str = str.concat(String.fromCharCode(randomInt(97, 123))); + } + return str; +}; + +const TEST_CREDENTIALS = [ + { service: "TestKeyring", account: "TestASCII" }, + { service: "TestKeyring", account: "TestUTF8" }, + { service: "TestKeyring", account: "TestCharSet" }, + { service: "TestKeyring", account: "TestUTF16" }, + { service: "TestKeyring", account: "TestCJK" }, + { service: "TestKeyring", account: "TestBinary" }, + { service: "TestEmptyAccount", account: "" }, + { service: "", account: "TestEmptyService" }, + { service: "TestKeyring", account: "PwNullTerm" }, +]; + +test.serial("get/setPassword with binary data", async (t) => { + const binaryGroups = + "01001000 01100101 01101100 01101100 01101111 00100000 01110111 01101111 01110010 01101100 01100100 00100001".match( + /[01]{8}/g + ); + const parsed = binaryGroups.map((binary) => parseInt(binary, 2)); + const buffer = Buffer.from(new Uint8Array(parsed).buffer); + + await setPassword("TestKeyring", "TestBinary", buffer.toString()); + const res = await getPassword("TestKeyring", "TestBinary"); + t.is(res, buffer.toString()); +}); + +test.serial("get/setPassword with empty string parameters", async (t) => { + // Empty "account" parameter + await setPassword("TestEmptyAccount", "", "emptyAccountPW"); + const accountRes = await getPassword("TestEmptyAccount", ""); + t.is(accountRes, "emptyAccountPW"); + + // Empty "service" parameter + await setPassword("", "TestEmptyService", "emptyServicePW"); + const serviceRes = await getPassword("", "TestEmptyService"); + t.is(serviceRes, "emptyServicePW"); +}); + +test.serial("get/setPassword with ASCII string", async (t) => { + await setPassword("TestKeyring", "TestASCII", "ASCII string"); + + const str = await getPassword("TestKeyring", "TestASCII"); + t.is(str, "ASCII string"); +}); + +test.serial("get/setPassword with mixed character set", async (t) => { + await setPassword("TestKeyring", "TestCharSet", "I 💔 ASCII"); + + const str = await getPassword("TestKeyring", "TestCharSet"); + t.is(str, "I 💔 ASCII"); +}); + +test.serial("get/setPassword with UTF-16 chars", async (t) => { + await setPassword("TestKeyring", "TestUTF16", "🌞🌙🌟🌴"); + + const str = await getPassword("TestKeyring", "TestUTF16"); + t.is(str, "🌞🌙🌟🌴"); +}); + +test.serial("get/setPassword with UTF-8 chars", async (t) => { + await setPassword( + "TestKeyring", + "TestUTF8", + "ᚻᛖ ᚳᚹᚫᚦ ᚦᚫᛏ ᚻᛖ ᛒᚢᛞᛖ ᚩᚾ ᚦᚫᛗ ᛚᚪᚾᛞᛖ ᚾᚩᚱᚦᚹᛖᚪᚱᛞᚢᛗ ᚹᛁᚦ ᚦᚪ ᚹᛖᛥᚫ" + ); + + const str = await getPassword("TestKeyring", "TestUTF8"); + t.is(str, "ᚻᛖ ᚳᚹᚫᚦ ᚦᚫᛏ ᚻᛖ ᛒᚢᛞᛖ ᚩᚾ ᚦᚫᛗ ᛚᚪᚾᛞᛖ ᚾᚩᚱᚦᚹᛖᚪᚱᛞᚢᛗ ᚹᛁᚦ ᚦᚪ ᚹᛖᛥᚫ"); +}); + +test.serial("get/setPassword with CJK symbols", async (t) => { + await setPassword("TestKeyring", "TestCJK", "「こんにちは世界」"); + + const str = await getPassword("TestKeyring", "TestCJK"); + t.is(str, "「こんにちは世界」"); +}); + +test.serial("get/setPassword fails with null/undefined data", async (t) => { + try { + await setPassword("TestKeyring", "TestNull", null); + } catch (err) { + t.is(err.code, "StringExpected"); + } + + try { + await setPassword("TestKeyring", "TestNull", undefined); + } catch (err) { + t.is(err.code, "StringExpected"); + } +}); + +test.serial( + "get/setPassword with password containing extra null terminators", + async (t) => { + // "password" parameter w/ extra null terminator + await setPassword("TestKeyring", "PwNullTerm", "PW\0"); + const pwRes = await getPassword("TestKeyring", "PwNullTerm"); + if (process.platform === "linux") { + // libsecret automatically strips off null terminator + t.is(pwRes, "PW"); + } else { + t.is(pwRes, "PW\0"); + } + } +); + +test.serial("getPassword with missing data", async (t) => { + const str = await getPassword("TestKeyring", "TestMissingPW"); + t.is(str, null); +}); + +test.serial( + "findCredentials verifies that test credentials were stored", + async (t) => { + let expected = [ + { account: "TestASCII", password: "ASCII string" }, + { account: "TestBinary", password: "Hello world!" }, + { account: "TestCharSet", password: "I 💔 ASCII" }, + { account: "TestCJK", password: "「こんにちは世界」" }, + { + account: "TestUTF8", + password: "ᚻᛖ ᚳᚹᚫᚦ ᚦᚫᛏ ᚻᛖ ᛒᚢᛞᛖ ᚩᚾ ᚦᚫᛗ ᛚᚪᚾᛞᛖ ᚾᚩᚱᚦᚹᛖᚪᚱᛞᚢᛗ ᚹᛁᚦ ᚦᚪ ᚹᛖᛥᚫ", + }, + { account: "TestUTF16", password: "🌞🌙🌟🌴" }, + { account: "PwNullTerm", password: "PW\x00" }, + ]; + const actual = await findCredentials("TestKeyring"); + t.is( + actual.length, + expected.length, + `actual: ${JSON.stringify(actual)}; expected: ${JSON.stringify(expected)}` + ); + + expected.forEach((cred) => + t.not( + actual.find((c) => c === cred), + null + ) + ); + } +); + +test.serial("findCredentials works when only one credential is found", async (t) => { + await setPassword("TestKeyring2", "TestOneCred", "pass"); + + const creds = await findCredentials("TestKeyring2"); + t.deepEqual(creds, [{ + account: "TestOneCred", + password: "pass" + }]); + await deletePassword("TestKeyring2", "TestOneCred"); +}); + +test.serial("findPassword for ASCII string", async (t) => { + const pw = await findPassword("TestKeyring/TestASCII"); + t.is(pw, "ASCII string"); +}); + +test.serial("findPassword for mixed character set", async (t) => { + const pw = await findPassword("TestKeyring/TestCharSet"); + t.is(pw, "I 💔 ASCII"); +}); + +test.serial("findPassword for UTF-16", async (t) => { + const pw = await findPassword("TestKeyring/TestUTF16"); + t.is(pw, "🌞🌙🌟🌴"); +}); + +test.serial("findPassword for CJK symbols", async (t) => { + const pw = await findPassword("TestKeyring/TestCJK"); + t.is(pw, "「こんにちは世界」"); +}); + +test("deletePassword deletes all test credentials", async (t) => { + console.log( + "\nThe deletePassword test is running. There is an intended delay of 5 seconds to wait for the keyring to update." + ); + const timeout = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); + // initial timeout to give keyrings time to populate + await timeout(5000); + for (const cred of TEST_CREDENTIALS) { + const result = await deletePassword(cred.service, cred.account); + if (!result) { + t.fail(`Credential with account "${cred.account}" failed to delete.`); + } + } + + const afterDeletion = await findCredentials("TestKeyring"); + t.is( + afterDeletion.length, + 0, + `One or more credentials were still in the keyring: ${afterDeletion + .map((c) => c.account) + .join(", ")}` + ); +}); + +// Unit tests specific to Windows API calls +if (process.platform === "win32") { + test.serial( + "setPassword fails when blob exceeds CRED_MAX_CREDENTIAL_BLOB_SIZE", + async (t) => { + console.log("win32: platform-specific tests for WinAPI"); + const CRED_MAX_CREDENTIAL_BLOB_SIZE = 5 * 512; + const str = randomAsciiString(CRED_MAX_CREDENTIAL_BLOB_SIZE + 1); + try { + await setPassword("TestKeyringWindows", "MaxCredBlobSize", str); + } catch (err) { + t.not(err, null); + } + } + ); + + test.serial( + "setPassword fails when TargetName exceeds CRED_MAX_GENERIC_TARGET_NAME_LENGTH", + async (t) => { + const CRED_MAX_GENERIC_TARGET_NAME_LENGTH = 32767; + const str = randomAsciiString(CRED_MAX_GENERIC_TARGET_NAME_LENGTH + 1); + try { + await setPassword( + "TestKeyringWindows", + "MaxGenericTargetNameLen_".concat(str), + "pw" + ); + } catch (err) { + t.not(err, null); + } + } + ); + + test.serial( + "setPassword fails when account length exceeds CRED_MAX_USERNAME_LENGTH", + async (t) => { + const CRED_MAX_USERNAME_LENGTH = 512; + const str = randomAsciiString(CRED_MAX_USERNAME_LENGTH + 1); + try { + await setPassword("TestKeyringWindows", str, "pw"); + } catch (err) { + t.not(err, null); + } + } + ); + + test.serial( + "findCredentials where CredEnumerateW returns false", + async (t) => { + const found = await findCredentials("TestKeyringWindowsInvalidService"); + t.deepEqual(found, []); + } + ); + + test.serial("findCredentials where TargetName is NULL", async (t) => { + // Since rust won't accept null as a parameter in the backend, best test is an empty string + const found = await findCredentials(""); + t.is(found.length > 0, true); + }); + + test.serial( + "Error handled when CredReadW throws ERROR_NOT_FOUND", + async (t) => { + try { + const errorTest = await getPassword( + "TestKeyringWindowsInvalidService", + "FakeAccount" + ); + t.is(errorTest, null); + } catch (err) { + t.fail( + "getPassword should not throw an exception when no credentials are found (win32)" + ); + } + } + ); + + test.serial( + "CredDeleteW with a credential that does not exist", + async (t) => { + try { + await deletePassword("TestKeyringWindowsInvalidService", "FakeAccount"); + } catch (err) { + t.fail( + "deletePassword should not throw an exception for a credential that doesn't exist (win32)" + ); + } + + t.pass(); + } + ); +} diff --git a/src/secrets/src/keyring/build.rs b/src/secrets/src/keyring/build.rs new file mode 100644 index 00000000..1f866b6a --- /dev/null +++ b/src/secrets/src/keyring/build.rs @@ -0,0 +1,5 @@ +extern crate napi_build; + +fn main() { + napi_build::setup(); +} diff --git a/src/secrets/src/keyring/index.d.ts b/src/secrets/src/keyring/index.d.ts new file mode 100644 index 00000000..fe9a0998 --- /dev/null +++ b/src/secrets/src/keyring/index.d.ts @@ -0,0 +1,14 @@ +/* tslint:disable */ +/* eslint-disable */ + +/* auto-generated by NAPI-RS */ + +export interface Credential { + account: string + password: string +} +export function deletePassword(service: string, account: string): Promise +export function findCredentials(service: string): Promise> +export function findPassword(service: string): Promise +export function getPassword(service: string, account: string): Promise +export function setPassword(service: string, account: string, password: string): Promise diff --git a/src/secrets/src/keyring/index.js b/src/secrets/src/keyring/index.js new file mode 100644 index 00000000..fd3b886f --- /dev/null +++ b/src/secrets/src/keyring/index.js @@ -0,0 +1,66 @@ +/* +* This program and the accompanying materials are made available under the terms of the +* Eclipse Public License v2.0 which accompanies this distribution, and is available at +* https://www.eclipse.org/legal/epl-v20.html +* +* SPDX-License-Identifier: EPL-2.0 +* +* Copyright Contributors to the Zowe Project. +* +*/ + +const { existsSync } = require("fs"); +const { join, normalize, parse } = require("path"); + +function findPrebuildsDir(dir) { + if (!dir || existsSync(join(dir, "package.json"))) { + return join(dir, "prebuilds"); + } + + const dirUp = normalize(join(dir, "..")); + if (parse(dirUp).base.length > 0) { + return findPrebuildsDir(dirUp); + } +} + +function getTargetName() { + switch (process.platform) { + case "win32": + return `win32-${process.arch}-msvc`; + case "linux": + const isMusl = + process.report.getReport().header.glibcVersionRuntime == null; + const abi = isMusl ? "musl" : "gnu"; + switch (process.arch) { + case "arm": + return `linux-arm-${abi}eabihf`; + default: + return `linux-${process.arch}-${abi}`; + } + case "darwin": + default: + return `${process.platform}-${process.arch}`; + } +} + +const requireFn = + typeof __webpack_require__ === "function" + ? __non_webpack_require__ + : require; + +const binaryPath = requireFn.resolve(`./keyring.${getTargetName()}.node`, { + paths: [__dirname, findPrebuildsDir(__dirname)].filter(Boolean), +}); +const { + deletePassword, + findCredentials, + findPassword, + getPassword, + setPassword, +} = requireFn(binaryPath); + +module.exports.deletePassword = deletePassword; +module.exports.findCredentials = findCredentials; +module.exports.findPassword = findPassword; +module.exports.getPassword = getPassword; +module.exports.setPassword = setPassword; diff --git a/src/secrets/src/keyring/napi.json b/src/secrets/src/keyring/napi.json new file mode 100644 index 00000000..354e4cdc --- /dev/null +++ b/src/secrets/src/keyring/napi.json @@ -0,0 +1,21 @@ +{ + "napi": { + "name": "keyring", + "package": { + "name": "keyring" + }, + "triples": { + "defaults": true, + "additional": [ + "x86_64-unknown-linux-musl", + "aarch64-unknown-linux-gnu", + "i686-unknown-linux-gnu", + "i686-pc-windows-msvc", + "armv7-unknown-linux-gnueabihf", + "aarch64-apple-darwin", + "aarch64-unknown-linux-musl", + "aarch64-pc-windows-msvc" + ] + } + } +} diff --git a/src/secrets/src/keyring/rustfmt.toml b/src/secrets/src/keyring/rustfmt.toml new file mode 100644 index 00000000..ca5602ae --- /dev/null +++ b/src/secrets/src/keyring/rustfmt.toml @@ -0,0 +1,2 @@ +tab_spaces = 4 +edition = "2021" diff --git a/src/secrets/src/keyring/src/lib.rs b/src/secrets/src/keyring/src/lib.rs new file mode 100644 index 00000000..2965de83 --- /dev/null +++ b/src/secrets/src/keyring/src/lib.rs @@ -0,0 +1,35 @@ +use napi::bindgen_prelude::AsyncTask; +use napi_derive::napi; +use workers::{DeletePassword, FindCredentials, FindPassword, GetPassword, SetPassword}; + +mod os; +mod workers; + +#[napi] +fn delete_password(service: String, account: String) -> AsyncTask { + AsyncTask::new(DeletePassword { service, account }) +} + +#[napi] +fn find_credentials(service: String) -> AsyncTask { + AsyncTask::new(FindCredentials { service }) +} + +#[napi(ts_return_type = "Promise")] +fn find_password(service: String) -> AsyncTask { + AsyncTask::new(FindPassword { service }) +} + +#[napi(ts_return_type = "Promise")] +fn get_password(service: String, account: String) -> AsyncTask { + AsyncTask::new(GetPassword { service, account }) +} + +#[napi(ts_return_type = "Promise")] +fn set_password(service: String, account: String, password: String) -> AsyncTask { + AsyncTask::new(SetPassword { + service, + account, + password, + }) +} diff --git a/src/secrets/src/keyring/src/os/error.rs b/src/secrets/src/keyring/src/os/error.rs new file mode 100644 index 00000000..7d7c251d --- /dev/null +++ b/src/secrets/src/keyring/src/os/error.rs @@ -0,0 +1,41 @@ +use std::{str::Utf8Error, string::FromUtf16Error, string::FromUtf8Error}; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum KeyringError { + #[cfg(target_os = "macos")] + #[error("[keyring] Invalid parameter provided for '{argument:?}'. Details:\n\n{details:?}")] + InvalidArg { argument: String, details: String }, + + #[cfg(any(target_os = "macos", target_os = "linux", target_os = "freebsd"))] + #[error("[keyring] {name:?} library returned an error:\n\n{details:?}")] + Library { name: String, details: String }, + + #[cfg(not(target_os = "macos"))] + #[error("[keyring] An OS error has occurred:\n\n{0}")] + Os(String), + + #[error("[keyring] A UTF-8 error has occurred:\n\n{0}")] + Utf8(String), + + #[error("[keyring] A UTF-16 error has occurred:\n\n{0}")] + Utf16(String), +} + +impl From for KeyringError { + fn from(error: FromUtf8Error) -> Self { + KeyringError::Utf8(format!("{:?}", error)) + } +} + +impl From for KeyringError { + fn from(error: FromUtf16Error) -> Self { + KeyringError::Utf16(format!("{:?}", error)) + } +} + +impl From for KeyringError { + fn from(error: Utf8Error) -> Self { + KeyringError::Utf8(format!("{:?}", error)) + } +} diff --git a/src/secrets/src/keyring/src/os/mac/error.rs b/src/secrets/src/keyring/src/os/mac/error.rs new file mode 100644 index 00000000..980527b0 --- /dev/null +++ b/src/secrets/src/keyring/src/os/mac/error.rs @@ -0,0 +1,66 @@ +use crate::os::mac::ffi::SecCopyErrorMessageString; +use core_foundation::base::TCFType; +use core_foundation::string::CFString; +use core_foundation_sys::base::OSStatus; +use std::fmt::{Debug, Display, Formatter}; +use std::num::NonZeroI32; + +#[derive(Copy, Clone)] +pub struct Error(NonZeroI32); + +/// errSecItemNotFound +pub const ERR_SEC_ITEM_NOT_FOUND: i32 = -25300; + +impl Error { + #[inline] + #[must_use] + pub fn from_code(code: OSStatus) -> Self { + Self(NonZeroI32::new(code).unwrap_or_else(|| NonZeroI32::new(1).unwrap())) + } + + pub fn code(self) -> i32 { + self.0.get() as _ + } + + /// Gets the message matching an OSStatus error code, if one exists. + pub fn message(&self) -> Option { + unsafe { + let s = SecCopyErrorMessageString(self.code(), std::ptr::null_mut()); + if s.is_null() { + None + } else { + Some(CFString::wrap_under_create_rule(s).to_string()) + } + } + } +} + +impl Debug for Error { + fn fmt(&self, fmt: &mut Formatter<'_>) -> std::fmt::Result { + let mut builder = fmt.debug_struct("Error"); + builder.field("code", &self.0); + if let Some(message) = self.message() { + builder.field("message", &message); + } + builder.finish() + } +} + +impl Display for Error { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self.message() { + Some(msg) => write!(f, "{}", msg), + None => write!(f, "code: {}", self.code()), + } + } +} + +/// Handles the OSStatus code from macOS FFI calls (error handling helper fn) +#[inline(always)] +pub fn handle_os_status(err: OSStatus) -> Result<(), Error> { + match err { + // errSecSuccess + 0 => Ok(()), + err => Err(Error::from_code(err)), + } +} diff --git a/src/secrets/src/keyring/src/os/mac/ffi.rs b/src/secrets/src/keyring/src/os/mac/ffi.rs new file mode 100644 index 00000000..e76e92c0 --- /dev/null +++ b/src/secrets/src/keyring/src/os/mac/ffi.rs @@ -0,0 +1,118 @@ +use core_foundation_sys::base::{CFTypeID, CFTypeRef, OSStatus}; +use core_foundation_sys::dictionary::CFDictionaryRef; +use core_foundation_sys::string::CFStringRef; +use std::ffi::{c_char, c_void}; + +/// +/// Keychain item reference types. +/// +/// See lib/SecBase.h here: +/// https://opensource.apple.com/source/libsecurity_keychain/libsecurity_keychain-55050.2/ +/// +pub enum OpaqueSecKeychainItemRef {} +pub enum OpaqueSecKeychainRef {} +pub type SecKeychainItemRef = *mut OpaqueSecKeychainItemRef; +pub type SecKeychainRef = *mut OpaqueSecKeychainRef; + +/// +/// Certificate item reference types. +/// https://developer.apple.com/documentation/security/opaqueseccertificateref +/// +pub enum OpaqueSecCertificateRef {} +pub type SecCertificateRef = *mut OpaqueSecCertificateRef; + +/// +/// Identity reference types. +/// https://developer.apple.com/documentation/security/opaquesecidentityref +/// +pub enum OpaqueSecIdentityRef {} +pub type SecIdentityRef = *mut OpaqueSecIdentityRef; + +/// +/// Key reference types. +/// https://developer.apple.com/documentation/security/seckeyref +/// +pub enum OpaqueSecKeyRef {} +pub type SecKeyRef = *mut OpaqueSecKeyRef; + +/// +/// Keychain attribute structure for searching items. +/// https://developer.apple.com/documentation/security/seckeychainattribute, +/// https://developer.apple.com/documentation/security/seckeychainattributelist +/// +#[repr(C)] +#[derive(Copy, Clone)] +pub struct SecKeychainAttribute { + pub tag: u32, + pub length: u32, + pub data: *mut c_void, +} +#[repr(C)] +#[derive(Copy, Clone)] +pub struct SecKeychainAttributeList { + pub count: u32, + pub attr: *mut SecKeychainAttribute, +} + +/* + * Defined below are the C functions that the Rust logic + * uses to interact with macOS's Security.framework. + * + * Since we can call C functions directly using Rust FFI, we just need to define + * the function prototypes ahead of time, and link them to the Security.framework library. + * Rust will evaluate these symbols during compile time. + */ +#[link(name = "Security", kind = "framework")] +extern "C" { + // keychain.rs: + pub fn SecCopyErrorMessageString(status: OSStatus, reserved: *mut c_void) -> CFStringRef; + pub fn SecKeychainAddGenericPassword( + keychain: SecKeychainRef, + service_name_length: u32, + service_name: *const c_char, + account_name_length: u32, + account_name: *const c_char, + password_length: u32, + password_data: *const c_void, + item_ref: *mut SecKeychainItemRef, + ) -> OSStatus; + pub fn SecKeychainCopyDefault(keychain: *mut SecKeychainRef) -> OSStatus; + pub fn SecKeychainFindGenericPassword( + keychain_or_array: CFTypeRef, + service_name_len: u32, + service_name: *const c_char, + account_name_len: u32, + account_name: *const c_char, + password_len: *mut u32, + password: *mut *mut c_void, + item_ref: *mut SecKeychainItemRef, + ) -> OSStatus; + pub fn SecKeychainGetTypeID() -> CFTypeID; + + // keychain_item.rs: + pub fn SecKeychainItemDelete(item_ref: SecKeychainItemRef) -> OSStatus; + pub fn SecKeychainItemGetTypeID() -> CFTypeID; + pub fn SecKeychainItemModifyAttributesAndData( + item_ref: SecKeychainItemRef, + attr_list: *const SecKeychainAttributeList, + length: u32, + data: *const c_void, + ) -> OSStatus; + + // keychain_search.rs: + pub fn SecItemCopyMatching(query: CFDictionaryRef, result: *mut CFTypeRef) -> OSStatus; + pub static kSecAttrAccount: CFStringRef; + pub static kSecAttrLabel: CFStringRef; + pub static kSecAttrService: CFStringRef; + pub static kSecClass: CFStringRef; + pub static kSecClassGenericPassword: CFStringRef; + pub static kSecMatchLimit: CFStringRef; + pub static kSecReturnAttributes: CFStringRef; + pub static kSecReturnData: CFStringRef; + pub static kSecReturnRef: CFStringRef; + + // misc.rs: + pub fn SecCertificateGetTypeID() -> CFTypeID; + pub fn SecIdentityGetTypeID() -> CFTypeID; + pub fn SecKeyGetTypeID() -> CFTypeID; +} diff --git a/src/secrets/src/keyring/src/os/mac/keychain.rs b/src/secrets/src/keyring/src/os/mac/keychain.rs new file mode 100644 index 00000000..250b7c25 --- /dev/null +++ b/src/secrets/src/keyring/src/os/mac/keychain.rs @@ -0,0 +1,114 @@ +use crate::os::mac::error::{handle_os_status, Error}; +use crate::os::mac::ffi::{ + SecKeychainAddGenericPassword, SecKeychainCopyDefault, SecKeychainFindGenericPassword, + SecKeychainGetTypeID, SecKeychainRef, +}; +use crate::os::mac::keychain_item::SecKeychainItem; +use core_foundation::{base::TCFType, declare_TCFType, impl_TCFType}; +use std::ops::Deref; + +/* + * SecKeychain: https://developer.apple.com/documentation/security/seckeychain + * SecKeychainRef: https://developer.apple.com/documentation/security/seckeychainref + */ +declare_TCFType! { + SecKeychain, SecKeychainRef +} +impl_TCFType!(SecKeychain, SecKeychainRef, SecKeychainGetTypeID); + +/* Wrapper struct for handling passwords within SecKeychainItem objects. */ +pub struct KeychainItemPassword { + pub data: *const u8, + pub data_len: usize, +} + +impl AsRef<[u8]> for KeychainItemPassword { + #[inline] + fn as_ref(&self) -> &[u8] { + unsafe { std::slice::from_raw_parts(self.data, self.data_len) } + } +} + +impl Deref for KeychainItemPassword { + type Target = [u8]; + #[inline] + fn deref(&self) -> &Self::Target { + self.as_ref() + } +} + +impl SecKeychain { + pub fn default() -> Result { + let mut keychain = std::ptr::null_mut(); + unsafe { + handle_os_status(SecKeychainCopyDefault(&mut keychain))?; + } + unsafe { Ok(Self::wrap_under_create_rule(keychain)) } + } + + /// + /// set_password + /// Attempts to set the password within the keychain for a given service and account. + /// + /// Returns: + /// - Nothing if the password was set successfully, or + /// - An `Error` object if an error was encountered + /// + pub fn set_password(&self, service: &str, account: &str, password: &[u8]) -> Result<(), Error> { + match self.find_password(service, account) { + Ok((_, mut item)) => item.set_password(password), + _ => unsafe { + handle_os_status(SecKeychainAddGenericPassword( + self.as_CFTypeRef() as *mut _, + service.len() as u32, + service.as_ptr().cast(), + account.len() as u32, + account.as_ptr().cast(), + password.len() as u32, + password.as_ptr().cast(), + std::ptr::null_mut(), + )) + }, + } + } + + /// + /// find_password + /// Attempts to find a password within the keychain matching a given service and account. + /// + /// Returns: + /// - A pair containing the KeychainItem object with its password data if the password was found, or + /// - An `Error` object if an error was encountered + /// + pub fn find_password( + &self, + service: &str, + account: &str, + ) -> Result<(KeychainItemPassword, SecKeychainItem), Error> { + let keychain_ref = self.as_CFTypeRef(); + + let mut len = 0; + let mut data = std::ptr::null_mut(); + let mut item = std::ptr::null_mut(); + + unsafe { + handle_os_status(SecKeychainFindGenericPassword( + keychain_ref, + service.len() as u32, + service.as_ptr().cast(), + account.len() as u32, + account.as_ptr().cast(), + &mut len, + &mut data, + &mut item, + ))?; + Ok(( + KeychainItemPassword { + data: data as *const _, + data_len: len as usize, + }, + SecKeychainItem::wrap_under_create_rule(item), + )) + } + } +} diff --git a/src/secrets/src/keyring/src/os/mac/keychain_item.rs b/src/secrets/src/keyring/src/os/mac/keychain_item.rs new file mode 100644 index 00000000..8008e278 --- /dev/null +++ b/src/secrets/src/keyring/src/os/mac/keychain_item.rs @@ -0,0 +1,56 @@ +use crate::os::mac::error::{handle_os_status, Error}; +use crate::os::mac::ffi::{ + SecKeychainItemDelete, SecKeychainItemGetTypeID, SecKeychainItemModifyAttributesAndData, + SecKeychainItemRef, +}; +use core_foundation::base::TCFType; +use core_foundation::{declare_TCFType, impl_TCFType}; + +/* + * SecKeychainItem: https://developer.apple.com/documentation/security/seckeychainitem + * SecKeychainItemRef: https://developer.apple.com/documentation/security/seckeychainitemref + */ +declare_TCFType! { + SecKeychainItem, SecKeychainItemRef +} +impl_TCFType! { + SecKeychainItem, + SecKeychainItemRef, + SecKeychainItemGetTypeID +} + +impl SecKeychainItem { + /// + /// delete + /// Attempts to delete this keychain item from the keychain. + /// + /// Returns: + /// - Nothing if the deletion request was successful, or + /// - An `Error` object if an error was encountered + /// + #[inline] + pub fn delete(self) -> Result<(), Error> { + unsafe { handle_os_status(SecKeychainItemDelete(self.as_CFTypeRef() as *mut _)) } + } + + /// + /// set_password + /// Attempts to set the password for this keychain item. + /// + /// Returns: + /// - Nothing if the password was set successfully, or + /// - An `Error` object if an error was encountered + /// + pub fn set_password(&mut self, password: &[u8]) -> Result<(), Error> { + unsafe { + handle_os_status(SecKeychainItemModifyAttributesAndData( + self.as_CFTypeRef() as *mut _, + std::ptr::null(), + password.len() as u32, + password.as_ptr().cast(), + ))?; + } + + Ok(()) + } +} diff --git a/src/secrets/src/keyring/src/os/mac/keychain_search.rs b/src/secrets/src/keyring/src/os/mac/keychain_search.rs new file mode 100644 index 00000000..8de998df --- /dev/null +++ b/src/secrets/src/keyring/src/os/mac/keychain_search.rs @@ -0,0 +1,249 @@ +use crate::os::mac::error::{handle_os_status, Error}; +use crate::os::mac::ffi::{ + kSecAttrAccount, kSecAttrLabel, kSecAttrService, kSecClass, kSecClassGenericPassword, + kSecMatchLimit, kSecReturnAttributes, kSecReturnData, kSecReturnRef, SecItemCopyMatching, +}; +use crate::os::mac::keychain_item::SecKeychainItem; +use crate::os::mac::misc::{SecCertificate, SecIdentity, SecKey}; +use core_foundation::array::CFArray; +use core_foundation::base::{CFType, TCFType}; +use core_foundation::boolean::CFBoolean; +use core_foundation::data::CFData; +use core_foundation::date::CFDate; +use core_foundation::dictionary::CFDictionary; +use core_foundation::number::CFNumber; +use core_foundation::string::CFString; +use core_foundation_sys::base::{CFCopyDescription, CFGetTypeID, CFRelease, CFTypeRef}; +use std::collections::HashMap; + +/// Keychain Search structure to reference when making searches within the keychain. +#[derive(Default)] +pub struct KeychainSearch { + label: Option, + service: Option, + account: Option, + load_attrs: bool, + load_data: bool, + load_refs: bool, +} + +/// Reference enum for categorizing search results based on item type. +pub enum Reference { + Identity(SecIdentity), + Certificate(SecCertificate), + Key(SecKey), + KeychainItem(SecKeychainItem), +} + +/// Enum for organizing types of items found during the keychain search operation. +pub enum SearchResult { + Ref(Reference), + Dict(CFDictionary), + Data(Vec), +} + +impl SearchResult { + /// + /// parse_dict + /// Tries to parse a CFDictionary object into a hashmap of string pairs. + /// + /// Returns: + /// - `Some(hash_map)` containing the attribute keys/values if parsed successfully + /// - `None` otherwise + #[must_use] + pub fn parse_dict(&self) -> Option> { + match *self { + Self::Dict(ref d) => unsafe { + // build map of attributes to return for this search result + let mut retmap = HashMap::new(); + let (keys, values) = d.get_keys_and_values(); + for (k, v) in keys.iter().zip(values.iter()) { + // get key as CFString from pointer + let key_cfstr = CFString::wrap_under_get_rule((*k).cast()); + + // get value based on CFType + let val: String = match CFGetTypeID(*v) { + cfstring if cfstring == CFString::type_id() => { + format!("{}", CFString::wrap_under_get_rule((*v).cast())) + } + cfdata if cfdata == CFData::type_id() => { + let buf = CFData::wrap_under_get_rule((*v).cast()); + let mut vec = Vec::new(); + vec.extend_from_slice(buf.bytes()); + format!("{}", String::from_utf8_lossy(&vec)) + } + cfdate if cfdate == CFDate::type_id() => format!( + "{}", + CFString::wrap_under_create_rule(CFCopyDescription(*v)) + ), + _ => String::from("unknown"), + }; + retmap.insert(format!("{}", key_cfstr), val); + } + Some(retmap) + }, + _ => None, + } + } +} + +/// +/// get_item +/// +/// item: The item reference to convert to a SearchResult +/// Returns: +/// - a SearchResult enum variant based on the item reference provided. +/// +unsafe fn get_item(item: CFTypeRef) -> SearchResult { + let type_id = CFGetTypeID(item); + + // if type is a raw buffer, return Vec of bytes based on item size + if type_id == CFData::type_id() { + let data = CFData::wrap_under_get_rule(item as *mut _); + let mut buf = Vec::new(); + buf.extend_from_slice(data.bytes()); + return SearchResult::Data(buf); + } + + // if type is dictionary of items, cast as CFDictionary object + if type_id == CFDictionary::<*const u8, *const u8>::type_id() { + return SearchResult::Dict(CFDictionary::wrap_under_get_rule(item as *mut _)); + } + + // if type is a single Keychain item, return it as a reference + if type_id == SecKeychainItem::type_id() { + return SearchResult::Ref(Reference::KeychainItem( + SecKeychainItem::wrap_under_get_rule(item as *mut _), + )); + } + + // handle certificate, cryptographic key, and identity types as + // they can also appear in search results for the keychain + let reference = match type_id { + r if r == SecCertificate::type_id() => { + Reference::Certificate(SecCertificate::wrap_under_get_rule(item as *mut _)) + } + r if r == SecKey::type_id() => Reference::Key(SecKey::wrap_under_get_rule(item as *mut _)), + r if r == SecIdentity::type_id() => { + Reference::Identity(SecIdentity::wrap_under_get_rule(item as *mut _)) + } + _ => panic!("Bad type received from SecItemCopyMatching: {}", type_id), + }; + + SearchResult::Ref(reference) +} + +impl KeychainSearch { + #[inline(always)] + #[must_use] + pub fn new() -> Self { + Self::default() + } + + pub fn label(&mut self, label: &str) -> &mut Self { + self.label = Some(CFString::new(label)); + self + } + + pub fn with_attrs(&mut self) -> &mut Self { + self.load_attrs = true; + self + } + + pub fn with_data(&mut self) -> &mut Self { + self.load_data = true; + self + } + + pub fn with_refs(&mut self) -> &mut Self { + self.load_refs = true; + self + } + + /// Executes a search within the keychain, factoring in the set search options. + /// + /// Returns: + /// - If successful, a `Vec` containing a list of search results + /// - an `Error` object otherwise + pub fn execute(&self) -> Result, Error> { + let mut params = vec![]; + + unsafe { + params.push(( + CFString::wrap_under_get_rule(kSecClass), + CFType::wrap_under_get_rule(kSecClassGenericPassword.cast()), + )); + + // Handle any parameters that were configured before execution (label, service, account) + if let Some(ref label) = self.label { + params.push(( + CFString::wrap_under_get_rule(kSecAttrLabel), + label.as_CFType(), + )); + } + if let Some(ref service) = self.service { + params.push(( + CFString::wrap_under_get_rule(kSecAttrService), + service.as_CFType(), + )); + } + if let Some(ref acc) = self.account { + params.push(( + CFString::wrap_under_get_rule(kSecAttrAccount), + acc.as_CFType(), + )); + } + + // Add params to fetch data, attributes, and/or refs if requested from search options + if self.load_data { + params.push(( + CFString::wrap_under_get_rule(kSecReturnData), + CFBoolean::true_value().into_CFType(), + )); + } + if self.load_attrs { + params.push(( + CFString::wrap_under_get_rule(kSecReturnAttributes), + CFBoolean::true_value().into_CFType(), + )); + } + if self.load_refs { + params.push(( + CFString::wrap_under_get_rule(kSecReturnRef), + CFBoolean::true_value().into_CFType(), + )); + } + + // Remove the default limit of 0 by requesting all items that match the search + params.push(( + CFString::wrap_under_get_rule(kSecMatchLimit), + CFNumber::from(i32::MAX).into_CFType(), + )); + + let params = CFDictionary::from_CFType_pairs(¶ms); + let mut ret = std::ptr::null(); + + // handle copy operation status and get type ID based on return value + handle_os_status(SecItemCopyMatching(params.as_concrete_TypeRef(), &mut ret))?; + if ret.is_null() { + return Ok(vec![]); + } + let type_id = CFGetTypeID(ret); + + // Build vector of items based on return reference type + let mut items = vec![]; + if type_id == CFArray::::type_id() { + let array: CFArray = CFArray::wrap_under_create_rule(ret as *mut _); + for item in array.iter() { + items.push(get_item(item.as_CFTypeRef())); + } + } else { + items.push(get_item(ret)); + + CFRelease(ret); + } + + Ok(items) + } + } +} diff --git a/src/secrets/src/keyring/src/os/mac/misc.rs b/src/secrets/src/keyring/src/os/mac/misc.rs new file mode 100644 index 00000000..1af69da4 --- /dev/null +++ b/src/secrets/src/keyring/src/os/mac/misc.rs @@ -0,0 +1,21 @@ +use crate::os::mac::ffi::{ + SecCertificateGetTypeID, SecCertificateRef, SecIdentityGetTypeID, SecIdentityRef, + SecKeyGetTypeID, SecKeyRef, +}; +use core_foundation::base::TCFType; +use core_foundation::{declare_TCFType, impl_TCFType}; + +// Structure that represents identities within the keychain +// https://developer.apple.com/documentation/security/secidentity +declare_TCFType!(SecIdentity, SecIdentityRef); +impl_TCFType!(SecIdentity, SecIdentityRef, SecIdentityGetTypeID); + +// Structure that represents certificates within the keychain +// https://developer.apple.com/documentation/security/seccertificate +declare_TCFType!(SecCertificate, SecCertificateRef); +impl_TCFType!(SecCertificate, SecCertificateRef, SecCertificateGetTypeID); + +// Structure that represents cryptographic keys within the keychain +// https://developer.apple.com/documentation/security/seckey +declare_TCFType!(SecKey, SecKeyRef); +impl_TCFType!(SecKey, SecKeyRef, SecKeyGetTypeID); diff --git a/src/secrets/src/keyring/src/os/mac/mod.rs b/src/secrets/src/keyring/src/os/mac/mod.rs new file mode 100644 index 00000000..3ff76377 --- /dev/null +++ b/src/secrets/src/keyring/src/os/mac/mod.rs @@ -0,0 +1,160 @@ +use super::error::KeyringError; + +mod error; +mod ffi; +mod keychain; +mod keychain_item; +mod keychain_search; +mod misc; + +use error::Error; + +use crate::os::mac::error::ERR_SEC_ITEM_NOT_FOUND; +use crate::os::mac::keychain_search::{KeychainSearch, SearchResult}; +use keychain::SecKeychain; + +impl From for KeyringError { + fn from(error: Error) -> Self { + KeyringError::Library { + name: "macOS Security.framework".to_owned(), + details: format!("{:?}", error.message()), + } + } +} + +/// +/// Attempts to set a password for a given service and account. +/// +/// - `service`: The service name for the new credential +/// - `account`: The account name for the new credential +/// +/// Returns: +/// - `true` if the credential was stored successfully +/// - A `KeyringError` if there were any issues interacting with the credential vault +/// +pub fn set_password( + service: &String, + account: &String, + password: &mut String, +) -> Result { + let keychain = SecKeychain::default().unwrap(); + match keychain.set_password(service.as_str(), account.as_str(), password.as_bytes()) { + Ok(()) => Ok(true), + Err(err) => Err(KeyringError::from(err)), + } +} + +/// +/// Returns a password contained in the given service and account, if found. +/// +/// - `service`: The service name that matches the credential of interest +/// - `account`: The account name that matches the credential of interest +/// +/// Returns: +/// - `Some(password)` if a matching credential was found; `None` otherwise +/// - A `KeyringError` if there were any issues interacting with the credential vault +/// +pub fn get_password(service: &String, account: &String) -> Result, KeyringError> { + let keychain = SecKeychain::default().unwrap(); + match keychain.find_password(service.as_str(), account.as_str()) { + Ok((pw, _)) => Ok(Some(String::from_utf8(pw.to_owned())?)), + Err(err) if err.code() == ERR_SEC_ITEM_NOT_FOUND => Ok(None), + Err(err) => Err(KeyringError::from(err)), + } +} + +/// +/// Returns the first password (if any) that matches the given service pattern. +/// +/// - `service`: The service pattern that matches the credential of interest +/// +/// Returns: +/// - `Some(password)` if a matching credential was found; `None` otherwise +/// - A `KeyringError` if there were any issues interacting with the credential vault +/// +pub fn find_password(service: &String) -> Result, KeyringError> { + let cred_attrs: Vec<&str> = service.split("/").collect(); + if cred_attrs.len() < 2 { + return Err(KeyringError::InvalidArg { + argument: "service".to_owned(), + details: "Invalid format for service string; must be in format 'SERVICE/ACCOUNT'" + .to_owned(), + }); + } + + let keychain = SecKeychain::default().unwrap(); + match keychain.find_password(cred_attrs[0], cred_attrs[1]) { + Ok((pw, _)) => { + let pw_str = String::from_utf8(pw.to_owned())?; + return Ok(Some(pw_str)); + } + Err(err) => Err(KeyringError::from(err)), + } +} + +/// +/// Attempts to delete the password associated with a given service and account. +/// +/// - `service`: The service name of the credential to delete +/// - `account`: The account name of the credential to delete +/// +/// Returns: +/// - `true` if a matching credential was deleted; `false` otherwise +/// - A `KeyringError` if there were any issues interacting with the credential vault +/// +pub fn delete_password(service: &String, account: &String) -> Result { + let keychain = SecKeychain::default().unwrap(); + match keychain.find_password(service.as_str(), account.as_str()) { + Ok((_, item)) => { + item.delete()?; + return Ok(true); + } + Err(err) if err.code() == ERR_SEC_ITEM_NOT_FOUND => Ok(false), + Err(err) => Err(KeyringError::from(err)), + } +} + +/// +/// Builds a vector of all credentials matching the given service pattern. +/// +/// - `service`: The service pattern that matches the credential(s) of interest +/// - `credentials`: The vector consisting of (username, password) pairs for each credential that matches +/// +/// Returns: +/// - `true` if at least 1 credential was found, `false` otherwise +/// - A `KeyringError` if there were any issues interacting with the credential vault +/// +pub fn find_credentials( + service: &String, + credentials: &mut Vec<(String, String)>, +) -> Result { + match KeychainSearch::new() + .label(service.as_str()) + .with_attrs() + .with_data() + .with_refs() + .execute() + { + Ok(search_results) => { + *credentials = search_results + .iter() + .filter_map(|result| match result { + SearchResult::Dict(_) => { + return match result.parse_dict() { + Some(attrs) => Some(( + attrs.get("acct").unwrap().to_owned(), + attrs.get("v_Data").unwrap().to_owned(), + )), + None => None, + }; + } + _ => None, + }) + .collect(); + + Ok(!credentials.is_empty()) + } + Err(err) if err.code() == ERR_SEC_ITEM_NOT_FOUND => Ok(false), + Err(err) => Err(KeyringError::from(err)), + } +} diff --git a/src/secrets/src/keyring/src/os/mod.rs b/src/secrets/src/keyring/src/os/mod.rs new file mode 100644 index 00000000..640ee83f --- /dev/null +++ b/src/secrets/src/keyring/src/os/mod.rs @@ -0,0 +1,14 @@ +pub mod error; + +cfg_if::cfg_if! { + if #[cfg(target_os = "windows")] { + pub mod win; + pub use win::{delete_password, find_credentials, find_password, get_password, set_password}; + } else if #[cfg(target_os = "macos")] { + pub mod mac; + pub use mac::{delete_password, find_credentials, find_password, get_password, set_password}; + } else if #[cfg(any(target_os = "freebsd", target_os = "linux"))] { + pub mod unix; + pub use unix::{delete_password, find_credentials, find_password, get_password, set_password}; + } +} diff --git a/src/secrets/src/keyring/src/os/unix.rs b/src/secrets/src/keyring/src/os/unix.rs new file mode 100644 index 00000000..723817a7 --- /dev/null +++ b/src/secrets/src/keyring/src/os/unix.rs @@ -0,0 +1,221 @@ +extern crate glib_sys; +extern crate libsecret; +use glib::translate::{FromGlibPtrContainer, ToGlibPtr}; +use glib_sys::g_hash_table_unref; +use libsecret::{ + prelude::CollectionExtManual, traits::ItemExt, SearchFlags, Service, ServiceFlags, +}; +use std::collections::HashMap; + +use super::error::KeyringError; + +impl From for KeyringError { + fn from(err: glib::error::Error) -> Self { + KeyringError::Library { + name: "glib".to_owned(), + details: format!("{:?}", err.message().to_owned()), + } + } +} + +/// +/// Returns the libsecret schema that corresponds to service and account attributes. +/// +fn get_schema() -> libsecret::Schema { + libsecret::Schema::new( + "org.freedesktop.Secret.Generic", + libsecret::SchemaFlags::NONE, + HashMap::from([ + ("service", libsecret::SchemaAttributeType::String), + ("account", libsecret::SchemaAttributeType::String), + ]), + ) +} + +/// +/// Builds an attribute map with the given service and account names. +/// +/// Returns: +/// - A `HashMap` built with the `service` and `account` values provided. Used for attribute functions. +/// +fn get_attribute_map<'a>(service: &'a str, account: &'a str) -> HashMap<&'a str, &'a str> { + HashMap::from([("service", service), ("account", account)]) +} + +/// +/// Attempts to set a password for a given service and account. +/// +/// - `service`: The service name for the new credential +/// - `account`: The account name for the new credential +/// +/// Returns: +/// - `true` if the credential was stored successfully +/// - A `KeyringError` if there were any issues interacting with the credential vault +/// +pub fn set_password( + service: &String, + account: &String, + password: &String, +) -> Result { + let attributes = get_attribute_map(service.as_str(), account.as_str()); + + let collection = libsecret::COLLECTION_DEFAULT; + match libsecret::password_store_sync( + Some(&get_schema()), + attributes, + Some(collection), + format!("{}/{}", service, account).as_str(), + password.as_str(), + gio::Cancellable::NONE, + ) { + Ok(_) => Ok(true), + Err(err) => Err(KeyringError::from(err)), + } +} + +/// +/// Returns a password contained in the given service and account, if found. +/// +/// - `service`: The service name that matches the credential of interest +/// - `account`: The account name that matches the credential of interest +/// +/// Returns: +/// - `Some(password)` if a matching credential was found; `None` otherwise +/// - A `KeyringError` if there were any issues interacting with the credential vault +/// +pub fn get_password(service: &String, account: &String) -> Result, KeyringError> { + let attributes = get_attribute_map(service.as_str(), account.as_str()); + + match libsecret::password_lookup_sync(Some(&get_schema()), attributes, gio::Cancellable::NONE) { + Ok(pw) => match pw { + Some(pass) => Ok(Some(pass.to_string())), + None => Ok(None), + }, + Err(err) => Err(KeyringError::from(err)), + } +} + +/// +/// Returns the first password (if any) that matches the given service pattern. +/// +/// - `service`: The service pattern that matches the credential of interest +/// +/// Returns: +/// - `Some(password)` if a matching credential was found; `None` otherwise +/// - A `KeyringError` if there were any issues interacting with the credential vault +/// +pub fn find_password(service: &String) -> Result, KeyringError> { + let attributes = if service.contains("/") && service.len() > 1 { + // In format "service/account" + let values: Vec<&str> = service.split("/").collect(); + get_attribute_map(values[0], values[1]) + } else { + HashMap::from([("service", service.as_str())]) + }; + + match libsecret::password_lookup_sync(Some(&get_schema()), attributes, gio::Cancellable::NONE) { + Ok(pw) => match pw { + Some(pass) => Ok(Some(pass.to_string())), + None => Ok(None), + }, + Err(err) => Err(KeyringError::from(err)), + } +} + +/// +/// Attempts to delete the password associated with a given service and account. +/// +/// - `service`: The service name of the credential to delete +/// - `account`: The account name of the credential to delete +/// +/// Returns: +/// - `true` if a matching credential was deleted; `false` otherwise +/// - A `KeyringError` if there were any issues interacting with the credential vault +/// +pub fn delete_password(service: &String, account: &String) -> Result { + match libsecret::password_clear_sync( + Some(&get_schema()), + get_attribute_map(service.as_str(), account.as_str()), + gio::Cancellable::NONE, + ) { + Ok(_) => Ok(true), + Err(err) => match err.kind() { + Some(glib::KeyFileError::NotFound) => Ok(false), + _ => Err(KeyringError::from(err)), + }, + } +} + +/// +/// Builds a vector of all credentials matching the given service pattern. +/// +/// - `service`: The service pattern that matches the credential(s) of interest +/// - `credentials`: The vector consisting of (username, password) pairs for each credential that matches +/// +/// Returns: +/// - `true` if at least 1 credential was found, `false` otherwise +/// - A `KeyringError` if there were any issues interacting with the credential vault +/// +pub fn find_credentials( + service: &String, + credentials: &mut Vec<(String, String)>, +) -> Result { + let secret_service = Service::sync( + ServiceFlags::OPEN_SESSION | ServiceFlags::LOAD_COLLECTIONS, + gio::Cancellable::NONE, + )?; + let collection = match libsecret::Collection::for_alias_sync( + Some(&secret_service), + "default", + libsecret::CollectionFlags::LOAD_ITEMS, + gio::Cancellable::NONE, + )? { + Some(col) => col, + None => { + return Err(KeyringError::Os( + "Unable to open libsecret collection".to_owned(), + )) + } + }; + + match collection.search_sync( + Some(&get_schema()), + HashMap::from([("service", service.as_str())]), + SearchFlags::ALL | SearchFlags::UNLOCK | SearchFlags::LOAD_SECRETS, + gio::Cancellable::NONE, + ) { + Ok(vec) => { + let valid_creds: Vec<(String, String)> = vec + .iter() + .filter_map(|item| match item.secret() { + Some(secret) => { + let attrs: HashMap = unsafe { + let attrs = + libsecret_sys::secret_item_get_attributes(item.to_glib_none().0); + FromGlibPtrContainer::from_glib_full(attrs) + }; + let bytes = secret.get(); + let pw = String::from_utf8(bytes).unwrap_or("".to_string()); + + let acc = attrs.get("account").unwrap().clone(); + unsafe { + g_hash_table_unref(attrs.to_glib_full()); + } + Some((acc, pw)) + } + None => None, + }) + .collect(); + *credentials = valid_creds; + + Ok(true) + } + Err(err) => { + if err.message().contains("No such secret item at path") { + Ok(false) + } else { + Err(KeyringError::Os(err.message().to_owned())) + } + } + } +} diff --git a/src/secrets/src/keyring/src/os/win.rs b/src/secrets/src/keyring/src/os/win.rs new file mode 100644 index 00000000..a0d2742d --- /dev/null +++ b/src/secrets/src/keyring/src/os/win.rs @@ -0,0 +1,340 @@ +use super::error::KeyringError; +use std::ffi::c_void; +use std::result::Result; +use windows_sys::{ + core::{PCWSTR, PWSTR}, + Win32::Foundation::*, + Win32::Security::Credentials::*, + Win32::System::{ + Diagnostics::Debug::{ + FormatMessageW, FORMAT_MESSAGE_ALLOCATE_BUFFER, FORMAT_MESSAGE_FROM_SYSTEM, + FORMAT_MESSAGE_IGNORE_INSERTS, + }, + Memory::LocalFree, + }, +}; + +impl From for KeyringError { + fn from(error: WIN32_ERROR) -> Self { + KeyringError::Os(win32_error_as_string(error)) + } +} + +/// +/// Helper function to convert the last Win32 error into a human-readable error message. +/// +/// Returns: +/// A `String` object containing the error message +/// +fn win32_error_as_string(error: WIN32_ERROR) -> String { + let buffer: PWSTR = std::ptr::null_mut(); + + // https://github.com/microsoft/windows-rs/blob/master/crates/libs/core/src/hresult.rs#L96 + let as_hresult = if error == 0 { + 0 + } else { + (error & 0x0000_FFFF) | (7 << 16) | 0x8000_0000 + } as _; + let mut str = "No error details available.".to_owned(); + unsafe { + let size = FormatMessageW( + FORMAT_MESSAGE_ALLOCATE_BUFFER + | FORMAT_MESSAGE_FROM_SYSTEM + | FORMAT_MESSAGE_IGNORE_INSERTS, + as_hresult as *const c_void, + as_hresult, + 0, + buffer, + 0, + std::ptr::null(), + ); + + if buffer.is_null() { + return str; + } + + str = String::from_utf16(std::slice::from_raw_parts(buffer, size as usize)).unwrap_or(str); + LocalFree(buffer as isize); + } + + str +} + +/// +/// Helper function to encode a string as a null-terminated UTF-16 string for use w/ credential APIs. +/// +/// Returns: +/// - `Some(val)` if the string was successfully converted to UTF-16, or `None` otherwise. +/// +fn encode_utf16(str: &str) -> Vec { + let mut chars: Vec = str.encode_utf16().collect(); + chars.push(0); + chars +} + +/// +/// Attempts to set a password for a given service and account. +/// +/// - `service`: The service name for the new credential +/// - `account`: The account name for the new credential +/// +/// Returns: +/// - `true` if the credential was stored successfully +/// - A `KeyringError` if there were any issues interacting with the credential vault +/// +pub fn set_password( + service: &String, + account: &String, + password: &mut String, +) -> Result { + // Build WinAPI strings and object parameters from arguments + let target_bytes = encode_utf16(format!("{}/{}", service, account).as_str()); + let username_bytes = encode_utf16(account.as_str()); + + let cred = CREDENTIALW { + Flags: 0, + Type: CRED_TYPE_GENERIC, + TargetName: target_bytes.as_ptr() as PWSTR, + Comment: std::ptr::null_mut(), + LastWritten: FILETIME { + dwLowDateTime: 0, + dwHighDateTime: 0, + }, + Persist: CRED_PERSIST_ENTERPRISE, + CredentialBlobSize: password.len() as u32, + CredentialBlob: password.as_ptr() as *mut u8, + AttributeCount: 0, + Attributes: std::ptr::null_mut(), + TargetAlias: std::ptr::null_mut(), + UserName: username_bytes.as_ptr() as PWSTR, + }; + + // Save credential to user's credential set + let write_result = unsafe { CredWriteW(&cred, 0) }; + + if write_result != TRUE { + let error_code = unsafe { GetLastError() }; + return Err(KeyringError::from(error_code)); + } + + Ok(true) +} + +/// +/// Returns a password contained in the given service and account, if found. +/// +/// - `service`: The service name that matches the credential of interest +/// - `account`: The account name that matches the credential of interest +/// +/// Returns: +/// - `Some(password)` if a matching credential was found; `None` otherwise +/// - A `KeyringError` if there were any issues interacting with the credential vault +/// +pub fn get_password(service: &String, account: &String) -> Result, KeyringError> { + let mut cred: *mut CREDENTIALW = std::ptr::null_mut::(); + let target_name = encode_utf16(format!("{}/{}", service, account).as_str()); + + // Attempt to read credential from user's credential set + let read_result = unsafe { + CredReadW( + target_name.as_ptr() as PCWSTR, + CRED_TYPE_GENERIC, + 0, + &mut cred, + ) + }; + + if read_result != TRUE { + let error_code = unsafe { GetLastError() }; + if cred != std::ptr::null_mut() { + unsafe { + CredFree(cred as *const c_void); + } + } + return match error_code { + ERROR_NOT_FOUND => Ok(None), + _ => Err(KeyringError::from(error_code)), + }; + } + + // Build buffer for credential secret and return as UTF-8 string + unsafe { + let bytes = + std::slice::from_raw_parts((*cred).CredentialBlob, (*cred).CredentialBlobSize as usize); + + let result = match String::from_utf8(bytes.to_vec()) { + Ok(string) => Ok(Some(string)), + Err(err) => Err(KeyringError::Utf8( + format!("Failed to convert credential to UTF-8: {}", err).to_owned(), + )), + }; + CredFree(cred as *const c_void); + result + } +} + +/// +/// Attempts to delete the password associated with a given service and account. +/// +/// - `service`: The service name of the credential to delete +/// - `account`: The account name of the credential to delete +/// +/// Returns: +/// - `true` if a matching credential was deleted; `false` otherwise +/// - A `KeyringError` if there were any issues interacting with the credential vault +/// +pub fn delete_password(service: &String, account: &String) -> Result { + let target_name = encode_utf16(format!("{}/{}", service, account).as_str()); + + // Attempt to delete credential from user's credential set + let delete_result = + unsafe { CredDeleteW(target_name.as_ptr() as PCWSTR, CRED_TYPE_GENERIC, 0) }; + + if delete_result != TRUE { + let error_code = unsafe { GetLastError() }; + + return match error_code { + // If we are trying to delete a credential that doesn't exist, + // we didn't actually delete the password + ERROR_NOT_FOUND => Ok(false), + _ => Err(KeyringError::from(error_code)), + }; + } + + Ok(true) +} + +/// +/// Returns the first password (if any) that matches the given service pattern. +/// +/// - `service`: The service pattern that matches the credential of interest +/// +/// Returns: +/// - `Some(password)` if a matching credential was found; `None` otherwise +/// - A `KeyringError` if there were any issues interacting with the credential vault +/// +pub fn find_password(service: &String) -> Result, KeyringError> { + let filter = encode_utf16(format!("{}*", service).as_str()); + + let mut count: u32 = 0; + let mut creds: *mut *mut CREDENTIALW = std::ptr::null_mut::<*mut CREDENTIALW>(); + + // Attempt to find matching credential from user's credential set + let find_result = unsafe { + CredEnumerateW( + filter.as_ptr() as PCWSTR, + 0u32, + &mut count, + &mut creds as *mut *mut *mut CREDENTIALW, + ) + }; + + if find_result != TRUE { + let error_code = unsafe { GetLastError() }; + if creds != std::ptr::null_mut() { + unsafe { + CredFree(creds as *const c_void); + } + } + return match error_code { + ERROR_NOT_FOUND => Ok(None), + _ => Err(KeyringError::from(error_code)), + }; + } + + // Convert credential data into a valid String object and return. + unsafe { + let cred = *creds.offset(0); + let bytes = + std::slice::from_raw_parts((*cred).CredentialBlob, (*cred).CredentialBlobSize as usize); + + let result = match String::from_utf8(bytes.to_vec()) { + Ok(string) => Ok(Some(string)), + Err(err) => Err(KeyringError::from(err)), + }; + CredFree(creds as *const c_void); + result + } +} + +/// +/// Builds a vector of all credentials matching the given service pattern. +/// +/// - `service`: The service pattern that matches the credential(s) of interest +/// - `credentials`: The vector consisting of (username, password) pairs for each credential that matches +/// +/// Returns: +/// - `true` if at least 1 credential was found, `false` otherwise +/// - A `KeyringError` if there were any issues interacting with the credential vault +/// +pub fn find_credentials( + service: &String, + credentials: &mut Vec<(String, String)>, +) -> Result { + let filter_bytes: Vec = encode_utf16(format!("{}*", service).as_str()); + let filter = filter_bytes.as_ptr() as PCWSTR; + + let mut count: u32 = 0; + let mut creds: *mut *mut CREDENTIALW = std::ptr::null_mut::<*mut CREDENTIALW>(); + + // Attempt to fetch user's credential set + let find_result = unsafe { + CredEnumerateW( + filter, + 0u32, + &mut count, + &mut creds as *mut *mut *mut CREDENTIALW, + ) + }; + + if find_result != TRUE { + let error_code = unsafe { GetLastError() }; + if creds != std::ptr::null_mut() { + unsafe { + CredFree(creds as *const c_void); + } + } + return match error_code { + ERROR_NOT_FOUND => Ok(false), + _ => Err(KeyringError::from(error_code)), + }; + } + + // Find and build matching credential list from user's credential set + for i in 0..count { + let cred: &CREDENTIALW = unsafe { &**creds.offset(i as isize) }; + + if cred.UserName.is_null() || cred.CredentialBlobSize == 0 { + continue; + } + + // Build a valid String from the raw *u8 credential data. + let pw_bytes = unsafe { + std::slice::from_raw_parts((*cred).CredentialBlob, (*cred).CredentialBlobSize as usize) + }; + let password_result = match String::from_utf8(pw_bytes.to_vec()) { + Ok(string) => Ok(string), + Err(err) => Err(KeyringError::from(err)), + }; + if password_result.is_err() { + unsafe { + CredFree(creds as *const c_void); + } + return Err(password_result.unwrap_err()); + } + + // Decode the raw wchar_t* username data as a UTF-16 String + let username: String; + unsafe { + let size = (0..).take_while(|&i| *cred.UserName.offset(i) != 0).count(); + username = String::from_utf16(std::slice::from_raw_parts(cred.UserName, size))?; + } + credentials.push((username, password_result.unwrap())); + } + + unsafe { + CredFree(creds as *const c_void); + } + + Ok(true) +} diff --git a/src/secrets/src/keyring/src/workers.rs b/src/secrets/src/keyring/src/workers.rs new file mode 100644 index 00000000..88ee339d --- /dev/null +++ b/src/secrets/src/keyring/src/workers.rs @@ -0,0 +1,153 @@ +use napi::{Env, Error, JsBoolean, JsUnknown, Result, Task}; +use napi_derive::napi; + +use crate::os; + +pub struct SetPassword { + pub service: String, + pub account: String, + pub password: String, +} + +pub struct GetPassword { + pub service: String, + pub account: String, +} + +pub struct DeletePassword { + pub service: String, + pub account: String, +} + +pub struct FindCredentials { + pub service: String, +} +pub struct FindPassword { + pub service: String, +} + +#[napi(object)] +pub struct Credential { + pub account: String, + pub password: String, +} + +#[napi] +impl Task for GetPassword { + type Output = Option; + type JsValue = JsUnknown; + + fn compute(&mut self) -> Result { + match os::get_password(&self.service, &self.account) { + Ok(pw) => Ok(pw), + Err(err) => Err(napi::Error::from_reason(err.to_string())), + } + } + + fn resolve(&mut self, env: Env, output: Self::Output) -> Result { + Ok(match output { + Some(pw) => env.create_string(pw.as_str())?.into_unknown(), + None => env.get_null()?.into_unknown(), + }) + } + + fn reject(&mut self, _env: Env, err: Error) -> Result { + Err(err) + } +} + +#[napi] +impl Task for SetPassword { + type Output = bool; + type JsValue = JsUnknown; + + fn compute(&mut self) -> Result { + match os::set_password(&self.service, &self.account, &mut self.password) { + Ok(result) => Ok(result), + Err(err) => Err(napi::Error::from_reason(err.to_string())), + } + } + + fn resolve(&mut self, env: Env, _output: Self::Output) -> Result { + Ok(env.get_null()?.into_unknown()) + } + + fn reject(&mut self, _env: Env, err: Error) -> Result { + Err(err) + } +} + +#[napi] +impl Task for DeletePassword { + type Output = bool; + type JsValue = JsBoolean; + + fn compute(&mut self) -> Result { + match os::delete_password(&self.service, &self.account) { + Ok(result) => Ok(result), + Err(err) => Err(napi::Error::from_reason(err.to_string())), + } + } + + fn resolve(&mut self, env: Env, output: Self::Output) -> Result { + env.get_boolean(output) + } + + fn reject(&mut self, _env: Env, err: Error) -> Result { + Err(err) + } +} + +#[napi] +impl Task for FindCredentials { + type Output = Vec<(String, String)>; + type JsValue = Vec; + + fn compute(&mut self) -> Result { + let mut credentials: Self::Output = Vec::new(); + match os::find_credentials(&self.service, &mut credentials) { + Ok(_result) => Ok(credentials), + Err(err) => Err(napi::Error::from_reason(err.to_string())), + } + } + + fn resolve(&mut self, _env: Env, output: Self::Output) -> Result { + let mut creds = Vec::new(); + for cred in output { + creds.push(Credential { + account: cred.0, + password: cred.1, + }) + } + + Ok(creds) + } + + fn reject(&mut self, _env: Env, err: Error) -> Result { + Err(err) + } +} + +#[napi] +impl Task for FindPassword { + type Output = Option; + type JsValue = JsUnknown; + + fn compute(&mut self) -> Result { + match os::find_password(&self.service) { + Ok(pw) => Ok(pw), + Err(err) => Err(napi::Error::from_reason(err.to_string())), + } + } + + fn resolve(&mut self, env: Env, output: Self::Output) -> Result { + Ok(match output { + Some(pw) => env.create_string(pw.as_str())?.into_unknown(), + None => env.get_null()?.into_unknown(), + }) + } + + fn reject(&mut self, _env: Env, err: Error) -> Result { + Err(err) + } +} diff --git a/src/secrets/tsconfig.json b/src/secrets/tsconfig.json new file mode 100644 index 00000000..0b4bd5f7 --- /dev/null +++ b/src/secrets/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./lib", + "types": [ + "node", + ] + }, + "include": [ + "./src" + ], + "exclude": [ + "./src/keyring" + ] +} \ No newline at end of file From c0fbaec43f55e65c017b73c503fc4c1b297fd119 Mon Sep 17 00:00:00 2001 From: Trae Yelovich Date: Tue, 31 Oct 2023 11:53:20 -0400 Subject: [PATCH 02/45] feat(secrets): Create PoC Python bindings that use secrets-core Signed-off-by: Trae Yelovich --- src/secrets/src/{keyring => core}/Cargo.lock | 343 ++++++------------ src/secrets/src/{keyring => core}/Cargo.toml | 24 +- src/secrets/src/core/src/lib.rs | 1 + .../src/{keyring => core}/src/os/error.rs | 0 .../src/{keyring => core}/src/os/mac/error.rs | 0 .../src/{keyring => core}/src/os/mac/ffi.rs | 0 .../{keyring => core}/src/os/mac/keychain.rs | 0 .../src/os/mac/keychain_item.rs | 0 .../src/os/mac/keychain_search.rs | 0 .../src/{keyring => core}/src/os/mac/misc.rs | 0 .../src/{keyring => core}/src/os/mac/mod.rs | 0 .../src/{keyring => core}/src/os/mod.rs | 0 .../src/{keyring => core}/src/os/unix.rs | 0 .../src/{keyring => core}/src/os/win.rs | 52 +-- src/secrets/src/keyring-test/Cargo.lock | 169 +-------- src/secrets/src/keyring-test/Cargo.toml | 2 +- src/secrets/src/keyring-test/src/lib.rs | 52 +-- src/secrets/src/keyring/.cargo/config.toml | 2 - src/secrets/src/keyring/.npmignore | 13 - src/secrets/src/keyring/Cross.toml | 37 -- src/secrets/src/keyring/EXTENDERS.md | 140 ------- src/secrets/src/keyring/README.md | 177 --------- .../src/keyring/__test__/index.spec.mjs | 312 ---------------- src/secrets/src/keyring/build.rs | 5 - src/secrets/src/keyring/index.d.ts | 14 - src/secrets/src/keyring/index.js | 66 ---- src/secrets/src/keyring/napi.json | 21 -- src/secrets/src/keyring/rustfmt.toml | 2 - src/secrets/src/keyring/src/lib.rs | 35 -- src/secrets/src/keyring/src/workers.rs | 153 -------- 30 files changed, 189 insertions(+), 1431 deletions(-) rename src/secrets/src/{keyring => core}/Cargo.lock (63%) rename src/secrets/src/{keyring => core}/Cargo.toml (53%) create mode 100644 src/secrets/src/core/src/lib.rs rename src/secrets/src/{keyring => core}/src/os/error.rs (100%) rename src/secrets/src/{keyring => core}/src/os/mac/error.rs (100%) rename src/secrets/src/{keyring => core}/src/os/mac/ffi.rs (100%) rename src/secrets/src/{keyring => core}/src/os/mac/keychain.rs (100%) rename src/secrets/src/{keyring => core}/src/os/mac/keychain_item.rs (100%) rename src/secrets/src/{keyring => core}/src/os/mac/keychain_search.rs (100%) rename src/secrets/src/{keyring => core}/src/os/mac/misc.rs (100%) rename src/secrets/src/{keyring => core}/src/os/mac/mod.rs (100%) rename src/secrets/src/{keyring => core}/src/os/mod.rs (100%) rename src/secrets/src/{keyring => core}/src/os/unix.rs (100%) rename src/secrets/src/{keyring => core}/src/os/win.rs (98%) delete mode 100644 src/secrets/src/keyring/.cargo/config.toml delete mode 100644 src/secrets/src/keyring/.npmignore delete mode 100644 src/secrets/src/keyring/Cross.toml delete mode 100644 src/secrets/src/keyring/EXTENDERS.md delete mode 100644 src/secrets/src/keyring/README.md delete mode 100644 src/secrets/src/keyring/__test__/index.spec.mjs delete mode 100644 src/secrets/src/keyring/build.rs delete mode 100644 src/secrets/src/keyring/index.d.ts delete mode 100644 src/secrets/src/keyring/index.js delete mode 100644 src/secrets/src/keyring/napi.json delete mode 100644 src/secrets/src/keyring/rustfmt.toml delete mode 100644 src/secrets/src/keyring/src/lib.rs delete mode 100644 src/secrets/src/keyring/src/workers.rs diff --git a/src/secrets/src/keyring/Cargo.lock b/src/secrets/src/core/Cargo.lock similarity index 63% rename from src/secrets/src/keyring/Cargo.lock rename to src/secrets/src/core/Cargo.lock index ca9f2a10..fa08d203 100644 --- a/src/secrets/src/keyring/Cargo.lock +++ b/src/secrets/src/core/Cargo.lock @@ -2,15 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "aho-corasick" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" -dependencies = [ - "memchr", -] - [[package]] name = "autocfg" version = "1.1.0" @@ -19,15 +10,15 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "bitflags" -version = "2.3.3" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" [[package]] name = "cfg-expr" -version = "0.15.4" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b40ccee03b5175c18cde8f37e7d2a33bcef6f8ec8f7cc0d81090d1bb380949c9" +checksum = "03915af431787e6ffdcc74c645077518c6b6e01f80b761e0fbbfa288536311b3" dependencies = [ "smallvec", "target-lexicon", @@ -39,15 +30,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "convert_case" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" -dependencies = [ - "unicode-segmentation", -] - [[package]] name = "core-foundation" version = "0.9.3" @@ -64,16 +46,6 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" -[[package]] -name = "ctor" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f34ba9a9bcb8645379e9de8cb3ecfcf4d1c85ba66d90deb3259206fa5aa193b" -dependencies = [ - "quote", - "syn 2.0.28", -] - [[package]] name = "equivalent" version = "1.0.1" @@ -82,24 +54,24 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "futures-channel" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" dependencies = [ "futures-core", ] [[package]] name = "futures-core" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" [[package]] name = "futures-executor" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" dependencies = [ "futures-core", "futures-task", @@ -108,32 +80,32 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" +checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" [[package]] name = "futures-macro" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.38", ] [[package]] name = "futures-task" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" +checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" [[package]] name = "futures-util" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" dependencies = [ "futures-core", "futures-macro", @@ -209,7 +181,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.38", ] [[package]] @@ -235,9 +207,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.0" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" [[package]] name = "heck" @@ -247,48 +219,19 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "indexmap" -version = "2.0.0" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" dependencies = [ "equivalent", "hashbrown", ] -[[package]] -name = "keyring" -version = "1.0.0" -dependencies = [ - "cfg-if", - "core-foundation", - "core-foundation-sys", - "gio", - "glib", - "glib-sys", - "libsecret", - "libsecret-sys", - "napi", - "napi-build", - "napi-derive", - "thiserror", - "windows-sys", -] - [[package]] name = "libc" -version = "0.2.147" +version = "0.2.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" - -[[package]] -name = "libloading" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" -dependencies = [ - "cfg-if", - "winapi", -] +checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" [[package]] name = "libsecret" @@ -318,66 +261,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" - -[[package]] -name = "napi" -version = "2.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ede2d12cd6fce44da537a4be1f5510c73be2506c2e32dfaaafd1f36968f3a0e" -dependencies = [ - "bitflags", - "ctor", - "napi-derive", - "napi-sys", - "once_cell", -] - -[[package]] -name = "napi-build" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "882a73d9ef23e8dc2ebbffb6a6ae2ef467c0f18ac10711e4cc59c5485d41df0e" - -[[package]] -name = "napi-derive" -version = "2.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da1c6a8fa84d549aa8708fcd062372bf8ec6e849de39016ab921067d21bde367" -dependencies = [ - "cfg-if", - "convert_case", - "napi-derive-backend", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "napi-derive-backend" -version = "1.0.52" +version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20bbc7c69168d06a848f925ec5f0e0997f98e8c8d4f2cc30157f0da51c009e17" -dependencies = [ - "convert_case", - "once_cell", - "proc-macro2", - "quote", - "regex", - "semver", - "syn 1.0.109", -] - -[[package]] -name = "napi-sys" -version = "2.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "166b5ef52a3ab5575047a9fe8d4a030cdd0f63c96f071cd6907674453b07bae3" -dependencies = [ - "libloading", -] +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] name = "once_cell" @@ -387,9 +273,9 @@ checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "pin-project-lite" -version = "0.2.10" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] name = "pin-utils" @@ -410,7 +296,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" dependencies = [ "once_cell", - "toml_edit", + "toml_edit 0.19.15", ] [[package]] @@ -439,86 +325,81 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.66" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.32" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] [[package]] -name = "regex" -version = "1.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" +name = "secrets_core" +version = "0.1.0" dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", + "cfg-if", + "core-foundation", + "core-foundation-sys", + "gio", + "glib", + "glib-sys", + "libsecret", + "libsecret-sys", + "thiserror", + "windows-sys", ] [[package]] -name = "regex-automata" -version = "0.3.4" +name = "serde" +version = "1.0.190" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7b6d6190b7594385f61bd3911cd1be99dfddcfc365a4160cc2ab5bff4aed294" +checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7" dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", + "serde_derive", ] [[package]] -name = "regex-syntax" -version = "0.7.4" +name = "serde_derive" +version = "1.0.190" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" - -[[package]] -name = "semver" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" - -[[package]] -name = "serde" -version = "1.0.179" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a5bf42b8d227d4abf38a1ddb08602e229108a517cd4e5bb28f9c7eaafdce5c0" +checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] [[package]] name = "serde_spanned" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" +checksum = "12022b835073e5b11e90a14f86838ceb1c8fb0325b72416845c487ac0fa95e80" dependencies = [ "serde", ] [[package]] name = "slab" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] [[package]] name = "smallvec" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" +checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" [[package]] name = "syn" @@ -527,15 +408,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", - "quote", "unicode-ident", ] [[package]] name = "syn" -version = "2.0.28" +version = "2.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" +checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" dependencies = [ "proc-macro2", "quote", @@ -544,9 +424,9 @@ dependencies = [ [[package]] name = "system-deps" -version = "6.1.1" +version = "6.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30c2de8a4d8f4b823d634affc9cd2a74ec98c53a756f317e529a48046cbf71f3" +checksum = "94af52f9402f94aac4948a2518b43359be8d9ce6cd9efc1c4de3b2f7b7e897d6" dependencies = [ "cfg-expr", "heck", @@ -557,75 +437,80 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.12.10" +version = "0.12.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2faeef5759ab89935255b1a4cd98e0baf99d1085e37d36599c625dac49ae8e" +checksum = "14c39fd04924ca3a864207c66fc2cd7d22d7c016007f9ce846cbb9326331930a" [[package]] name = "thiserror" -version = "1.0.44" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "611040a08a0439f8248d1990b111c95baa9c704c805fa1f62104b39655fd7f90" +checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.44" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96" +checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.38", ] [[package]] name = "toml" -version = "0.7.6" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542" +checksum = "8ff9e3abce27ee2c9a37f9ad37238c1bdd4e789c84ba37df76aa4d528f5072cc" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit", + "toml_edit 0.20.7", ] [[package]] name = "toml_datetime" -version = "0.6.3" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.19.14" +version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ "indexmap", - "serde", - "serde_spanned", "toml_datetime", "winnow", ] [[package]] -name = "unicode-ident" -version = "1.0.11" +name = "toml_edit" +version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" +checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] [[package]] -name = "unicode-segmentation" -version = "1.10.1" +name = "unicode-ident" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "version-compare" @@ -672,9 +557,9 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.48.1" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", @@ -687,51 +572,51 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_i686_gnu" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_x86_64_gnu" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "winnow" -version = "0.5.2" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bd122eb777186e60c3fdf765a58ac76e41c582f1f535fbf3314434c6b58f3f7" +checksum = "a3b801d0e0a6726477cc207f60162da452f3a95adb368399bef20a946e06f65c" dependencies = [ "memchr", ] diff --git a/src/secrets/src/keyring/Cargo.toml b/src/secrets/src/core/Cargo.toml similarity index 53% rename from src/secrets/src/keyring/Cargo.toml rename to src/secrets/src/core/Cargo.toml index e3e3ab56..b178909c 100644 --- a/src/secrets/src/keyring/Cargo.toml +++ b/src/secrets/src/core/Cargo.toml @@ -1,20 +1,12 @@ [package] +name = "secrets_core" +version = "0.1.0" edition = "2021" -name = "keyring" -version = "1.0.0" -authors = ["Zowe Project"] -license = "EPL-2.0" -repository = "https://github.com/zowe/zowe-cli" -[lib] -name = "keyring" -crate-type = ["cdylib"] +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] cfg-if = "1.0" -# Default enable napi4 feature, see https://nodejs.org/api/n-api.html#node-api-version-matrix -napi = { version = "2", default-features = false, features = ["napi4"] } -napi-derive = "2" thiserror = "1.0.38" [target.'cfg(target_os = "windows")'.dependencies.windows-sys] @@ -36,12 +28,4 @@ glib = "0.18.2" glib-sys = "0.18.1" gio = "0.18.2" libsecret = "0.4.0" -libsecret-sys = "0.4.0" - -[build-dependencies] -napi-build = "2" - -[profile.release] -lto = true -opt-level = "z" # Optimize for size. -strip = "symbols" +libsecret-sys = "0.4.0" \ No newline at end of file diff --git a/src/secrets/src/core/src/lib.rs b/src/secrets/src/core/src/lib.rs new file mode 100644 index 00000000..dda20c34 --- /dev/null +++ b/src/secrets/src/core/src/lib.rs @@ -0,0 +1 @@ +pub mod os; \ No newline at end of file diff --git a/src/secrets/src/keyring/src/os/error.rs b/src/secrets/src/core/src/os/error.rs similarity index 100% rename from src/secrets/src/keyring/src/os/error.rs rename to src/secrets/src/core/src/os/error.rs diff --git a/src/secrets/src/keyring/src/os/mac/error.rs b/src/secrets/src/core/src/os/mac/error.rs similarity index 100% rename from src/secrets/src/keyring/src/os/mac/error.rs rename to src/secrets/src/core/src/os/mac/error.rs diff --git a/src/secrets/src/keyring/src/os/mac/ffi.rs b/src/secrets/src/core/src/os/mac/ffi.rs similarity index 100% rename from src/secrets/src/keyring/src/os/mac/ffi.rs rename to src/secrets/src/core/src/os/mac/ffi.rs diff --git a/src/secrets/src/keyring/src/os/mac/keychain.rs b/src/secrets/src/core/src/os/mac/keychain.rs similarity index 100% rename from src/secrets/src/keyring/src/os/mac/keychain.rs rename to src/secrets/src/core/src/os/mac/keychain.rs diff --git a/src/secrets/src/keyring/src/os/mac/keychain_item.rs b/src/secrets/src/core/src/os/mac/keychain_item.rs similarity index 100% rename from src/secrets/src/keyring/src/os/mac/keychain_item.rs rename to src/secrets/src/core/src/os/mac/keychain_item.rs diff --git a/src/secrets/src/keyring/src/os/mac/keychain_search.rs b/src/secrets/src/core/src/os/mac/keychain_search.rs similarity index 100% rename from src/secrets/src/keyring/src/os/mac/keychain_search.rs rename to src/secrets/src/core/src/os/mac/keychain_search.rs diff --git a/src/secrets/src/keyring/src/os/mac/misc.rs b/src/secrets/src/core/src/os/mac/misc.rs similarity index 100% rename from src/secrets/src/keyring/src/os/mac/misc.rs rename to src/secrets/src/core/src/os/mac/misc.rs diff --git a/src/secrets/src/keyring/src/os/mac/mod.rs b/src/secrets/src/core/src/os/mac/mod.rs similarity index 100% rename from src/secrets/src/keyring/src/os/mac/mod.rs rename to src/secrets/src/core/src/os/mac/mod.rs diff --git a/src/secrets/src/keyring/src/os/mod.rs b/src/secrets/src/core/src/os/mod.rs similarity index 100% rename from src/secrets/src/keyring/src/os/mod.rs rename to src/secrets/src/core/src/os/mod.rs diff --git a/src/secrets/src/keyring/src/os/unix.rs b/src/secrets/src/core/src/os/unix.rs similarity index 100% rename from src/secrets/src/keyring/src/os/unix.rs rename to src/secrets/src/core/src/os/unix.rs diff --git a/src/secrets/src/keyring/src/os/win.rs b/src/secrets/src/core/src/os/win.rs similarity index 98% rename from src/secrets/src/keyring/src/os/win.rs rename to src/secrets/src/core/src/os/win.rs index a0d2742d..a66b03ab 100644 --- a/src/secrets/src/keyring/src/os/win.rs +++ b/src/secrets/src/core/src/os/win.rs @@ -22,10 +22,10 @@ impl From for KeyringError { /// /// Helper function to convert the last Win32 error into a human-readable error message. -/// -/// Returns: +/// +/// Returns: /// A `String` object containing the error message -/// +/// fn win32_error_as_string(error: WIN32_ERROR) -> String { let buffer: PWSTR = std::ptr::null_mut(); @@ -62,22 +62,22 @@ fn win32_error_as_string(error: WIN32_ERROR) -> String { /// /// Helper function to encode a string as a null-terminated UTF-16 string for use w/ credential APIs. -/// +/// /// Returns: /// - `Some(val)` if the string was successfully converted to UTF-16, or `None` otherwise. -/// +/// fn encode_utf16(str: &str) -> Vec { let mut chars: Vec = str.encode_utf16().collect(); chars.push(0); chars } -/// +/// /// Attempts to set a password for a given service and account. -/// +/// /// - `service`: The service name for the new credential /// - `account`: The account name for the new credential -/// +/// /// Returns: /// - `true` if the credential was stored successfully /// - A `KeyringError` if there were any issues interacting with the credential vault @@ -85,7 +85,7 @@ fn encode_utf16(str: &str) -> Vec { pub fn set_password( service: &String, account: &String, - password: &mut String, + password: &String, ) -> Result { // Build WinAPI strings and object parameters from arguments let target_bytes = encode_utf16(format!("{}/{}", service, account).as_str()); @@ -120,16 +120,16 @@ pub fn set_password( Ok(true) } -/// +/// /// Returns a password contained in the given service and account, if found. -/// +/// /// - `service`: The service name that matches the credential of interest /// - `account`: The account name that matches the credential of interest -/// +/// /// Returns: /// - `Some(password)` if a matching credential was found; `None` otherwise /// - A `KeyringError` if there were any issues interacting with the credential vault -/// +/// pub fn get_password(service: &String, account: &String) -> Result, KeyringError> { let mut cred: *mut CREDENTIALW = std::ptr::null_mut::(); let target_name = encode_utf16(format!("{}/{}", service, account).as_str()); @@ -173,16 +173,16 @@ pub fn get_password(service: &String, account: &String) -> Result } } -/// +/// /// Attempts to delete the password associated with a given service and account. -/// +/// /// - `service`: The service name of the credential to delete /// - `account`: The account name of the credential to delete -/// +/// /// Returns: /// - `true` if a matching credential was deleted; `false` otherwise /// - A `KeyringError` if there were any issues interacting with the credential vault -/// +/// pub fn delete_password(service: &String, account: &String) -> Result { let target_name = encode_utf16(format!("{}/{}", service, account).as_str()); @@ -204,15 +204,15 @@ pub fn delete_password(service: &String, account: &String) -> Result Result, KeyringError> { let filter = encode_utf16(format!("{}*", service).as_str()); @@ -257,16 +257,16 @@ pub fn find_password(service: &String) -> Result, KeyringError> { } } -/// +/// /// Builds a vector of all credentials matching the given service pattern. -/// +/// /// - `service`: The service pattern that matches the credential(s) of interest -/// - `credentials`: The vector consisting of (username, password) pairs for each credential that matches -/// +/// - `credentials`: The vector consisting of (username, password) pairs for each credential that matches +/// /// Returns: /// - `true` if at least 1 credential was found, `false` otherwise /// - A `KeyringError` if there were any issues interacting with the credential vault -/// +/// pub fn find_credentials( service: &String, credentials: &mut Vec<(String, String)>, diff --git a/src/secrets/src/keyring-test/Cargo.lock b/src/secrets/src/keyring-test/Cargo.lock index 2d2672ed..482d67cc 100644 --- a/src/secrets/src/keyring-test/Cargo.lock +++ b/src/secrets/src/keyring-test/Cargo.lock @@ -2,15 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "aho-corasick" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" -dependencies = [ - "memchr", -] - [[package]] name = "autocfg" version = "1.1.0" @@ -45,15 +36,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "convert_case" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" -dependencies = [ - "unicode-segmentation", -] - [[package]] name = "core-foundation" version = "0.9.3" @@ -70,16 +52,6 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" -[[package]] -name = "ctor" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37e366bff8cd32dd8754b0991fb66b279dc48f598c3a18914852a6673deef583" -dependencies = [ - "quote", - "syn 2.0.38", -] - [[package]] name = "equivalent" version = "1.0.1" @@ -267,31 +239,12 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa799dd5ed20a7e349f3b4639aa80d74549c81716d9ec4f994c9b5815598306" -[[package]] -name = "keyring" -version = "1.0.0" -dependencies = [ - "cfg-if", - "core-foundation", - "core-foundation-sys", - "gio", - "glib", - "glib-sys", - "libsecret", - "libsecret-sys", - "napi", - "napi-build", - "napi-derive", - "thiserror", - "windows-sys", -] - [[package]] name = "keyring-test" version = "0.1.0" dependencies = [ - "keyring", "pyo3", + "secrets_core", ] [[package]] @@ -300,16 +253,6 @@ version = "0.2.149" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" -[[package]] -name = "libloading" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" -dependencies = [ - "cfg-if", - "winapi", -] - [[package]] name = "libsecret" version = "0.4.0" @@ -361,63 +304,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "napi" -version = "2.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd063c93b900149304e3ba96ce5bf210cd4f81ef5eb80ded0d100df3e85a3ac0" -dependencies = [ - "bitflags 2.4.1", - "ctor", - "napi-derive", - "napi-sys", - "once_cell", -] - -[[package]] -name = "napi-build" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "882a73d9ef23e8dc2ebbffb6a6ae2ef467c0f18ac10711e4cc59c5485d41df0e" - -[[package]] -name = "napi-derive" -version = "2.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da1c6a8fa84d549aa8708fcd062372bf8ec6e849de39016ab921067d21bde367" -dependencies = [ - "cfg-if", - "convert_case", - "napi-derive-backend", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "napi-derive-backend" -version = "1.0.52" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20bbc7c69168d06a848f925ec5f0e0997f98e8c8d4f2cc30157f0da51c009e17" -dependencies = [ - "convert_case", - "once_cell", - "proc-macro2", - "quote", - "regex", - "semver", - "syn 1.0.109", -] - -[[package]] -name = "napi-sys" -version = "2.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "166b5ef52a3ab5575047a9fe8d4a030cdd0f63c96f071cd6907674453b07bae3" -dependencies = [ - "libloading", -] - [[package]] name = "once_cell" version = "1.18.0" @@ -586,35 +472,6 @@ dependencies = [ "bitflags 1.3.2", ] -[[package]] -name = "regex" -version = "1.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" - [[package]] name = "scopeguard" version = "1.2.0" @@ -622,10 +479,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] -name = "semver" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" +name = "secrets_core" +version = "0.1.0" +dependencies = [ + "cfg-if", + "core-foundation", + "core-foundation-sys", + "gio", + "glib", + "glib-sys", + "libsecret", + "libsecret-sys", + "thiserror", + "windows-sys", +] [[package]] name = "serde" @@ -783,12 +650,6 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" -[[package]] -name = "unicode-segmentation" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" - [[package]] name = "unindent" version = "0.1.11" diff --git a/src/secrets/src/keyring-test/Cargo.toml b/src/secrets/src/keyring-test/Cargo.toml index 65bbe461..bfb2b113 100644 --- a/src/secrets/src/keyring-test/Cargo.toml +++ b/src/secrets/src/keyring-test/Cargo.toml @@ -10,4 +10,4 @@ crate-type = ["cdylib"] [dependencies] pyo3 = "0.19.0" -keyring = { path = "../keyring" } +secrets_core = { path = "../core" } diff --git a/src/secrets/src/keyring-test/src/lib.rs b/src/secrets/src/keyring-test/src/lib.rs index 858ab70d..b2b7c846 100644 --- a/src/secrets/src/keyring-test/src/lib.rs +++ b/src/secrets/src/keyring-test/src/lib.rs @@ -1,56 +1,60 @@ +use pyo3::exceptions::PyValueError; use pyo3::prelude::*; -extern crate keyring; -use keyring::os::*; +extern crate secrets_core; +use secrets_core::*; #[pyfunction] -fn py_set_password(service: &str, account: &str, password: &str) -> PyResult<()> { - match set_password(service, account, password) { +fn set_password(service: String, account: String, password: String) -> PyResult<()> { + match os::set_password(&service, &account, &password) { Ok(_) => Ok(()), - Err(e) => Err(e), + Err(e) => Err(PyValueError::new_err("error in set_password")), } } #[pyfunction] -fn py_get_password(service: &str, account: &str) -> PyResult { - match get_password(service, account) { - Ok(_) => Ok(()), - Err(e) => Err(e), +fn get_password(service: String, account: String) -> PyResult { + match os::get_password(&service, &account) { + Ok(pw) => Ok(pw.unwrap_or("".to_string())), + Err(e) => Err(PyValueError::new_err("error in get_password")), } } #[pyfunction] -fn py_delete_password(service: &str, account: &str) -> PyResult { - match delete_password(service, account) { +fn delete_password(service: String, account: String) -> PyResult { + match os::delete_password(&service, &account) { Ok(res) => Ok(res), - Err(e) => Err(e), + Err(e) => Err(PyValueError::new_err("error in delete_password")), } } #[pyfunction] -fn py_find_password(service: &str) -> PyResult { - match find_password(service) { - Ok(res) => Ok(res), - Err(e) => Err(e), +fn find_password(service: String) -> PyResult { + match os::find_password(&service) { + Ok(res) => match res { + Some(val) => Ok(val), + _ => Ok("".to_owned()), + }, + Err(e) => Err(PyValueError::new_err("error in find_password")), } } #[pyfunction] -fn py_find_credentials(service: &str) -> PyResult> { +fn find_credentials(service: String) -> PyResult> { let mut creds: Vec<(String, String)> = vec![]; - match find_credentials(service, &mut creds) { + match os::find_credentials(&service, &mut creds) { Ok(res) => Ok(creds), - Err(e) => Err(e), + Err(e) => Err(PyValueError::new_err("error in find_credentials")), } } /// A Python module implemented in Rust. #[pymodule] fn keyring_test(_py: Python, m: &PyModule) -> PyResult<()> { - m.add_function(wrap_pyfunction!(py_get_password, m)?)?; - m.add_function(wrap_pyfunction!(py_set_password, m)?)?; - m.add_function(wrap_pyfunction!(py_delete_password, m)?)?; - m.add_function(wrap_pyfunction!(py_find_password, m)?)?; - m.add_function(wrap_pyfunction!(py_find_credentials, m)?)?; + 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/src/keyring/.cargo/config.toml b/src/secrets/src/keyring/.cargo/config.toml deleted file mode 100644 index d6d7ccc3..00000000 --- a/src/secrets/src/keyring/.cargo/config.toml +++ /dev/null @@ -1,2 +0,0 @@ -[target.'cfg(all(windows, target_env = "msvc"))'] -rustflags = ["-Ctarget-feature=+crt-static"] \ No newline at end of file diff --git a/src/secrets/src/keyring/.npmignore b/src/secrets/src/keyring/.npmignore deleted file mode 100644 index d0560b0c..00000000 --- a/src/secrets/src/keyring/.npmignore +++ /dev/null @@ -1,13 +0,0 @@ -target -Cross.toml -.cargo -.github -npm -.eslintrc -.prettierignore -rustfmt.toml -yarn.lock -*.node -.yarn -__test__ -renovate.json diff --git a/src/secrets/src/keyring/Cross.toml b/src/secrets/src/keyring/Cross.toml deleted file mode 100644 index 106f07ec..00000000 --- a/src/secrets/src/keyring/Cross.toml +++ /dev/null @@ -1,37 +0,0 @@ -[build] -pre-build = [ - "dpkg --add-architecture $CROSS_DEB_ARCH", - "apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y libsecret-1-dev:$CROSS_DEB_ARCH", -] - -[build.env] -passthrough = [ - "PKG_CONFIG_SYSROOT_DIR", - "PKG_CONFIG_PATH", - "RUSTFLAGS" -] - -[target.aarch64-unknown-linux-gnu] -image = "ghcr.io/cross-rs/aarch64-unknown-linux-gnu:main" - -[target.aarch64-unknown-linux-musl] -image = "rust:alpine" -pre-build = [ - "wget -qO- https://musl.cc/aarch64-linux-musl-cross.tgz | tar -xzC / && export PATH=\"/aarch64-linux-musl-cross/bin:$PATH\"", - "apk add --no-cache musl-dev pkgconfig", - "apk add -p /aarch64-linux-musl-cross --initdb --arch aarch64 --allow-untrusted -X \"https://dl-cdn.alpinelinux.org/alpine/latest-stable/main/\" --no-cache --no-scripts libsecret-dev", - "rustup target add aarch64-unknown-linux-musl" -] - -[target.armv7-unknown-linux-gnueabihf] -image = "ghcr.io/cross-rs/armv7-unknown-linux-gnueabihf:main" - -[target.i686-unknown-linux-gnu] -image = "ghcr.io/cross-rs/i686-unknown-linux-gnu:main" - -[target.x86_64-unknown-linux-gnu] -image = "ghcr.io/cross-rs/x86_64-unknown-linux-gnu:main" - -[target.x86_64-unknown-linux-musl] -image = "rust:alpine" -pre-build = ["apk add libsecret-dev musl-dev"] diff --git a/src/secrets/src/keyring/EXTENDERS.md b/src/secrets/src/keyring/EXTENDERS.md deleted file mode 100644 index d2f55110..00000000 --- a/src/secrets/src/keyring/EXTENDERS.md +++ /dev/null @@ -1,140 +0,0 @@ -# Usage (Extenders) - -## What is `keyring`? - -`keyring` is a cross-platform module meant to interact with OS (operating system) credential storage. `keyring` is written in Rust, and uses other Rust libraries to interface with credential storage APIs (application programming interfaces). - -## Why switch to `keyring`? - -As `node-keytar` is now unmaintained, there was a demand for a replacement that can function identically to the original module. - -As `keyring` was modeled after `node-keytar`, the same operations can be performed in credential storage: - -- Storing credentials -- Retrieving credentials -- Searching for passwords based on a matching label -- Searching for matching credentials based on a prefix/query -- Deleting credentials - -**Currently, there are no breaking changes** between the use of `node-keytar` and `keyring`. This is intended by design. - -From a developer's perspective, one can simply update existing extenders or plug-ins to import the keyring module from `@zowe/secrets-for-zowe-sdk` instead of `node-keytar`, allowing for a straightforward transition. All functions previously exported in `node-keytar` will be available in `keyring`. Simply add `@zowe/secrets-for-zowe-sdk` to your project using `npm` or `yarn`. - -Importing a function from `keyring` is identical to the `node-keytar` import process: - -```ts -// Import all functions under a namespace... -import { keyring } from "@zowe/secrets-for-zowe-sdk"; -// Or, use require to import the keyring module. -const { keyring } = require("@zowe/secrets-for-zowe-sdk"); -``` - -After the desired functions are imported, feel free to use them in the same fashion as the `node-keytar` functions. For the examples below, `async/await` keywords are used, but the functions can also be used with `.then/.catch` promise blocks: - -```ts -getPassword("TestService", "AccountA") -.then((pw) => { - console.log("The password for TestService/AccountA is:", pw); -}) -.catch((err) => { - console.error("An error occurred!", err.message); -}); -``` - -**Examples:** - -```ts -// Set a password with a given service and account name -// Password will be stored under / -await keyring.setPassword("TestService", "AccountA", "Apassword"); - -// Get a password, given a service and account name -await keyring.getPassword("TestService", "AccountA"); - -// Find credentials based on a matching label -await keyring.findCredentials("TestService"); - -// Find password that matches a service and account -await keyring.findPassword("TestService/AccountA"); - -// Delete a credential w/ the provided service and account name -await keyring.deletePassword("TestService", "AccountA"); -``` - -## Webpacking/bundling alongside your project - -Some projects leverage a JavaScript bundler, such as Webpack or Vite, to minify and compress their Node.js packages. -While the Secrets SDK does support Webpack, developers who want to bundle the Secrets SDK alongside their package should set up a `prebuilds` folder alongside their package root. - -For example, if your package places build output in the `out` folder, your directory structure should look like this: - -``` -your-pkg/ -├── package.json -├── src/ -├── out/ -│ └── bundle.js -├── prebuilds/ -│ └── (node binaries for Secrets SDK) -``` - -If you are using ESbuild or Webpack, consider using a copy plugin to copy the `prebuilds` folder from the Secrets SDK *node_modules* folder: -- ESbuild: [esbuild-copy-static-files](https://www.npmjs.com/package/esbuild-copy-static-files) -- Webpack: [copy-webpack-plugin](https://www.npmjs.com/package/copy-webpack-plugin) - -Otherwise, use the Node.js script below (**requires Node 16.7.0 and above**). It creates a copy of the `prebuilds` folder containing the required Secrets SDK binaries. Run this script in the same directory as your extension's `package.json`: - -```js -const { cpSync } = require("fs"); -cpSync("/path/to/node_modules/@zowe/secrets-for-zowe-sdk/prebuilds", "prebuilds", {force: true, recursive: true}); -``` -**Note:** The first argument for `cpSync` will vary, depending on where the *node_modules* folder is located in your environment. - -We recommend that developers add this logic to a `prepare` script in their `package.json` to guarantee the binaries are up-to-date after installing dependencies. -Save the above script as a JavaScript file (e.g., `scripts/copyKeyringBinaries.js`), and execute the script: - -```js -{ - "scripts": { - "prepare": "node scripts/copyKeyringBinaries.js" - } -} -``` - -If you are bundling a VSCode extension, and are using a `.vscodeignore` file, you must allow the prebuilds folder to be packaged in the VSIX. -Add the following line to your `.vscodeignore` file: -``` -!prebuilds/** -``` - -### Updating imports - -Some extenders might import `keytar` directly as a dependency. In these cases, extenders should import the `keyring` module from this package instead. - -**Take caution when importing** as the import process is slightly different than `node-keytar`: - -Before: -```js -const keytar = require("node-keytar"); -// ES6 import: -import * as keytar from "node-keytar"; -``` - -After: -```js -const { keyring } = require("@zowe/secrets-for-zowe-sdk"); -// ES6 import: -import { keyring } from "@zowe/secrets-for-zowe-sdk"; -``` - -Notice that the keyring module must be accessed from the dependency imports before use. -To reduce the amount of code that needs updated, users can use an import alias to the phrase "keytar": - -```js -const { keyring: keytar } = require("@zowe/secrets-for-zowe-sdk"); -// ES6 import: -import { keyring as keytar } from "@zowe/secrets-for-zowe-sdk"; - -// Existing code, such as the example below, can remain unchanged with this import alias: -keytar.setPassword("Hello", "World", "ExamplePassword"); -``` \ No newline at end of file diff --git a/src/secrets/src/keyring/README.md b/src/secrets/src/keyring/README.md deleted file mode 100644 index 3a61337c..00000000 --- a/src/secrets/src/keyring/README.md +++ /dev/null @@ -1,177 +0,0 @@ -# keyring - -keyring is a native Node.js module for accessing and managing OS credential storage. - -## Compatibility - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
OS / ArchitectureNode.js Version
v12v14v16v18
Windowsx64
x86
arm64
macOSx64
aarch64
Linux (gnu)x64
x86
aarch64
armv7l (gnueabihf)
Linux (musl)x64
aarch64
- -## Features - -keyring supports the following operations within credential storage: - -- [x] **Set** a credential -- [x] **Retrieve** a credential -- [x] **Find all credentials** with matching attributes -- [x] **Find a password** with matching attributes - -Some benefits to using keyring: - -- [x] **Cross-platform support** makes for straight-forward secrets management -- [x] **Existing OS credentials are supported** out-of-the-box -- [x] **Avoids memory allocation** - memory only allocated as needed for OS-specific APIs - -## Node API documentation - -### deletePassword - -Deletes a password with matching `service` and `account` parameters. - -**Returns:** Whether the password was deleted successfully. - -```ts -function deletePassword(service: string, account: string) -> Promise -``` - -### findCredentials - -Finds all credentials with a matching `service` parameter. - -**Returns:** An array of `Credential` objects, containing the `account` and `password` for each credential that is found within `service`. - -```ts -interface Credential { - account: string; - password: string; -}; - -function findCredentials(service: string) -> Promise> -``` - -### findPassword - -Finds a password with a matching `service` and `account` parameter. - -**Returns:** The first password found in `/`, or `null` if not found. - -```ts -function findPassword(service: string, account: string) -> Promise -``` - -### getPassword - -Gets a password with a matching `service` and `account` parameter. - -**Returns:** The password stored under `/`, or `null` if not found. - -```ts -function getPassword(service: string, account: string) -> Promise -``` - -### setPassword - -Stores a password with the given `service`, `account`, and `password`. - -```ts -function setPassword(service: string, account: string, password: string) -> Promise -``` diff --git a/src/secrets/src/keyring/__test__/index.spec.mjs b/src/secrets/src/keyring/__test__/index.spec.mjs deleted file mode 100644 index 6ca2cd4c..00000000 --- a/src/secrets/src/keyring/__test__/index.spec.mjs +++ /dev/null @@ -1,312 +0,0 @@ -import test from "ava"; -import { - deletePassword, - findCredentials, - findPassword, - getPassword, - setPassword, -} from "../index.js"; - -// generate a number in range [min, max) -const randomInt = (min, max) => { - return Math.floor(Math.random() * (max - min)) + min; -}; - -// generate random ASCII string of length "len" -// (could use constants instead, but this should better emulate real-world scenarios) -const randomAsciiString = (len) => { - let str = ""; - for (let i = 0; i < len; i++) { - str = str.concat(String.fromCharCode(randomInt(97, 123))); - } - return str; -}; - -const TEST_CREDENTIALS = [ - { service: "TestKeyring", account: "TestASCII" }, - { service: "TestKeyring", account: "TestUTF8" }, - { service: "TestKeyring", account: "TestCharSet" }, - { service: "TestKeyring", account: "TestUTF16" }, - { service: "TestKeyring", account: "TestCJK" }, - { service: "TestKeyring", account: "TestBinary" }, - { service: "TestEmptyAccount", account: "" }, - { service: "", account: "TestEmptyService" }, - { service: "TestKeyring", account: "PwNullTerm" }, -]; - -test.serial("get/setPassword with binary data", async (t) => { - const binaryGroups = - "01001000 01100101 01101100 01101100 01101111 00100000 01110111 01101111 01110010 01101100 01100100 00100001".match( - /[01]{8}/g - ); - const parsed = binaryGroups.map((binary) => parseInt(binary, 2)); - const buffer = Buffer.from(new Uint8Array(parsed).buffer); - - await setPassword("TestKeyring", "TestBinary", buffer.toString()); - const res = await getPassword("TestKeyring", "TestBinary"); - t.is(res, buffer.toString()); -}); - -test.serial("get/setPassword with empty string parameters", async (t) => { - // Empty "account" parameter - await setPassword("TestEmptyAccount", "", "emptyAccountPW"); - const accountRes = await getPassword("TestEmptyAccount", ""); - t.is(accountRes, "emptyAccountPW"); - - // Empty "service" parameter - await setPassword("", "TestEmptyService", "emptyServicePW"); - const serviceRes = await getPassword("", "TestEmptyService"); - t.is(serviceRes, "emptyServicePW"); -}); - -test.serial("get/setPassword with ASCII string", async (t) => { - await setPassword("TestKeyring", "TestASCII", "ASCII string"); - - const str = await getPassword("TestKeyring", "TestASCII"); - t.is(str, "ASCII string"); -}); - -test.serial("get/setPassword with mixed character set", async (t) => { - await setPassword("TestKeyring", "TestCharSet", "I 💔 ASCII"); - - const str = await getPassword("TestKeyring", "TestCharSet"); - t.is(str, "I 💔 ASCII"); -}); - -test.serial("get/setPassword with UTF-16 chars", async (t) => { - await setPassword("TestKeyring", "TestUTF16", "🌞🌙🌟🌴"); - - const str = await getPassword("TestKeyring", "TestUTF16"); - t.is(str, "🌞🌙🌟🌴"); -}); - -test.serial("get/setPassword with UTF-8 chars", async (t) => { - await setPassword( - "TestKeyring", - "TestUTF8", - "ᚻᛖ ᚳᚹᚫᚦ ᚦᚫᛏ ᚻᛖ ᛒᚢᛞᛖ ᚩᚾ ᚦᚫᛗ ᛚᚪᚾᛞᛖ ᚾᚩᚱᚦᚹᛖᚪᚱᛞᚢᛗ ᚹᛁᚦ ᚦᚪ ᚹᛖᛥᚫ" - ); - - const str = await getPassword("TestKeyring", "TestUTF8"); - t.is(str, "ᚻᛖ ᚳᚹᚫᚦ ᚦᚫᛏ ᚻᛖ ᛒᚢᛞᛖ ᚩᚾ ᚦᚫᛗ ᛚᚪᚾᛞᛖ ᚾᚩᚱᚦᚹᛖᚪᚱᛞᚢᛗ ᚹᛁᚦ ᚦᚪ ᚹᛖᛥᚫ"); -}); - -test.serial("get/setPassword with CJK symbols", async (t) => { - await setPassword("TestKeyring", "TestCJK", "「こんにちは世界」"); - - const str = await getPassword("TestKeyring", "TestCJK"); - t.is(str, "「こんにちは世界」"); -}); - -test.serial("get/setPassword fails with null/undefined data", async (t) => { - try { - await setPassword("TestKeyring", "TestNull", null); - } catch (err) { - t.is(err.code, "StringExpected"); - } - - try { - await setPassword("TestKeyring", "TestNull", undefined); - } catch (err) { - t.is(err.code, "StringExpected"); - } -}); - -test.serial( - "get/setPassword with password containing extra null terminators", - async (t) => { - // "password" parameter w/ extra null terminator - await setPassword("TestKeyring", "PwNullTerm", "PW\0"); - const pwRes = await getPassword("TestKeyring", "PwNullTerm"); - if (process.platform === "linux") { - // libsecret automatically strips off null terminator - t.is(pwRes, "PW"); - } else { - t.is(pwRes, "PW\0"); - } - } -); - -test.serial("getPassword with missing data", async (t) => { - const str = await getPassword("TestKeyring", "TestMissingPW"); - t.is(str, null); -}); - -test.serial( - "findCredentials verifies that test credentials were stored", - async (t) => { - let expected = [ - { account: "TestASCII", password: "ASCII string" }, - { account: "TestBinary", password: "Hello world!" }, - { account: "TestCharSet", password: "I 💔 ASCII" }, - { account: "TestCJK", password: "「こんにちは世界」" }, - { - account: "TestUTF8", - password: "ᚻᛖ ᚳᚹᚫᚦ ᚦᚫᛏ ᚻᛖ ᛒᚢᛞᛖ ᚩᚾ ᚦᚫᛗ ᛚᚪᚾᛞᛖ ᚾᚩᚱᚦᚹᛖᚪᚱᛞᚢᛗ ᚹᛁᚦ ᚦᚪ ᚹᛖᛥᚫ", - }, - { account: "TestUTF16", password: "🌞🌙🌟🌴" }, - { account: "PwNullTerm", password: "PW\x00" }, - ]; - const actual = await findCredentials("TestKeyring"); - t.is( - actual.length, - expected.length, - `actual: ${JSON.stringify(actual)}; expected: ${JSON.stringify(expected)}` - ); - - expected.forEach((cred) => - t.not( - actual.find((c) => c === cred), - null - ) - ); - } -); - -test.serial("findCredentials works when only one credential is found", async (t) => { - await setPassword("TestKeyring2", "TestOneCred", "pass"); - - const creds = await findCredentials("TestKeyring2"); - t.deepEqual(creds, [{ - account: "TestOneCred", - password: "pass" - }]); - await deletePassword("TestKeyring2", "TestOneCred"); -}); - -test.serial("findPassword for ASCII string", async (t) => { - const pw = await findPassword("TestKeyring/TestASCII"); - t.is(pw, "ASCII string"); -}); - -test.serial("findPassword for mixed character set", async (t) => { - const pw = await findPassword("TestKeyring/TestCharSet"); - t.is(pw, "I 💔 ASCII"); -}); - -test.serial("findPassword for UTF-16", async (t) => { - const pw = await findPassword("TestKeyring/TestUTF16"); - t.is(pw, "🌞🌙🌟🌴"); -}); - -test.serial("findPassword for CJK symbols", async (t) => { - const pw = await findPassword("TestKeyring/TestCJK"); - t.is(pw, "「こんにちは世界」"); -}); - -test("deletePassword deletes all test credentials", async (t) => { - console.log( - "\nThe deletePassword test is running. There is an intended delay of 5 seconds to wait for the keyring to update." - ); - const timeout = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); - // initial timeout to give keyrings time to populate - await timeout(5000); - for (const cred of TEST_CREDENTIALS) { - const result = await deletePassword(cred.service, cred.account); - if (!result) { - t.fail(`Credential with account "${cred.account}" failed to delete.`); - } - } - - const afterDeletion = await findCredentials("TestKeyring"); - t.is( - afterDeletion.length, - 0, - `One or more credentials were still in the keyring: ${afterDeletion - .map((c) => c.account) - .join(", ")}` - ); -}); - -// Unit tests specific to Windows API calls -if (process.platform === "win32") { - test.serial( - "setPassword fails when blob exceeds CRED_MAX_CREDENTIAL_BLOB_SIZE", - async (t) => { - console.log("win32: platform-specific tests for WinAPI"); - const CRED_MAX_CREDENTIAL_BLOB_SIZE = 5 * 512; - const str = randomAsciiString(CRED_MAX_CREDENTIAL_BLOB_SIZE + 1); - try { - await setPassword("TestKeyringWindows", "MaxCredBlobSize", str); - } catch (err) { - t.not(err, null); - } - } - ); - - test.serial( - "setPassword fails when TargetName exceeds CRED_MAX_GENERIC_TARGET_NAME_LENGTH", - async (t) => { - const CRED_MAX_GENERIC_TARGET_NAME_LENGTH = 32767; - const str = randomAsciiString(CRED_MAX_GENERIC_TARGET_NAME_LENGTH + 1); - try { - await setPassword( - "TestKeyringWindows", - "MaxGenericTargetNameLen_".concat(str), - "pw" - ); - } catch (err) { - t.not(err, null); - } - } - ); - - test.serial( - "setPassword fails when account length exceeds CRED_MAX_USERNAME_LENGTH", - async (t) => { - const CRED_MAX_USERNAME_LENGTH = 512; - const str = randomAsciiString(CRED_MAX_USERNAME_LENGTH + 1); - try { - await setPassword("TestKeyringWindows", str, "pw"); - } catch (err) { - t.not(err, null); - } - } - ); - - test.serial( - "findCredentials where CredEnumerateW returns false", - async (t) => { - const found = await findCredentials("TestKeyringWindowsInvalidService"); - t.deepEqual(found, []); - } - ); - - test.serial("findCredentials where TargetName is NULL", async (t) => { - // Since rust won't accept null as a parameter in the backend, best test is an empty string - const found = await findCredentials(""); - t.is(found.length > 0, true); - }); - - test.serial( - "Error handled when CredReadW throws ERROR_NOT_FOUND", - async (t) => { - try { - const errorTest = await getPassword( - "TestKeyringWindowsInvalidService", - "FakeAccount" - ); - t.is(errorTest, null); - } catch (err) { - t.fail( - "getPassword should not throw an exception when no credentials are found (win32)" - ); - } - } - ); - - test.serial( - "CredDeleteW with a credential that does not exist", - async (t) => { - try { - await deletePassword("TestKeyringWindowsInvalidService", "FakeAccount"); - } catch (err) { - t.fail( - "deletePassword should not throw an exception for a credential that doesn't exist (win32)" - ); - } - - t.pass(); - } - ); -} diff --git a/src/secrets/src/keyring/build.rs b/src/secrets/src/keyring/build.rs deleted file mode 100644 index 1f866b6a..00000000 --- a/src/secrets/src/keyring/build.rs +++ /dev/null @@ -1,5 +0,0 @@ -extern crate napi_build; - -fn main() { - napi_build::setup(); -} diff --git a/src/secrets/src/keyring/index.d.ts b/src/secrets/src/keyring/index.d.ts deleted file mode 100644 index fe9a0998..00000000 --- a/src/secrets/src/keyring/index.d.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* tslint:disable */ -/* eslint-disable */ - -/* auto-generated by NAPI-RS */ - -export interface Credential { - account: string - password: string -} -export function deletePassword(service: string, account: string): Promise -export function findCredentials(service: string): Promise> -export function findPassword(service: string): Promise -export function getPassword(service: string, account: string): Promise -export function setPassword(service: string, account: string, password: string): Promise diff --git a/src/secrets/src/keyring/index.js b/src/secrets/src/keyring/index.js deleted file mode 100644 index fd3b886f..00000000 --- a/src/secrets/src/keyring/index.js +++ /dev/null @@ -1,66 +0,0 @@ -/* -* This program and the accompanying materials are made available under the terms of the -* Eclipse Public License v2.0 which accompanies this distribution, and is available at -* https://www.eclipse.org/legal/epl-v20.html -* -* SPDX-License-Identifier: EPL-2.0 -* -* Copyright Contributors to the Zowe Project. -* -*/ - -const { existsSync } = require("fs"); -const { join, normalize, parse } = require("path"); - -function findPrebuildsDir(dir) { - if (!dir || existsSync(join(dir, "package.json"))) { - return join(dir, "prebuilds"); - } - - const dirUp = normalize(join(dir, "..")); - if (parse(dirUp).base.length > 0) { - return findPrebuildsDir(dirUp); - } -} - -function getTargetName() { - switch (process.platform) { - case "win32": - return `win32-${process.arch}-msvc`; - case "linux": - const isMusl = - process.report.getReport().header.glibcVersionRuntime == null; - const abi = isMusl ? "musl" : "gnu"; - switch (process.arch) { - case "arm": - return `linux-arm-${abi}eabihf`; - default: - return `linux-${process.arch}-${abi}`; - } - case "darwin": - default: - return `${process.platform}-${process.arch}`; - } -} - -const requireFn = - typeof __webpack_require__ === "function" - ? __non_webpack_require__ - : require; - -const binaryPath = requireFn.resolve(`./keyring.${getTargetName()}.node`, { - paths: [__dirname, findPrebuildsDir(__dirname)].filter(Boolean), -}); -const { - deletePassword, - findCredentials, - findPassword, - getPassword, - setPassword, -} = requireFn(binaryPath); - -module.exports.deletePassword = deletePassword; -module.exports.findCredentials = findCredentials; -module.exports.findPassword = findPassword; -module.exports.getPassword = getPassword; -module.exports.setPassword = setPassword; diff --git a/src/secrets/src/keyring/napi.json b/src/secrets/src/keyring/napi.json deleted file mode 100644 index 354e4cdc..00000000 --- a/src/secrets/src/keyring/napi.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "napi": { - "name": "keyring", - "package": { - "name": "keyring" - }, - "triples": { - "defaults": true, - "additional": [ - "x86_64-unknown-linux-musl", - "aarch64-unknown-linux-gnu", - "i686-unknown-linux-gnu", - "i686-pc-windows-msvc", - "armv7-unknown-linux-gnueabihf", - "aarch64-apple-darwin", - "aarch64-unknown-linux-musl", - "aarch64-pc-windows-msvc" - ] - } - } -} diff --git a/src/secrets/src/keyring/rustfmt.toml b/src/secrets/src/keyring/rustfmt.toml deleted file mode 100644 index ca5602ae..00000000 --- a/src/secrets/src/keyring/rustfmt.toml +++ /dev/null @@ -1,2 +0,0 @@ -tab_spaces = 4 -edition = "2021" diff --git a/src/secrets/src/keyring/src/lib.rs b/src/secrets/src/keyring/src/lib.rs deleted file mode 100644 index 2965de83..00000000 --- a/src/secrets/src/keyring/src/lib.rs +++ /dev/null @@ -1,35 +0,0 @@ -use napi::bindgen_prelude::AsyncTask; -use napi_derive::napi; -use workers::{DeletePassword, FindCredentials, FindPassword, GetPassword, SetPassword}; - -mod os; -mod workers; - -#[napi] -fn delete_password(service: String, account: String) -> AsyncTask { - AsyncTask::new(DeletePassword { service, account }) -} - -#[napi] -fn find_credentials(service: String) -> AsyncTask { - AsyncTask::new(FindCredentials { service }) -} - -#[napi(ts_return_type = "Promise")] -fn find_password(service: String) -> AsyncTask { - AsyncTask::new(FindPassword { service }) -} - -#[napi(ts_return_type = "Promise")] -fn get_password(service: String, account: String) -> AsyncTask { - AsyncTask::new(GetPassword { service, account }) -} - -#[napi(ts_return_type = "Promise")] -fn set_password(service: String, account: String, password: String) -> AsyncTask { - AsyncTask::new(SetPassword { - service, - account, - password, - }) -} diff --git a/src/secrets/src/keyring/src/workers.rs b/src/secrets/src/keyring/src/workers.rs deleted file mode 100644 index 88ee339d..00000000 --- a/src/secrets/src/keyring/src/workers.rs +++ /dev/null @@ -1,153 +0,0 @@ -use napi::{Env, Error, JsBoolean, JsUnknown, Result, Task}; -use napi_derive::napi; - -use crate::os; - -pub struct SetPassword { - pub service: String, - pub account: String, - pub password: String, -} - -pub struct GetPassword { - pub service: String, - pub account: String, -} - -pub struct DeletePassword { - pub service: String, - pub account: String, -} - -pub struct FindCredentials { - pub service: String, -} -pub struct FindPassword { - pub service: String, -} - -#[napi(object)] -pub struct Credential { - pub account: String, - pub password: String, -} - -#[napi] -impl Task for GetPassword { - type Output = Option; - type JsValue = JsUnknown; - - fn compute(&mut self) -> Result { - match os::get_password(&self.service, &self.account) { - Ok(pw) => Ok(pw), - Err(err) => Err(napi::Error::from_reason(err.to_string())), - } - } - - fn resolve(&mut self, env: Env, output: Self::Output) -> Result { - Ok(match output { - Some(pw) => env.create_string(pw.as_str())?.into_unknown(), - None => env.get_null()?.into_unknown(), - }) - } - - fn reject(&mut self, _env: Env, err: Error) -> Result { - Err(err) - } -} - -#[napi] -impl Task for SetPassword { - type Output = bool; - type JsValue = JsUnknown; - - fn compute(&mut self) -> Result { - match os::set_password(&self.service, &self.account, &mut self.password) { - Ok(result) => Ok(result), - Err(err) => Err(napi::Error::from_reason(err.to_string())), - } - } - - fn resolve(&mut self, env: Env, _output: Self::Output) -> Result { - Ok(env.get_null()?.into_unknown()) - } - - fn reject(&mut self, _env: Env, err: Error) -> Result { - Err(err) - } -} - -#[napi] -impl Task for DeletePassword { - type Output = bool; - type JsValue = JsBoolean; - - fn compute(&mut self) -> Result { - match os::delete_password(&self.service, &self.account) { - Ok(result) => Ok(result), - Err(err) => Err(napi::Error::from_reason(err.to_string())), - } - } - - fn resolve(&mut self, env: Env, output: Self::Output) -> Result { - env.get_boolean(output) - } - - fn reject(&mut self, _env: Env, err: Error) -> Result { - Err(err) - } -} - -#[napi] -impl Task for FindCredentials { - type Output = Vec<(String, String)>; - type JsValue = Vec; - - fn compute(&mut self) -> Result { - let mut credentials: Self::Output = Vec::new(); - match os::find_credentials(&self.service, &mut credentials) { - Ok(_result) => Ok(credentials), - Err(err) => Err(napi::Error::from_reason(err.to_string())), - } - } - - fn resolve(&mut self, _env: Env, output: Self::Output) -> Result { - let mut creds = Vec::new(); - for cred in output { - creds.push(Credential { - account: cred.0, - password: cred.1, - }) - } - - Ok(creds) - } - - fn reject(&mut self, _env: Env, err: Error) -> Result { - Err(err) - } -} - -#[napi] -impl Task for FindPassword { - type Output = Option; - type JsValue = JsUnknown; - - fn compute(&mut self) -> Result { - match os::find_password(&self.service) { - Ok(pw) => Ok(pw), - Err(err) => Err(napi::Error::from_reason(err.to_string())), - } - } - - fn resolve(&mut self, env: Env, output: Self::Output) -> Result { - Ok(match output { - Some(pw) => env.create_string(pw.as_str())?.into_unknown(), - None => env.get_null()?.into_unknown(), - }) - } - - fn reject(&mut self, _env: Env, err: Error) -> Result { - Err(err) - } -} From 9e60ae3ecd7c87a463af42c7b39cad1b92e335a3 Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Tue, 31 Oct 2023 13:34:13 -0400 Subject: [PATCH 03/45] WIP Move secrets_core dep to CLI repo Signed-off-by: Timothy Johnson --- .github/workflows/secrets-sdk.yml | 2 +- src/secrets/CHANGELOG.md | 31 - src/secrets/LICENSE | 277 -------- src/secrets/OVERVIEW.md | 48 -- src/secrets/README.md | 49 -- src/secrets/package.json | 51 -- src/secrets/scripts/configure-cross.sh | 30 - src/secrets/scripts/linux-test.sh | 4 - src/secrets/scripts/prebuildCheck.js | 22 - src/secrets/scripts/prebuildify.sh | 8 - src/secrets/src/core/Cargo.lock | 622 ------------------ src/secrets/src/core/Cargo.toml | 31 - src/secrets/src/core/src/lib.rs | 1 - src/secrets/src/core/src/os/error.rs | 41 -- src/secrets/src/core/src/os/mac/error.rs | 66 -- src/secrets/src/core/src/os/mac/ffi.rs | 118 ---- src/secrets/src/core/src/os/mac/keychain.rs | 114 ---- .../src/core/src/os/mac/keychain_item.rs | 56 -- .../src/core/src/os/mac/keychain_search.rs | 249 ------- src/secrets/src/core/src/os/mac/misc.rs | 21 - src/secrets/src/core/src/os/mac/mod.rs | 160 ----- src/secrets/src/core/src/os/mod.rs | 14 - src/secrets/src/core/src/os/unix.rs | 221 ------- src/secrets/src/core/src/os/win.rs | 340 ---------- src/secrets/src/index.ts | 12 - .../src/{keyring-test => keyring}/.gitignore | 0 .../src/{keyring-test => keyring}/Cargo.lock | 3 +- .../src/{keyring-test => keyring}/Cargo.toml | 6 +- .../{keyring-test => keyring}/pyproject.toml | 2 +- .../src/{keyring-test => keyring}/src/lib.rs | 0 src/secrets/tsconfig.json | 15 - .../zowe/secrets_for_zowe_sdk/__init__.py | 0 32 files changed, 7 insertions(+), 2607 deletions(-) delete mode 100644 src/secrets/CHANGELOG.md delete mode 100644 src/secrets/LICENSE delete mode 100644 src/secrets/OVERVIEW.md delete mode 100644 src/secrets/README.md delete mode 100644 src/secrets/package.json delete mode 100644 src/secrets/scripts/configure-cross.sh delete mode 100644 src/secrets/scripts/linux-test.sh delete mode 100644 src/secrets/scripts/prebuildCheck.js delete mode 100644 src/secrets/scripts/prebuildify.sh delete mode 100644 src/secrets/src/core/Cargo.lock delete mode 100644 src/secrets/src/core/Cargo.toml delete mode 100644 src/secrets/src/core/src/lib.rs delete mode 100644 src/secrets/src/core/src/os/error.rs delete mode 100644 src/secrets/src/core/src/os/mac/error.rs delete mode 100644 src/secrets/src/core/src/os/mac/ffi.rs delete mode 100644 src/secrets/src/core/src/os/mac/keychain.rs delete mode 100644 src/secrets/src/core/src/os/mac/keychain_item.rs delete mode 100644 src/secrets/src/core/src/os/mac/keychain_search.rs delete mode 100644 src/secrets/src/core/src/os/mac/misc.rs delete mode 100644 src/secrets/src/core/src/os/mac/mod.rs delete mode 100644 src/secrets/src/core/src/os/mod.rs delete mode 100644 src/secrets/src/core/src/os/unix.rs delete mode 100644 src/secrets/src/core/src/os/win.rs delete mode 100644 src/secrets/src/index.ts rename src/secrets/src/{keyring-test => keyring}/.gitignore (100%) rename src/secrets/src/{keyring-test => keyring}/Cargo.lock (99%) rename src/secrets/src/{keyring-test => keyring}/Cargo.toml (60%) rename src/secrets/src/{keyring-test => keyring}/pyproject.toml (94%) rename src/secrets/src/{keyring-test => keyring}/src/lib.rs (100%) delete mode 100644 src/secrets/tsconfig.json create mode 100644 src/secrets/zowe/secrets_for_zowe_sdk/__init__.py diff --git a/.github/workflows/secrets-sdk.yml b/.github/workflows/secrets-sdk.yml index 830638ed..2cd40371 100644 --- a/.github/workflows/secrets-sdk.yml +++ b/.github/workflows/secrets-sdk.yml @@ -17,7 +17,7 @@ on: defaults: run: - working-directory: packages/secrets + working-directory: packages/secrets/src/keyring permissions: contents: read diff --git a/src/secrets/CHANGELOG.md b/src/secrets/CHANGELOG.md deleted file mode 100644 index 054df8ce..00000000 --- a/src/secrets/CHANGELOG.md +++ /dev/null @@ -1,31 +0,0 @@ -# Change Log - -All notable changes to the Zowe Secrets SDK package will be documented in this file. - -## `7.18.6` - -- BugFix: Use `core-foundation-rs` instead of `security-framework` for macOS logic, as `security-framework` is now archived. [#1802](https://github.com/zowe/zowe-cli/issues/1802) -- BugFix: Resolve bug where `findCredentials` scenarios with one match causes a segmentation fault on Linux. - -## `7.18.5` - -- BugFix: Enable `KeyringError::Library` enum variant to fix building on FreeBSD targets. - -## `7.18.4` - -- BugFix: Separated module resolution logic during installation; added more error handling to provide a more graceful installation process. -- BugFix: Add static CRT when compiling Windows builds. -- Added OVERVIEW document to package: provides context on the Secrets SDK transition and how it affects Zowe CLI and Zowe Explorer. - -## `7.18.2` - -- BugFix: Adds logic to allow the `keyring` module to locate the current package directory for the `prebuilds/` folder. - -## `7.18.1` - -- Added README to package w/ description, instructions and examples of using the `keyring` module. - -## `7.18.0` - -- Initial release. -- `keyring` module added for interacting with OS-specific keyring/credential vaults. See [src/keyring](src/keyring/README.md) for information on this native module and how it can be used. \ No newline at end of file diff --git a/src/secrets/LICENSE b/src/secrets/LICENSE deleted file mode 100644 index d3087e4c..00000000 --- a/src/secrets/LICENSE +++ /dev/null @@ -1,277 +0,0 @@ -Eclipse Public License - v 2.0 - - THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE - PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION - OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. - -1. DEFINITIONS - -"Contribution" means: - - a) in the case of the initial Contributor, the initial content - Distributed under this Agreement, and - - b) in the case of each subsequent Contributor: - i) changes to the Program, and - ii) additions to the Program; - where such changes and/or additions to the Program originate from - and are Distributed by that particular Contributor. A Contribution - "originates" from a Contributor if it was added to the Program by - such Contributor itself or anyone acting on such Contributor's behalf. - Contributions do not include changes or additions to the Program that - are not Modified Works. - -"Contributor" means any person or entity that Distributes the Program. - -"Licensed Patents" mean patent claims licensable by a Contributor which -are necessarily infringed by the use or sale of its Contribution alone -or when combined with the Program. - -"Program" means the Contributions Distributed in accordance with this -Agreement. - -"Recipient" means anyone who receives the Program under this Agreement -or any Secondary License (as applicable), including Contributors. - -"Derivative Works" shall mean any work, whether in Source Code or other -form, that is based on (or derived from) the Program and for which the -editorial revisions, annotations, elaborations, or other modifications -represent, as a whole, an original work of authorship. - -"Modified Works" shall mean any work in Source Code or other form that -results from an addition to, deletion from, or modification of the -contents of the Program, including, for purposes of clarity any new file -in Source Code form that contains any contents of the Program. Modified -Works shall not include works that contain only declarations, -interfaces, types, classes, structures, or files of the Program solely -in each case in order to link to, bind by name, or subclass the Program -or Modified Works thereof. - -"Distribute" means the acts of a) distributing or b) making available -in any manner that enables the transfer of a copy. - -"Source Code" means the form of a Program preferred for making -modifications, including but not limited to software source code, -documentation source, and configuration files. - -"Secondary License" means either the GNU General Public License, -Version 2.0, or any later versions of that license, including any -exceptions or additional permissions as identified by the initial -Contributor. - -2. GRANT OF RIGHTS - - a) Subject to the terms of this Agreement, each Contributor hereby - grants Recipient a non-exclusive, worldwide, royalty-free copyright - license to reproduce, prepare Derivative Works of, publicly display, - publicly perform, Distribute and sublicense the Contribution of such - Contributor, if any, and such Derivative Works. - - b) Subject to the terms of this Agreement, each Contributor hereby - grants Recipient a non-exclusive, worldwide, royalty-free patent - license under Licensed Patents to make, use, sell, offer to sell, - import and otherwise transfer the Contribution of such Contributor, - if any, in Source Code or other form. This patent license shall - apply to the combination of the Contribution and the Program if, at - the time the Contribution is added by the Contributor, such addition - of the Contribution causes such combination to be covered by the - Licensed Patents. The patent license shall not apply to any other - combinations which include the Contribution. No hardware per se is - licensed hereunder. - - c) Recipient understands that although each Contributor grants the - licenses to its Contributions set forth herein, no assurances are - provided by any Contributor that the Program does not infringe the - patent or other intellectual property rights of any other entity. - Each Contributor disclaims any liability to Recipient for claims - brought by any other entity based on infringement of intellectual - property rights or otherwise. As a condition to exercising the - rights and licenses granted hereunder, each Recipient hereby - assumes sole responsibility to secure any other intellectual - property rights needed, if any. For example, if a third party - patent license is required to allow Recipient to Distribute the - Program, it is Recipient's responsibility to acquire that license - before distributing the Program. - - d) Each Contributor represents that to its knowledge it has - sufficient copyright rights in its Contribution, if any, to grant - the copyright license set forth in this Agreement. - - e) Notwithstanding the terms of any Secondary License, no - Contributor makes additional grants to any Recipient (other than - those set forth in this Agreement) as a result of such Recipient's - receipt of the Program under the terms of a Secondary License - (if permitted under the terms of Section 3). - -3. REQUIREMENTS - -3.1 If a Contributor Distributes the Program in any form, then: - - a) the Program must also be made available as Source Code, in - accordance with section 3.2, and the Contributor must accompany - the Program with a statement that the Source Code for the Program - is available under this Agreement, and informs Recipients how to - obtain it in a reasonable manner on or through a medium customarily - used for software exchange; and - - b) the Contributor may Distribute the Program under a license - different than this Agreement, provided that such license: - i) effectively disclaims on behalf of all other Contributors all - warranties and conditions, express and implied, including - warranties or conditions of title and non-infringement, and - implied warranties or conditions of merchantability and fitness - for a particular purpose; - - ii) effectively excludes on behalf of all other Contributors all - liability for damages, including direct, indirect, special, - incidental and consequential damages, such as lost profits; - - iii) does not attempt to limit or alter the recipients' rights - in the Source Code under section 3.2; and - - iv) requires any subsequent distribution of the Program by any - party to be under a license that satisfies the requirements - of this section 3. - -3.2 When the Program is Distributed as Source Code: - - a) it must be made available under this Agreement, or if the - Program (i) is combined with other material in a separate file or - files made available under a Secondary License, and (ii) the initial - Contributor attached to the Source Code the notice described in - Exhibit A of this Agreement, then the Program may be made available - under the terms of such Secondary Licenses, and - - b) a copy of this Agreement must be included with each copy of - the Program. - -3.3 Contributors may not remove or alter any copyright, patent, -trademark, attribution notices, disclaimers of warranty, or limitations -of liability ("notices") contained within the Program from any copy of -the Program which they Distribute, provided that Contributors may add -their own appropriate notices. - -4. COMMERCIAL DISTRIBUTION - -Commercial distributors of software may accept certain responsibilities -with respect to end users, business partners and the like. While this -license is intended to facilitate the commercial use of the Program, -the Contributor who includes the Program in a commercial product -offering should do so in a manner which does not create potential -liability for other Contributors. Therefore, if a Contributor includes -the Program in a commercial product offering, such Contributor -("Commercial Contributor") hereby agrees to defend and indemnify every -other Contributor ("Indemnified Contributor") against any losses, -damages and costs (collectively "Losses") arising from claims, lawsuits -and other legal actions brought by a third party against the Indemnified -Contributor to the extent caused by the acts or omissions of such -Commercial Contributor in connection with its distribution of the Program -in a commercial product offering. The obligations in this section do not -apply to any claims or Losses relating to any actual or alleged -intellectual property infringement. In order to qualify, an Indemnified -Contributor must: a) promptly notify the Commercial Contributor in -writing of such claim, and b) allow the Commercial Contributor to control, -and cooperate with the Commercial Contributor in, the defense and any -related settlement negotiations. The Indemnified Contributor may -participate in any such claim at its own expense. - -For example, a Contributor might include the Program in a commercial -product offering, Product X. That Contributor is then a Commercial -Contributor. If that Commercial Contributor then makes performance -claims, or offers warranties related to Product X, those performance -claims and warranties are such Commercial Contributor's responsibility -alone. Under this section, the Commercial Contributor would have to -defend claims against the other Contributors related to those performance -claims and warranties, and if a court requires any other Contributor to -pay any damages as a result, the Commercial Contributor must pay -those damages. - -5. NO WARRANTY - -EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT -PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS" -BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR -IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF -TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR -PURPOSE. Each Recipient is solely responsible for determining the -appropriateness of using and distributing the Program and assumes all -risks associated with its exercise of rights under this Agreement, -including but not limited to the risks and costs of program errors, -compliance with applicable laws, damage to or loss of data, programs -or equipment, and unavailability or interruption of operations. - -6. DISCLAIMER OF LIABILITY - -EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT -PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS -SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST -PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE -EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - -7. GENERAL - -If any provision of this Agreement is invalid or unenforceable under -applicable law, it shall not affect the validity or enforceability of -the remainder of the terms of this Agreement, and without further -action by the parties hereto, such provision shall be reformed to the -minimum extent necessary to make such provision valid and enforceable. - -If Recipient institutes patent litigation against any entity -(including a cross-claim or counterclaim in a lawsuit) alleging that the -Program itself (excluding combinations of the Program with other software -or hardware) infringes such Recipient's patent(s), then such Recipient's -rights granted under Section 2(b) shall terminate as of the date such -litigation is filed. - -All Recipient's rights under this Agreement shall terminate if it -fails to comply with any of the material terms or conditions of this -Agreement and does not cure such failure in a reasonable period of -time after becoming aware of such noncompliance. If all Recipient's -rights under this Agreement terminate, Recipient agrees to cease use -and distribution of the Program as soon as reasonably practicable. -However, Recipient's obligations under this Agreement and any licenses -granted by Recipient relating to the Program shall continue and survive. - -Everyone is permitted to copy and distribute copies of this Agreement, -but in order to avoid inconsistency the Agreement is copyrighted and -may only be modified in the following manner. The Agreement Steward -reserves the right to publish new versions (including revisions) of -this Agreement from time to time. No one other than the Agreement -Steward has the right to modify this Agreement. The Eclipse Foundation -is the initial Agreement Steward. The Eclipse Foundation may assign the -responsibility to serve as the Agreement Steward to a suitable separate -entity. Each new version of the Agreement will be given a distinguishing -version number. The Program (including Contributions) may always be -Distributed subject to the version of the Agreement under which it was -received. In addition, after a new version of the Agreement is published, -Contributor may elect to Distribute the Program (including its -Contributions) under the new version. - -Except as expressly stated in Sections 2(a) and 2(b) above, Recipient -receives no rights or licenses to the intellectual property of any -Contributor under this Agreement, whether expressly, by implication, -estoppel or otherwise. All rights in the Program not expressly granted -under this Agreement are reserved. Nothing in this Agreement is intended -to be enforceable by any entity that is not a Contributor or Recipient. -No third-party beneficiary rights are created under this Agreement. - -Exhibit A - Form of Secondary Licenses Notice - -"This Source Code may also be made available under the following -Secondary Licenses when the conditions for such availability set forth -in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), -version(s), and exceptions or additional permissions here}." - - Simply including a copy of this Agreement, including this Exhibit A - is not sufficient to license the Source Code under Secondary Licenses. - - If it is not possible or desirable to put the notice in a particular - file, then You may include the notice in a location (such as a LICENSE - file in a relevant directory) where a recipient would be likely to - look for such a notice. - - You may add additional accurate notices of copyright ownership. diff --git a/src/secrets/OVERVIEW.md b/src/secrets/OVERVIEW.md deleted file mode 100644 index 33968e61..00000000 --- a/src/secrets/OVERVIEW.md +++ /dev/null @@ -1,48 +0,0 @@ -# Secrets SDK - -Secrets for Zowe SDK is a new development package used in the following Zowe releases: -- Zowe CLI - - v7.18.0 and above from the NPM package - - Bundle v2.11.0 and above from [Zowe.org](https://www.zowe.org/download#download-v2) -- Zowe Explorer and Zowe Explorer API - - v2.10.0 and above (v2 LTS) - - v1.22.5 and above (v1 LTS) - -The `keyring` module in this package replaces node-keytar. Node-keytar was developed by Atom, and is no longer maintained as of December 15th, 2022. - -## Impact on End Users - -### Zowe CLI - -CLI users should not be affected by this change. It is intended to be a drop-in replacement for node-keytar, so existing credentials will still be accessible. - -### Zowe Explorer - -- **Most** Zowe Explorer users should not be affected by this change. -- Some Zowe Explorer users may be affected by this change. Specifically, users connecting over remote environments with Zowe Explorer are required to re-enter credentials. - - Since keytar has been removed from VS Code for the next release, Zowe Explorer cannot access the local credential vault while the extension is running through a remote server. - - "Remote environments" include: - - Remote SSH - - Remote Tunnel - - Remote Docker - - [Click here](https://github.com/zowe/vscode-extension-for-zowe/wiki/Usage-in-Remote-Environments) for more information. - -## Impact on Extenders - -**CAUTION:** Regardless of whether you extend Zowe CLI or Zowe Explorer, developers that directly reference `keytar` as a dependency or dev dependency must use the new `keyring` module in `@zowe/secrets-for-zowe-sdk`. - -### Zowe CLI - -- Developers can continue using the credential manager utilities from Imperative. -- Developers that indirectly use Zowe CLI to store and load secure credentials are not affected. - -### Zowe Explorer - -- Extenders that leverage Zowe Explorer API and Webpack (or another bundler) are required to update their extensions to provide prebuilds for the Secrets SDK. - - This involves providing a folder named `prebuilds` with the Secrets SDK binaries alongside their extension root directory (same level as `package.json`). - - [Click here](https://github.com/zowe/zowe-cli/blob/master/packages/secrets/src/keyring/EXTENDERS.md#webpackingbundling-alongside-your-project) for more information on this process. -- Extenders that do not use a bundler can continue using the credential manager utilities from Zowe Explorer API. These extensions are not affected by this change. - ---- - -For more information on how to use the Secrets SDK, visit the [README for the `keyring` module](/packages/secrets/src/keyring/README.md). \ No newline at end of file diff --git a/src/secrets/README.md b/src/secrets/README.md deleted file mode 100644 index 70eac693..00000000 --- a/src/secrets/README.md +++ /dev/null @@ -1,49 +0,0 @@ -# Secrets Package - -Contains APIs for secure credential management. - -## `keyring` API Examples - -Developers that reference the dependency `keytar` directly in their code need to use the new `keyring` module from this package. - -Use the `keyring` module in the same fashion as `keytar`. - -### Storing and loading credentials - -```js -const { keyring } = require("@zowe/secrets-for-zowe-sdk"); -await keyring.setPassword("ServiceName", "AccountName", "SomePassword"); - -const password = await keyring.getPassword("ServiceName", "AccountName"); -// password should equal "SomePassword" -``` - -### Finding a credential - -```js -const { keyring } = require("@zowe/secrets-for-zowe-sdk"); -const password = await keyring.findPassword("ServiceName/AccountName"); -// password should equal "SomePassword" -``` - -### Finding all credentials matching service - -```js -const { keyring } = require("@zowe/secrets-for-zowe-sdk"); -const matchingCredentials = await keyring.findCredentials("ServiceName"); -// returns: -// [ -// { account: "AccountName", password: "SomePassword" }, -// ... -// ] -``` - -### Deleting a credential - -```js -const { keyring } = require("@zowe/secrets-for-zowe-sdk"); -const wasDeleted = await keyring.deletePassword("ServiceName", "AccountName"); -// wasDeleted should be true; ServiceName/AccountName removed from credential vault -``` - -For more detailed information, see [src/keyring/EXTENDERS.md](/packages/secrets/src/keyring/EXTENDERS.md). \ No newline at end of file diff --git a/src/secrets/package.json b/src/secrets/package.json deleted file mode 100644 index d265ccde..00000000 --- a/src/secrets/package.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "name": "@zowe/secrets-for-zowe-sdk", - "description": "Credential management facilities for Imperative, Zowe CLI, and extenders.", - "repository": "https://github.com/zowe/zowe-cli.git", - "author": "Zowe", - "version": "7.18.6", - "homepage": "https://github.com/zowe/zowe-cli/tree/master/packages/secrets#readme", - "bugs": { - "url": "https://github.com/zowe/zowe-cli/issues" - }, - "private": true, - "main": "lib/index.js", - "types": "lib/index.d.ts", - "files": [ - "lib", - "prebuilds/*.node", - "src/keyring", - "src/keyring/.cargo", - "index.d.ts", - "index.js", - "scripts/*.js" - ], - "publishConfig": { - "registry": "https://zowe.jfrog.io/zowe/api/npm/npm-local-release/" - }, - "license": "EPL-2.0", - "devDependencies": { - "@napi-rs/cli": "^2.16.2", - "@types/node": "^14.18.28", - "ava": "^4.3.3", - "typescript": "^4.0.0" - }, - "ava": { - "timeout": "3m" - }, - "engines": { - "node": ">= 14" - }, - "scripts": { - "artifacts": "napi artifacts", - "build": "npm run build:ts && cd src/keyring && napi build --config napi.json --js false --platform --release", - "build:debug": "cd src/keyring && napi build --config napi.json --js false --platform", - "build:ts": "tsc --pretty", - "install": "node scripts/prebuildCheck.js || npm run rebuild", - "prepack": "node ../../scripts/prepareLicenses.js", - "prepublishOnly": "bash scripts/prebuildify.sh", - "rebuild": "npx --yes --package=@napi-rs/cli@2.16.2 -- napi build --config src/keyring/napi.json --cargo-cwd src/keyring --platform --release --js=false src/keyring", - "test": "ava", - "version": "napi version" - } -} diff --git a/src/secrets/scripts/configure-cross.sh b/src/secrets/scripts/configure-cross.sh deleted file mode 100644 index a1263a8b..00000000 --- a/src/secrets/scripts/configure-cross.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/bin/bash - -# Set environment variables needed for cross-compilation in current shell -set_env() { - export PKG_CONFIG_SYSROOT_DIR="${CHROOT:-/}" - export RUSTFLAGS="-L $CHROOT$1 $RUSTFLAGS" - export PKG_CONFIG_PATH="$CHROOT$1/pkgconfig" -} - -case "$1" in - "aarch64-unknown-linux-gnu") - set_env "/usr/lib/aarch64-linux-gnu" - ;; - "aarch64-unknown-linux-musl") - CHROOT="/aarch64-linux-musl-cross" - RUSTFLAGS="-C linker=$CHROOT/bin/aarch64-linux-musl-gcc" - set_env "/usr/lib" - ;; - "armv7-unknown-linux-gnueabihf") - set_env "/usr/lib/arm-linux-gnueabihf" - ;; - "i686-unknown-linux-gnu") - set_env "/usr/lib/i386-linux-gnu" - ;; - "x86_64-unknown-linux-gnu") - set_env "/usr/lib/x86_64-linux-gnu" - ;; - *) - ;; -esac diff --git a/src/secrets/scripts/linux-test.sh b/src/secrets/scripts/linux-test.sh deleted file mode 100644 index 3af4397f..00000000 --- a/src/secrets/scripts/linux-test.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env bash -rm -f $HOME/.local/share/keyrings/* -echo -n "test" | gnome-keyring-daemon --unlock -yarn test diff --git a/src/secrets/scripts/prebuildCheck.js b/src/secrets/scripts/prebuildCheck.js deleted file mode 100644 index 5410f65e..00000000 --- a/src/secrets/scripts/prebuildCheck.js +++ /dev/null @@ -1,22 +0,0 @@ -/* -* This program and the accompanying materials are made available under the terms of the -* Eclipse Public License v2.0 which accompanies this distribution, and is available at -* https://www.eclipse.org/legal/epl-v20.html -* -* SPDX-License-Identifier: EPL-2.0 -* -* Copyright Contributors to the Zowe Project. -* -*/ - -const { join } = require("path"); - -try { - require(join("..", "lib", "index.js")); -} catch (err) { - if (err.code === "ERR_MODULE_NOT_FOUND" || err.code === "MODULE_NOT_FOUND") { - throw new Error(`Unable to find prebuilds for Secrets SDK keyring module: ${err.message}`); - } else { - console.error(err.message); - } -} \ No newline at end of file diff --git a/src/secrets/scripts/prebuildify.sh b/src/secrets/scripts/prebuildify.sh deleted file mode 100644 index 9342d770..00000000 --- a/src/secrets/scripts/prebuildify.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash -set -ex -rm -rf prebuilds && mkdir -p prebuilds -SECRETS_BRANCH="${SECRETS_BRANCH:-master}" -SECRETS_WORKFLOW_ID=$(gh run list -b $SECRETS_BRANCH --limit 1 --status success --workflow "Secrets SDK CI" --json databaseId --jq ".[0].databaseId") -echo "Downloading Secrets SDK prebuilds from $SECRETS_BRANCH branch..." -gh run download $SECRETS_WORKFLOW_ID --dir prebuilds --pattern "bindings-*" -mv prebuilds/*/* prebuilds && rm -r prebuilds/*/ \ No newline at end of file diff --git a/src/secrets/src/core/Cargo.lock b/src/secrets/src/core/Cargo.lock deleted file mode 100644 index fa08d203..00000000 --- a/src/secrets/src/core/Cargo.lock +++ /dev/null @@ -1,622 +0,0 @@ -# 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 = "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", - "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 = "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 = "memchr" -version = "2.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" - -[[package]] -name = "once_cell" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" - -[[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 = "quote" -version = "1.0.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "secrets_core" -version = "0.1.0" -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 = "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/src/core/Cargo.toml b/src/secrets/src/core/Cargo.toml deleted file mode 100644 index b178909c..00000000 --- a/src/secrets/src/core/Cargo.toml +++ /dev/null @@ -1,31 +0,0 @@ -[package] -name = "secrets_core" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -cfg-if = "1.0" -thiserror = "1.0.38" - -[target.'cfg(target_os = "windows")'.dependencies.windows-sys] -features = [ - "Win32_Foundation", - "Win32_Security_Credentials", - "Win32_System_Diagnostics_Debug", - "Win32_System_Memory", - "Win32_System_WindowsProgramming", -] -version = "0.48.0" - -[target.'cfg(target_os = "macos")'.dependencies] -core-foundation = "0.9.3" -core-foundation-sys = "0.8.4" - -[target.'cfg(any(target_os = "freebsd", target_os = "linux"))'.dependencies] -glib = "0.18.2" -glib-sys = "0.18.1" -gio = "0.18.2" -libsecret = "0.4.0" -libsecret-sys = "0.4.0" \ No newline at end of file diff --git a/src/secrets/src/core/src/lib.rs b/src/secrets/src/core/src/lib.rs deleted file mode 100644 index dda20c34..00000000 --- a/src/secrets/src/core/src/lib.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod os; \ No newline at end of file diff --git a/src/secrets/src/core/src/os/error.rs b/src/secrets/src/core/src/os/error.rs deleted file mode 100644 index 7d7c251d..00000000 --- a/src/secrets/src/core/src/os/error.rs +++ /dev/null @@ -1,41 +0,0 @@ -use std::{str::Utf8Error, string::FromUtf16Error, string::FromUtf8Error}; -use thiserror::Error; - -#[derive(Error, Debug)] -pub enum KeyringError { - #[cfg(target_os = "macos")] - #[error("[keyring] Invalid parameter provided for '{argument:?}'. Details:\n\n{details:?}")] - InvalidArg { argument: String, details: String }, - - #[cfg(any(target_os = "macos", target_os = "linux", target_os = "freebsd"))] - #[error("[keyring] {name:?} library returned an error:\n\n{details:?}")] - Library { name: String, details: String }, - - #[cfg(not(target_os = "macos"))] - #[error("[keyring] An OS error has occurred:\n\n{0}")] - Os(String), - - #[error("[keyring] A UTF-8 error has occurred:\n\n{0}")] - Utf8(String), - - #[error("[keyring] A UTF-16 error has occurred:\n\n{0}")] - Utf16(String), -} - -impl From for KeyringError { - fn from(error: FromUtf8Error) -> Self { - KeyringError::Utf8(format!("{:?}", error)) - } -} - -impl From for KeyringError { - fn from(error: FromUtf16Error) -> Self { - KeyringError::Utf16(format!("{:?}", error)) - } -} - -impl From for KeyringError { - fn from(error: Utf8Error) -> Self { - KeyringError::Utf8(format!("{:?}", error)) - } -} diff --git a/src/secrets/src/core/src/os/mac/error.rs b/src/secrets/src/core/src/os/mac/error.rs deleted file mode 100644 index 980527b0..00000000 --- a/src/secrets/src/core/src/os/mac/error.rs +++ /dev/null @@ -1,66 +0,0 @@ -use crate::os::mac::ffi::SecCopyErrorMessageString; -use core_foundation::base::TCFType; -use core_foundation::string::CFString; -use core_foundation_sys::base::OSStatus; -use std::fmt::{Debug, Display, Formatter}; -use std::num::NonZeroI32; - -#[derive(Copy, Clone)] -pub struct Error(NonZeroI32); - -/// errSecItemNotFound -pub const ERR_SEC_ITEM_NOT_FOUND: i32 = -25300; - -impl Error { - #[inline] - #[must_use] - pub fn from_code(code: OSStatus) -> Self { - Self(NonZeroI32::new(code).unwrap_or_else(|| NonZeroI32::new(1).unwrap())) - } - - pub fn code(self) -> i32 { - self.0.get() as _ - } - - /// Gets the message matching an OSStatus error code, if one exists. - pub fn message(&self) -> Option { - unsafe { - let s = SecCopyErrorMessageString(self.code(), std::ptr::null_mut()); - if s.is_null() { - None - } else { - Some(CFString::wrap_under_create_rule(s).to_string()) - } - } - } -} - -impl Debug for Error { - fn fmt(&self, fmt: &mut Formatter<'_>) -> std::fmt::Result { - let mut builder = fmt.debug_struct("Error"); - builder.field("code", &self.0); - if let Some(message) = self.message() { - builder.field("message", &message); - } - builder.finish() - } -} - -impl Display for Error { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self.message() { - Some(msg) => write!(f, "{}", msg), - None => write!(f, "code: {}", self.code()), - } - } -} - -/// Handles the OSStatus code from macOS FFI calls (error handling helper fn) -#[inline(always)] -pub fn handle_os_status(err: OSStatus) -> Result<(), Error> { - match err { - // errSecSuccess - 0 => Ok(()), - err => Err(Error::from_code(err)), - } -} diff --git a/src/secrets/src/core/src/os/mac/ffi.rs b/src/secrets/src/core/src/os/mac/ffi.rs deleted file mode 100644 index e76e92c0..00000000 --- a/src/secrets/src/core/src/os/mac/ffi.rs +++ /dev/null @@ -1,118 +0,0 @@ -use core_foundation_sys::base::{CFTypeID, CFTypeRef, OSStatus}; -use core_foundation_sys::dictionary::CFDictionaryRef; -use core_foundation_sys::string::CFStringRef; -use std::ffi::{c_char, c_void}; - -/// -/// Keychain item reference types. -/// -/// See lib/SecBase.h here: -/// https://opensource.apple.com/source/libsecurity_keychain/libsecurity_keychain-55050.2/ -/// -pub enum OpaqueSecKeychainItemRef {} -pub enum OpaqueSecKeychainRef {} -pub type SecKeychainItemRef = *mut OpaqueSecKeychainItemRef; -pub type SecKeychainRef = *mut OpaqueSecKeychainRef; - -/// -/// Certificate item reference types. -/// https://developer.apple.com/documentation/security/opaqueseccertificateref -/// -pub enum OpaqueSecCertificateRef {} -pub type SecCertificateRef = *mut OpaqueSecCertificateRef; - -/// -/// Identity reference types. -/// https://developer.apple.com/documentation/security/opaquesecidentityref -/// -pub enum OpaqueSecIdentityRef {} -pub type SecIdentityRef = *mut OpaqueSecIdentityRef; - -/// -/// Key reference types. -/// https://developer.apple.com/documentation/security/seckeyref -/// -pub enum OpaqueSecKeyRef {} -pub type SecKeyRef = *mut OpaqueSecKeyRef; - -/// -/// Keychain attribute structure for searching items. -/// https://developer.apple.com/documentation/security/seckeychainattribute, -/// https://developer.apple.com/documentation/security/seckeychainattributelist -/// -#[repr(C)] -#[derive(Copy, Clone)] -pub struct SecKeychainAttribute { - pub tag: u32, - pub length: u32, - pub data: *mut c_void, -} -#[repr(C)] -#[derive(Copy, Clone)] -pub struct SecKeychainAttributeList { - pub count: u32, - pub attr: *mut SecKeychainAttribute, -} - -/* - * Defined below are the C functions that the Rust logic - * uses to interact with macOS's Security.framework. - * - * Since we can call C functions directly using Rust FFI, we just need to define - * the function prototypes ahead of time, and link them to the Security.framework library. - * Rust will evaluate these symbols during compile time. - */ -#[link(name = "Security", kind = "framework")] -extern "C" { - // keychain.rs: - pub fn SecCopyErrorMessageString(status: OSStatus, reserved: *mut c_void) -> CFStringRef; - pub fn SecKeychainAddGenericPassword( - keychain: SecKeychainRef, - service_name_length: u32, - service_name: *const c_char, - account_name_length: u32, - account_name: *const c_char, - password_length: u32, - password_data: *const c_void, - item_ref: *mut SecKeychainItemRef, - ) -> OSStatus; - pub fn SecKeychainCopyDefault(keychain: *mut SecKeychainRef) -> OSStatus; - pub fn SecKeychainFindGenericPassword( - keychain_or_array: CFTypeRef, - service_name_len: u32, - service_name: *const c_char, - account_name_len: u32, - account_name: *const c_char, - password_len: *mut u32, - password: *mut *mut c_void, - item_ref: *mut SecKeychainItemRef, - ) -> OSStatus; - pub fn SecKeychainGetTypeID() -> CFTypeID; - - // keychain_item.rs: - pub fn SecKeychainItemDelete(item_ref: SecKeychainItemRef) -> OSStatus; - pub fn SecKeychainItemGetTypeID() -> CFTypeID; - pub fn SecKeychainItemModifyAttributesAndData( - item_ref: SecKeychainItemRef, - attr_list: *const SecKeychainAttributeList, - length: u32, - data: *const c_void, - ) -> OSStatus; - - // keychain_search.rs: - pub fn SecItemCopyMatching(query: CFDictionaryRef, result: *mut CFTypeRef) -> OSStatus; - pub static kSecAttrAccount: CFStringRef; - pub static kSecAttrLabel: CFStringRef; - pub static kSecAttrService: CFStringRef; - pub static kSecClass: CFStringRef; - pub static kSecClassGenericPassword: CFStringRef; - pub static kSecMatchLimit: CFStringRef; - pub static kSecReturnAttributes: CFStringRef; - pub static kSecReturnData: CFStringRef; - pub static kSecReturnRef: CFStringRef; - - // misc.rs: - pub fn SecCertificateGetTypeID() -> CFTypeID; - pub fn SecIdentityGetTypeID() -> CFTypeID; - pub fn SecKeyGetTypeID() -> CFTypeID; -} diff --git a/src/secrets/src/core/src/os/mac/keychain.rs b/src/secrets/src/core/src/os/mac/keychain.rs deleted file mode 100644 index 250b7c25..00000000 --- a/src/secrets/src/core/src/os/mac/keychain.rs +++ /dev/null @@ -1,114 +0,0 @@ -use crate::os::mac::error::{handle_os_status, Error}; -use crate::os::mac::ffi::{ - SecKeychainAddGenericPassword, SecKeychainCopyDefault, SecKeychainFindGenericPassword, - SecKeychainGetTypeID, SecKeychainRef, -}; -use crate::os::mac::keychain_item::SecKeychainItem; -use core_foundation::{base::TCFType, declare_TCFType, impl_TCFType}; -use std::ops::Deref; - -/* - * SecKeychain: https://developer.apple.com/documentation/security/seckeychain - * SecKeychainRef: https://developer.apple.com/documentation/security/seckeychainref - */ -declare_TCFType! { - SecKeychain, SecKeychainRef -} -impl_TCFType!(SecKeychain, SecKeychainRef, SecKeychainGetTypeID); - -/* Wrapper struct for handling passwords within SecKeychainItem objects. */ -pub struct KeychainItemPassword { - pub data: *const u8, - pub data_len: usize, -} - -impl AsRef<[u8]> for KeychainItemPassword { - #[inline] - fn as_ref(&self) -> &[u8] { - unsafe { std::slice::from_raw_parts(self.data, self.data_len) } - } -} - -impl Deref for KeychainItemPassword { - type Target = [u8]; - #[inline] - fn deref(&self) -> &Self::Target { - self.as_ref() - } -} - -impl SecKeychain { - pub fn default() -> Result { - let mut keychain = std::ptr::null_mut(); - unsafe { - handle_os_status(SecKeychainCopyDefault(&mut keychain))?; - } - unsafe { Ok(Self::wrap_under_create_rule(keychain)) } - } - - /// - /// set_password - /// Attempts to set the password within the keychain for a given service and account. - /// - /// Returns: - /// - Nothing if the password was set successfully, or - /// - An `Error` object if an error was encountered - /// - pub fn set_password(&self, service: &str, account: &str, password: &[u8]) -> Result<(), Error> { - match self.find_password(service, account) { - Ok((_, mut item)) => item.set_password(password), - _ => unsafe { - handle_os_status(SecKeychainAddGenericPassword( - self.as_CFTypeRef() as *mut _, - service.len() as u32, - service.as_ptr().cast(), - account.len() as u32, - account.as_ptr().cast(), - password.len() as u32, - password.as_ptr().cast(), - std::ptr::null_mut(), - )) - }, - } - } - - /// - /// find_password - /// Attempts to find a password within the keychain matching a given service and account. - /// - /// Returns: - /// - A pair containing the KeychainItem object with its password data if the password was found, or - /// - An `Error` object if an error was encountered - /// - pub fn find_password( - &self, - service: &str, - account: &str, - ) -> Result<(KeychainItemPassword, SecKeychainItem), Error> { - let keychain_ref = self.as_CFTypeRef(); - - let mut len = 0; - let mut data = std::ptr::null_mut(); - let mut item = std::ptr::null_mut(); - - unsafe { - handle_os_status(SecKeychainFindGenericPassword( - keychain_ref, - service.len() as u32, - service.as_ptr().cast(), - account.len() as u32, - account.as_ptr().cast(), - &mut len, - &mut data, - &mut item, - ))?; - Ok(( - KeychainItemPassword { - data: data as *const _, - data_len: len as usize, - }, - SecKeychainItem::wrap_under_create_rule(item), - )) - } - } -} diff --git a/src/secrets/src/core/src/os/mac/keychain_item.rs b/src/secrets/src/core/src/os/mac/keychain_item.rs deleted file mode 100644 index 8008e278..00000000 --- a/src/secrets/src/core/src/os/mac/keychain_item.rs +++ /dev/null @@ -1,56 +0,0 @@ -use crate::os::mac::error::{handle_os_status, Error}; -use crate::os::mac::ffi::{ - SecKeychainItemDelete, SecKeychainItemGetTypeID, SecKeychainItemModifyAttributesAndData, - SecKeychainItemRef, -}; -use core_foundation::base::TCFType; -use core_foundation::{declare_TCFType, impl_TCFType}; - -/* - * SecKeychainItem: https://developer.apple.com/documentation/security/seckeychainitem - * SecKeychainItemRef: https://developer.apple.com/documentation/security/seckeychainitemref - */ -declare_TCFType! { - SecKeychainItem, SecKeychainItemRef -} -impl_TCFType! { - SecKeychainItem, - SecKeychainItemRef, - SecKeychainItemGetTypeID -} - -impl SecKeychainItem { - /// - /// delete - /// Attempts to delete this keychain item from the keychain. - /// - /// Returns: - /// - Nothing if the deletion request was successful, or - /// - An `Error` object if an error was encountered - /// - #[inline] - pub fn delete(self) -> Result<(), Error> { - unsafe { handle_os_status(SecKeychainItemDelete(self.as_CFTypeRef() as *mut _)) } - } - - /// - /// set_password - /// Attempts to set the password for this keychain item. - /// - /// Returns: - /// - Nothing if the password was set successfully, or - /// - An `Error` object if an error was encountered - /// - pub fn set_password(&mut self, password: &[u8]) -> Result<(), Error> { - unsafe { - handle_os_status(SecKeychainItemModifyAttributesAndData( - self.as_CFTypeRef() as *mut _, - std::ptr::null(), - password.len() as u32, - password.as_ptr().cast(), - ))?; - } - - Ok(()) - } -} diff --git a/src/secrets/src/core/src/os/mac/keychain_search.rs b/src/secrets/src/core/src/os/mac/keychain_search.rs deleted file mode 100644 index 8de998df..00000000 --- a/src/secrets/src/core/src/os/mac/keychain_search.rs +++ /dev/null @@ -1,249 +0,0 @@ -use crate::os::mac::error::{handle_os_status, Error}; -use crate::os::mac::ffi::{ - kSecAttrAccount, kSecAttrLabel, kSecAttrService, kSecClass, kSecClassGenericPassword, - kSecMatchLimit, kSecReturnAttributes, kSecReturnData, kSecReturnRef, SecItemCopyMatching, -}; -use crate::os::mac::keychain_item::SecKeychainItem; -use crate::os::mac::misc::{SecCertificate, SecIdentity, SecKey}; -use core_foundation::array::CFArray; -use core_foundation::base::{CFType, TCFType}; -use core_foundation::boolean::CFBoolean; -use core_foundation::data::CFData; -use core_foundation::date::CFDate; -use core_foundation::dictionary::CFDictionary; -use core_foundation::number::CFNumber; -use core_foundation::string::CFString; -use core_foundation_sys::base::{CFCopyDescription, CFGetTypeID, CFRelease, CFTypeRef}; -use std::collections::HashMap; - -/// Keychain Search structure to reference when making searches within the keychain. -#[derive(Default)] -pub struct KeychainSearch { - label: Option, - service: Option, - account: Option, - load_attrs: bool, - load_data: bool, - load_refs: bool, -} - -/// Reference enum for categorizing search results based on item type. -pub enum Reference { - Identity(SecIdentity), - Certificate(SecCertificate), - Key(SecKey), - KeychainItem(SecKeychainItem), -} - -/// Enum for organizing types of items found during the keychain search operation. -pub enum SearchResult { - Ref(Reference), - Dict(CFDictionary), - Data(Vec), -} - -impl SearchResult { - /// - /// parse_dict - /// Tries to parse a CFDictionary object into a hashmap of string pairs. - /// - /// Returns: - /// - `Some(hash_map)` containing the attribute keys/values if parsed successfully - /// - `None` otherwise - #[must_use] - pub fn parse_dict(&self) -> Option> { - match *self { - Self::Dict(ref d) => unsafe { - // build map of attributes to return for this search result - let mut retmap = HashMap::new(); - let (keys, values) = d.get_keys_and_values(); - for (k, v) in keys.iter().zip(values.iter()) { - // get key as CFString from pointer - let key_cfstr = CFString::wrap_under_get_rule((*k).cast()); - - // get value based on CFType - let val: String = match CFGetTypeID(*v) { - cfstring if cfstring == CFString::type_id() => { - format!("{}", CFString::wrap_under_get_rule((*v).cast())) - } - cfdata if cfdata == CFData::type_id() => { - let buf = CFData::wrap_under_get_rule((*v).cast()); - let mut vec = Vec::new(); - vec.extend_from_slice(buf.bytes()); - format!("{}", String::from_utf8_lossy(&vec)) - } - cfdate if cfdate == CFDate::type_id() => format!( - "{}", - CFString::wrap_under_create_rule(CFCopyDescription(*v)) - ), - _ => String::from("unknown"), - }; - retmap.insert(format!("{}", key_cfstr), val); - } - Some(retmap) - }, - _ => None, - } - } -} - -/// -/// get_item -/// -/// item: The item reference to convert to a SearchResult -/// Returns: -/// - a SearchResult enum variant based on the item reference provided. -/// -unsafe fn get_item(item: CFTypeRef) -> SearchResult { - let type_id = CFGetTypeID(item); - - // if type is a raw buffer, return Vec of bytes based on item size - if type_id == CFData::type_id() { - let data = CFData::wrap_under_get_rule(item as *mut _); - let mut buf = Vec::new(); - buf.extend_from_slice(data.bytes()); - return SearchResult::Data(buf); - } - - // if type is dictionary of items, cast as CFDictionary object - if type_id == CFDictionary::<*const u8, *const u8>::type_id() { - return SearchResult::Dict(CFDictionary::wrap_under_get_rule(item as *mut _)); - } - - // if type is a single Keychain item, return it as a reference - if type_id == SecKeychainItem::type_id() { - return SearchResult::Ref(Reference::KeychainItem( - SecKeychainItem::wrap_under_get_rule(item as *mut _), - )); - } - - // handle certificate, cryptographic key, and identity types as - // they can also appear in search results for the keychain - let reference = match type_id { - r if r == SecCertificate::type_id() => { - Reference::Certificate(SecCertificate::wrap_under_get_rule(item as *mut _)) - } - r if r == SecKey::type_id() => Reference::Key(SecKey::wrap_under_get_rule(item as *mut _)), - r if r == SecIdentity::type_id() => { - Reference::Identity(SecIdentity::wrap_under_get_rule(item as *mut _)) - } - _ => panic!("Bad type received from SecItemCopyMatching: {}", type_id), - }; - - SearchResult::Ref(reference) -} - -impl KeychainSearch { - #[inline(always)] - #[must_use] - pub fn new() -> Self { - Self::default() - } - - pub fn label(&mut self, label: &str) -> &mut Self { - self.label = Some(CFString::new(label)); - self - } - - pub fn with_attrs(&mut self) -> &mut Self { - self.load_attrs = true; - self - } - - pub fn with_data(&mut self) -> &mut Self { - self.load_data = true; - self - } - - pub fn with_refs(&mut self) -> &mut Self { - self.load_refs = true; - self - } - - /// Executes a search within the keychain, factoring in the set search options. - /// - /// Returns: - /// - If successful, a `Vec` containing a list of search results - /// - an `Error` object otherwise - pub fn execute(&self) -> Result, Error> { - let mut params = vec![]; - - unsafe { - params.push(( - CFString::wrap_under_get_rule(kSecClass), - CFType::wrap_under_get_rule(kSecClassGenericPassword.cast()), - )); - - // Handle any parameters that were configured before execution (label, service, account) - if let Some(ref label) = self.label { - params.push(( - CFString::wrap_under_get_rule(kSecAttrLabel), - label.as_CFType(), - )); - } - if let Some(ref service) = self.service { - params.push(( - CFString::wrap_under_get_rule(kSecAttrService), - service.as_CFType(), - )); - } - if let Some(ref acc) = self.account { - params.push(( - CFString::wrap_under_get_rule(kSecAttrAccount), - acc.as_CFType(), - )); - } - - // Add params to fetch data, attributes, and/or refs if requested from search options - if self.load_data { - params.push(( - CFString::wrap_under_get_rule(kSecReturnData), - CFBoolean::true_value().into_CFType(), - )); - } - if self.load_attrs { - params.push(( - CFString::wrap_under_get_rule(kSecReturnAttributes), - CFBoolean::true_value().into_CFType(), - )); - } - if self.load_refs { - params.push(( - CFString::wrap_under_get_rule(kSecReturnRef), - CFBoolean::true_value().into_CFType(), - )); - } - - // Remove the default limit of 0 by requesting all items that match the search - params.push(( - CFString::wrap_under_get_rule(kSecMatchLimit), - CFNumber::from(i32::MAX).into_CFType(), - )); - - let params = CFDictionary::from_CFType_pairs(¶ms); - let mut ret = std::ptr::null(); - - // handle copy operation status and get type ID based on return value - handle_os_status(SecItemCopyMatching(params.as_concrete_TypeRef(), &mut ret))?; - if ret.is_null() { - return Ok(vec![]); - } - let type_id = CFGetTypeID(ret); - - // Build vector of items based on return reference type - let mut items = vec![]; - if type_id == CFArray::::type_id() { - let array: CFArray = CFArray::wrap_under_create_rule(ret as *mut _); - for item in array.iter() { - items.push(get_item(item.as_CFTypeRef())); - } - } else { - items.push(get_item(ret)); - - CFRelease(ret); - } - - Ok(items) - } - } -} diff --git a/src/secrets/src/core/src/os/mac/misc.rs b/src/secrets/src/core/src/os/mac/misc.rs deleted file mode 100644 index 1af69da4..00000000 --- a/src/secrets/src/core/src/os/mac/misc.rs +++ /dev/null @@ -1,21 +0,0 @@ -use crate::os::mac::ffi::{ - SecCertificateGetTypeID, SecCertificateRef, SecIdentityGetTypeID, SecIdentityRef, - SecKeyGetTypeID, SecKeyRef, -}; -use core_foundation::base::TCFType; -use core_foundation::{declare_TCFType, impl_TCFType}; - -// Structure that represents identities within the keychain -// https://developer.apple.com/documentation/security/secidentity -declare_TCFType!(SecIdentity, SecIdentityRef); -impl_TCFType!(SecIdentity, SecIdentityRef, SecIdentityGetTypeID); - -// Structure that represents certificates within the keychain -// https://developer.apple.com/documentation/security/seccertificate -declare_TCFType!(SecCertificate, SecCertificateRef); -impl_TCFType!(SecCertificate, SecCertificateRef, SecCertificateGetTypeID); - -// Structure that represents cryptographic keys within the keychain -// https://developer.apple.com/documentation/security/seckey -declare_TCFType!(SecKey, SecKeyRef); -impl_TCFType!(SecKey, SecKeyRef, SecKeyGetTypeID); diff --git a/src/secrets/src/core/src/os/mac/mod.rs b/src/secrets/src/core/src/os/mac/mod.rs deleted file mode 100644 index 3ff76377..00000000 --- a/src/secrets/src/core/src/os/mac/mod.rs +++ /dev/null @@ -1,160 +0,0 @@ -use super::error::KeyringError; - -mod error; -mod ffi; -mod keychain; -mod keychain_item; -mod keychain_search; -mod misc; - -use error::Error; - -use crate::os::mac::error::ERR_SEC_ITEM_NOT_FOUND; -use crate::os::mac::keychain_search::{KeychainSearch, SearchResult}; -use keychain::SecKeychain; - -impl From for KeyringError { - fn from(error: Error) -> Self { - KeyringError::Library { - name: "macOS Security.framework".to_owned(), - details: format!("{:?}", error.message()), - } - } -} - -/// -/// Attempts to set a password for a given service and account. -/// -/// - `service`: The service name for the new credential -/// - `account`: The account name for the new credential -/// -/// Returns: -/// - `true` if the credential was stored successfully -/// - A `KeyringError` if there were any issues interacting with the credential vault -/// -pub fn set_password( - service: &String, - account: &String, - password: &mut String, -) -> Result { - let keychain = SecKeychain::default().unwrap(); - match keychain.set_password(service.as_str(), account.as_str(), password.as_bytes()) { - Ok(()) => Ok(true), - Err(err) => Err(KeyringError::from(err)), - } -} - -/// -/// Returns a password contained in the given service and account, if found. -/// -/// - `service`: The service name that matches the credential of interest -/// - `account`: The account name that matches the credential of interest -/// -/// Returns: -/// - `Some(password)` if a matching credential was found; `None` otherwise -/// - A `KeyringError` if there were any issues interacting with the credential vault -/// -pub fn get_password(service: &String, account: &String) -> Result, KeyringError> { - let keychain = SecKeychain::default().unwrap(); - match keychain.find_password(service.as_str(), account.as_str()) { - Ok((pw, _)) => Ok(Some(String::from_utf8(pw.to_owned())?)), - Err(err) if err.code() == ERR_SEC_ITEM_NOT_FOUND => Ok(None), - Err(err) => Err(KeyringError::from(err)), - } -} - -/// -/// Returns the first password (if any) that matches the given service pattern. -/// -/// - `service`: The service pattern that matches the credential of interest -/// -/// Returns: -/// - `Some(password)` if a matching credential was found; `None` otherwise -/// - A `KeyringError` if there were any issues interacting with the credential vault -/// -pub fn find_password(service: &String) -> Result, KeyringError> { - let cred_attrs: Vec<&str> = service.split("/").collect(); - if cred_attrs.len() < 2 { - return Err(KeyringError::InvalidArg { - argument: "service".to_owned(), - details: "Invalid format for service string; must be in format 'SERVICE/ACCOUNT'" - .to_owned(), - }); - } - - let keychain = SecKeychain::default().unwrap(); - match keychain.find_password(cred_attrs[0], cred_attrs[1]) { - Ok((pw, _)) => { - let pw_str = String::from_utf8(pw.to_owned())?; - return Ok(Some(pw_str)); - } - Err(err) => Err(KeyringError::from(err)), - } -} - -/// -/// Attempts to delete the password associated with a given service and account. -/// -/// - `service`: The service name of the credential to delete -/// - `account`: The account name of the credential to delete -/// -/// Returns: -/// - `true` if a matching credential was deleted; `false` otherwise -/// - A `KeyringError` if there were any issues interacting with the credential vault -/// -pub fn delete_password(service: &String, account: &String) -> Result { - let keychain = SecKeychain::default().unwrap(); - match keychain.find_password(service.as_str(), account.as_str()) { - Ok((_, item)) => { - item.delete()?; - return Ok(true); - } - Err(err) if err.code() == ERR_SEC_ITEM_NOT_FOUND => Ok(false), - Err(err) => Err(KeyringError::from(err)), - } -} - -/// -/// Builds a vector of all credentials matching the given service pattern. -/// -/// - `service`: The service pattern that matches the credential(s) of interest -/// - `credentials`: The vector consisting of (username, password) pairs for each credential that matches -/// -/// Returns: -/// - `true` if at least 1 credential was found, `false` otherwise -/// - A `KeyringError` if there were any issues interacting with the credential vault -/// -pub fn find_credentials( - service: &String, - credentials: &mut Vec<(String, String)>, -) -> Result { - match KeychainSearch::new() - .label(service.as_str()) - .with_attrs() - .with_data() - .with_refs() - .execute() - { - Ok(search_results) => { - *credentials = search_results - .iter() - .filter_map(|result| match result { - SearchResult::Dict(_) => { - return match result.parse_dict() { - Some(attrs) => Some(( - attrs.get("acct").unwrap().to_owned(), - attrs.get("v_Data").unwrap().to_owned(), - )), - None => None, - }; - } - _ => None, - }) - .collect(); - - Ok(!credentials.is_empty()) - } - Err(err) if err.code() == ERR_SEC_ITEM_NOT_FOUND => Ok(false), - Err(err) => Err(KeyringError::from(err)), - } -} diff --git a/src/secrets/src/core/src/os/mod.rs b/src/secrets/src/core/src/os/mod.rs deleted file mode 100644 index 640ee83f..00000000 --- a/src/secrets/src/core/src/os/mod.rs +++ /dev/null @@ -1,14 +0,0 @@ -pub mod error; - -cfg_if::cfg_if! { - if #[cfg(target_os = "windows")] { - pub mod win; - pub use win::{delete_password, find_credentials, find_password, get_password, set_password}; - } else if #[cfg(target_os = "macos")] { - pub mod mac; - pub use mac::{delete_password, find_credentials, find_password, get_password, set_password}; - } else if #[cfg(any(target_os = "freebsd", target_os = "linux"))] { - pub mod unix; - pub use unix::{delete_password, find_credentials, find_password, get_password, set_password}; - } -} diff --git a/src/secrets/src/core/src/os/unix.rs b/src/secrets/src/core/src/os/unix.rs deleted file mode 100644 index 723817a7..00000000 --- a/src/secrets/src/core/src/os/unix.rs +++ /dev/null @@ -1,221 +0,0 @@ -extern crate glib_sys; -extern crate libsecret; -use glib::translate::{FromGlibPtrContainer, ToGlibPtr}; -use glib_sys::g_hash_table_unref; -use libsecret::{ - prelude::CollectionExtManual, traits::ItemExt, SearchFlags, Service, ServiceFlags, -}; -use std::collections::HashMap; - -use super::error::KeyringError; - -impl From for KeyringError { - fn from(err: glib::error::Error) -> Self { - KeyringError::Library { - name: "glib".to_owned(), - details: format!("{:?}", err.message().to_owned()), - } - } -} - -/// -/// Returns the libsecret schema that corresponds to service and account attributes. -/// -fn get_schema() -> libsecret::Schema { - libsecret::Schema::new( - "org.freedesktop.Secret.Generic", - libsecret::SchemaFlags::NONE, - HashMap::from([ - ("service", libsecret::SchemaAttributeType::String), - ("account", libsecret::SchemaAttributeType::String), - ]), - ) -} - -/// -/// Builds an attribute map with the given service and account names. -/// -/// Returns: -/// - A `HashMap` built with the `service` and `account` values provided. Used for attribute functions. -/// -fn get_attribute_map<'a>(service: &'a str, account: &'a str) -> HashMap<&'a str, &'a str> { - HashMap::from([("service", service), ("account", account)]) -} - -/// -/// Attempts to set a password for a given service and account. -/// -/// - `service`: The service name for the new credential -/// - `account`: The account name for the new credential -/// -/// Returns: -/// - `true` if the credential was stored successfully -/// - A `KeyringError` if there were any issues interacting with the credential vault -/// -pub fn set_password( - service: &String, - account: &String, - password: &String, -) -> Result { - let attributes = get_attribute_map(service.as_str(), account.as_str()); - - let collection = libsecret::COLLECTION_DEFAULT; - match libsecret::password_store_sync( - Some(&get_schema()), - attributes, - Some(collection), - format!("{}/{}", service, account).as_str(), - password.as_str(), - gio::Cancellable::NONE, - ) { - Ok(_) => Ok(true), - Err(err) => Err(KeyringError::from(err)), - } -} - -/// -/// Returns a password contained in the given service and account, if found. -/// -/// - `service`: The service name that matches the credential of interest -/// - `account`: The account name that matches the credential of interest -/// -/// Returns: -/// - `Some(password)` if a matching credential was found; `None` otherwise -/// - A `KeyringError` if there were any issues interacting with the credential vault -/// -pub fn get_password(service: &String, account: &String) -> Result, KeyringError> { - let attributes = get_attribute_map(service.as_str(), account.as_str()); - - match libsecret::password_lookup_sync(Some(&get_schema()), attributes, gio::Cancellable::NONE) { - Ok(pw) => match pw { - Some(pass) => Ok(Some(pass.to_string())), - None => Ok(None), - }, - Err(err) => Err(KeyringError::from(err)), - } -} - -/// -/// Returns the first password (if any) that matches the given service pattern. -/// -/// - `service`: The service pattern that matches the credential of interest -/// -/// Returns: -/// - `Some(password)` if a matching credential was found; `None` otherwise -/// - A `KeyringError` if there were any issues interacting with the credential vault -/// -pub fn find_password(service: &String) -> Result, KeyringError> { - let attributes = if service.contains("/") && service.len() > 1 { - // In format "service/account" - let values: Vec<&str> = service.split("/").collect(); - get_attribute_map(values[0], values[1]) - } else { - HashMap::from([("service", service.as_str())]) - }; - - match libsecret::password_lookup_sync(Some(&get_schema()), attributes, gio::Cancellable::NONE) { - Ok(pw) => match pw { - Some(pass) => Ok(Some(pass.to_string())), - None => Ok(None), - }, - Err(err) => Err(KeyringError::from(err)), - } -} - -/// -/// Attempts to delete the password associated with a given service and account. -/// -/// - `service`: The service name of the credential to delete -/// - `account`: The account name of the credential to delete -/// -/// Returns: -/// - `true` if a matching credential was deleted; `false` otherwise -/// - A `KeyringError` if there were any issues interacting with the credential vault -/// -pub fn delete_password(service: &String, account: &String) -> Result { - match libsecret::password_clear_sync( - Some(&get_schema()), - get_attribute_map(service.as_str(), account.as_str()), - gio::Cancellable::NONE, - ) { - Ok(_) => Ok(true), - Err(err) => match err.kind() { - Some(glib::KeyFileError::NotFound) => Ok(false), - _ => Err(KeyringError::from(err)), - }, - } -} - -/// -/// Builds a vector of all credentials matching the given service pattern. -/// -/// - `service`: The service pattern that matches the credential(s) of interest -/// - `credentials`: The vector consisting of (username, password) pairs for each credential that matches -/// -/// Returns: -/// - `true` if at least 1 credential was found, `false` otherwise -/// - A `KeyringError` if there were any issues interacting with the credential vault -/// -pub fn find_credentials( - service: &String, - credentials: &mut Vec<(String, String)>, -) -> Result { - let secret_service = Service::sync( - ServiceFlags::OPEN_SESSION | ServiceFlags::LOAD_COLLECTIONS, - gio::Cancellable::NONE, - )?; - let collection = match libsecret::Collection::for_alias_sync( - Some(&secret_service), - "default", - libsecret::CollectionFlags::LOAD_ITEMS, - gio::Cancellable::NONE, - )? { - Some(col) => col, - None => { - return Err(KeyringError::Os( - "Unable to open libsecret collection".to_owned(), - )) - } - }; - - match collection.search_sync( - Some(&get_schema()), - HashMap::from([("service", service.as_str())]), - SearchFlags::ALL | SearchFlags::UNLOCK | SearchFlags::LOAD_SECRETS, - gio::Cancellable::NONE, - ) { - Ok(vec) => { - let valid_creds: Vec<(String, String)> = vec - .iter() - .filter_map(|item| match item.secret() { - Some(secret) => { - let attrs: HashMap = unsafe { - let attrs = - libsecret_sys::secret_item_get_attributes(item.to_glib_none().0); - FromGlibPtrContainer::from_glib_full(attrs) - }; - let bytes = secret.get(); - let pw = String::from_utf8(bytes).unwrap_or("".to_string()); - - let acc = attrs.get("account").unwrap().clone(); - unsafe { - g_hash_table_unref(attrs.to_glib_full()); - } - Some((acc, pw)) - } - None => None, - }) - .collect(); - *credentials = valid_creds; - - Ok(true) - } - Err(err) => { - if err.message().contains("No such secret item at path") { - Ok(false) - } else { - Err(KeyringError::Os(err.message().to_owned())) - } - } - } -} diff --git a/src/secrets/src/core/src/os/win.rs b/src/secrets/src/core/src/os/win.rs deleted file mode 100644 index a66b03ab..00000000 --- a/src/secrets/src/core/src/os/win.rs +++ /dev/null @@ -1,340 +0,0 @@ -use super::error::KeyringError; -use std::ffi::c_void; -use std::result::Result; -use windows_sys::{ - core::{PCWSTR, PWSTR}, - Win32::Foundation::*, - Win32::Security::Credentials::*, - Win32::System::{ - Diagnostics::Debug::{ - FormatMessageW, FORMAT_MESSAGE_ALLOCATE_BUFFER, FORMAT_MESSAGE_FROM_SYSTEM, - FORMAT_MESSAGE_IGNORE_INSERTS, - }, - Memory::LocalFree, - }, -}; - -impl From for KeyringError { - fn from(error: WIN32_ERROR) -> Self { - KeyringError::Os(win32_error_as_string(error)) - } -} - -/// -/// Helper function to convert the last Win32 error into a human-readable error message. -/// -/// Returns: -/// A `String` object containing the error message -/// -fn win32_error_as_string(error: WIN32_ERROR) -> String { - let buffer: PWSTR = std::ptr::null_mut(); - - // https://github.com/microsoft/windows-rs/blob/master/crates/libs/core/src/hresult.rs#L96 - let as_hresult = if error == 0 { - 0 - } else { - (error & 0x0000_FFFF) | (7 << 16) | 0x8000_0000 - } as _; - let mut str = "No error details available.".to_owned(); - unsafe { - let size = FormatMessageW( - FORMAT_MESSAGE_ALLOCATE_BUFFER - | FORMAT_MESSAGE_FROM_SYSTEM - | FORMAT_MESSAGE_IGNORE_INSERTS, - as_hresult as *const c_void, - as_hresult, - 0, - buffer, - 0, - std::ptr::null(), - ); - - if buffer.is_null() { - return str; - } - - str = String::from_utf16(std::slice::from_raw_parts(buffer, size as usize)).unwrap_or(str); - LocalFree(buffer as isize); - } - - str -} - -/// -/// Helper function to encode a string as a null-terminated UTF-16 string for use w/ credential APIs. -/// -/// Returns: -/// - `Some(val)` if the string was successfully converted to UTF-16, or `None` otherwise. -/// -fn encode_utf16(str: &str) -> Vec { - let mut chars: Vec = str.encode_utf16().collect(); - chars.push(0); - chars -} - -/// -/// Attempts to set a password for a given service and account. -/// -/// - `service`: The service name for the new credential -/// - `account`: The account name for the new credential -/// -/// Returns: -/// - `true` if the credential was stored successfully -/// - A `KeyringError` if there were any issues interacting with the credential vault -/// -pub fn set_password( - service: &String, - account: &String, - password: &String, -) -> Result { - // Build WinAPI strings and object parameters from arguments - let target_bytes = encode_utf16(format!("{}/{}", service, account).as_str()); - let username_bytes = encode_utf16(account.as_str()); - - let cred = CREDENTIALW { - Flags: 0, - Type: CRED_TYPE_GENERIC, - TargetName: target_bytes.as_ptr() as PWSTR, - Comment: std::ptr::null_mut(), - LastWritten: FILETIME { - dwLowDateTime: 0, - dwHighDateTime: 0, - }, - Persist: CRED_PERSIST_ENTERPRISE, - CredentialBlobSize: password.len() as u32, - CredentialBlob: password.as_ptr() as *mut u8, - AttributeCount: 0, - Attributes: std::ptr::null_mut(), - TargetAlias: std::ptr::null_mut(), - UserName: username_bytes.as_ptr() as PWSTR, - }; - - // Save credential to user's credential set - let write_result = unsafe { CredWriteW(&cred, 0) }; - - if write_result != TRUE { - let error_code = unsafe { GetLastError() }; - return Err(KeyringError::from(error_code)); - } - - Ok(true) -} - -/// -/// Returns a password contained in the given service and account, if found. -/// -/// - `service`: The service name that matches the credential of interest -/// - `account`: The account name that matches the credential of interest -/// -/// Returns: -/// - `Some(password)` if a matching credential was found; `None` otherwise -/// - A `KeyringError` if there were any issues interacting with the credential vault -/// -pub fn get_password(service: &String, account: &String) -> Result, KeyringError> { - let mut cred: *mut CREDENTIALW = std::ptr::null_mut::(); - let target_name = encode_utf16(format!("{}/{}", service, account).as_str()); - - // Attempt to read credential from user's credential set - let read_result = unsafe { - CredReadW( - target_name.as_ptr() as PCWSTR, - CRED_TYPE_GENERIC, - 0, - &mut cred, - ) - }; - - if read_result != TRUE { - let error_code = unsafe { GetLastError() }; - if cred != std::ptr::null_mut() { - unsafe { - CredFree(cred as *const c_void); - } - } - return match error_code { - ERROR_NOT_FOUND => Ok(None), - _ => Err(KeyringError::from(error_code)), - }; - } - - // Build buffer for credential secret and return as UTF-8 string - unsafe { - let bytes = - std::slice::from_raw_parts((*cred).CredentialBlob, (*cred).CredentialBlobSize as usize); - - let result = match String::from_utf8(bytes.to_vec()) { - Ok(string) => Ok(Some(string)), - Err(err) => Err(KeyringError::Utf8( - format!("Failed to convert credential to UTF-8: {}", err).to_owned(), - )), - }; - CredFree(cred as *const c_void); - result - } -} - -/// -/// Attempts to delete the password associated with a given service and account. -/// -/// - `service`: The service name of the credential to delete -/// - `account`: The account name of the credential to delete -/// -/// Returns: -/// - `true` if a matching credential was deleted; `false` otherwise -/// - A `KeyringError` if there were any issues interacting with the credential vault -/// -pub fn delete_password(service: &String, account: &String) -> Result { - let target_name = encode_utf16(format!("{}/{}", service, account).as_str()); - - // Attempt to delete credential from user's credential set - let delete_result = - unsafe { CredDeleteW(target_name.as_ptr() as PCWSTR, CRED_TYPE_GENERIC, 0) }; - - if delete_result != TRUE { - let error_code = unsafe { GetLastError() }; - - return match error_code { - // If we are trying to delete a credential that doesn't exist, - // we didn't actually delete the password - ERROR_NOT_FOUND => Ok(false), - _ => Err(KeyringError::from(error_code)), - }; - } - - Ok(true) -} - -/// -/// Returns the first password (if any) that matches the given service pattern. -/// -/// - `service`: The service pattern that matches the credential of interest -/// -/// Returns: -/// - `Some(password)` if a matching credential was found; `None` otherwise -/// - A `KeyringError` if there were any issues interacting with the credential vault -/// -pub fn find_password(service: &String) -> Result, KeyringError> { - let filter = encode_utf16(format!("{}*", service).as_str()); - - let mut count: u32 = 0; - let mut creds: *mut *mut CREDENTIALW = std::ptr::null_mut::<*mut CREDENTIALW>(); - - // Attempt to find matching credential from user's credential set - let find_result = unsafe { - CredEnumerateW( - filter.as_ptr() as PCWSTR, - 0u32, - &mut count, - &mut creds as *mut *mut *mut CREDENTIALW, - ) - }; - - if find_result != TRUE { - let error_code = unsafe { GetLastError() }; - if creds != std::ptr::null_mut() { - unsafe { - CredFree(creds as *const c_void); - } - } - return match error_code { - ERROR_NOT_FOUND => Ok(None), - _ => Err(KeyringError::from(error_code)), - }; - } - - // Convert credential data into a valid String object and return. - unsafe { - let cred = *creds.offset(0); - let bytes = - std::slice::from_raw_parts((*cred).CredentialBlob, (*cred).CredentialBlobSize as usize); - - let result = match String::from_utf8(bytes.to_vec()) { - Ok(string) => Ok(Some(string)), - Err(err) => Err(KeyringError::from(err)), - }; - CredFree(creds as *const c_void); - result - } -} - -/// -/// Builds a vector of all credentials matching the given service pattern. -/// -/// - `service`: The service pattern that matches the credential(s) of interest -/// - `credentials`: The vector consisting of (username, password) pairs for each credential that matches -/// -/// Returns: -/// - `true` if at least 1 credential was found, `false` otherwise -/// - A `KeyringError` if there were any issues interacting with the credential vault -/// -pub fn find_credentials( - service: &String, - credentials: &mut Vec<(String, String)>, -) -> Result { - let filter_bytes: Vec = encode_utf16(format!("{}*", service).as_str()); - let filter = filter_bytes.as_ptr() as PCWSTR; - - let mut count: u32 = 0; - let mut creds: *mut *mut CREDENTIALW = std::ptr::null_mut::<*mut CREDENTIALW>(); - - // Attempt to fetch user's credential set - let find_result = unsafe { - CredEnumerateW( - filter, - 0u32, - &mut count, - &mut creds as *mut *mut *mut CREDENTIALW, - ) - }; - - if find_result != TRUE { - let error_code = unsafe { GetLastError() }; - if creds != std::ptr::null_mut() { - unsafe { - CredFree(creds as *const c_void); - } - } - return match error_code { - ERROR_NOT_FOUND => Ok(false), - _ => Err(KeyringError::from(error_code)), - }; - } - - // Find and build matching credential list from user's credential set - for i in 0..count { - let cred: &CREDENTIALW = unsafe { &**creds.offset(i as isize) }; - - if cred.UserName.is_null() || cred.CredentialBlobSize == 0 { - continue; - } - - // Build a valid String from the raw *u8 credential data. - let pw_bytes = unsafe { - std::slice::from_raw_parts((*cred).CredentialBlob, (*cred).CredentialBlobSize as usize) - }; - let password_result = match String::from_utf8(pw_bytes.to_vec()) { - Ok(string) => Ok(string), - Err(err) => Err(KeyringError::from(err)), - }; - if password_result.is_err() { - unsafe { - CredFree(creds as *const c_void); - } - return Err(password_result.unwrap_err()); - } - - // Decode the raw wchar_t* username data as a UTF-16 String - let username: String; - unsafe { - let size = (0..).take_while(|&i| *cred.UserName.offset(i) != 0).count(); - username = String::from_utf16(std::slice::from_raw_parts(cred.UserName, size))?; - } - credentials.push((username, password_result.unwrap())); - } - - unsafe { - CredFree(creds as *const c_void); - } - - Ok(true) -} diff --git a/src/secrets/src/index.ts b/src/secrets/src/index.ts deleted file mode 100644 index a4287b3f..00000000 --- a/src/secrets/src/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* -* This program and the accompanying materials are made available under the terms of the -* Eclipse Public License v2.0 which accompanies this distribution, and is available at -* https://www.eclipse.org/legal/epl-v20.html -* -* SPDX-License-Identifier: EPL-2.0 -* -* Copyright Contributors to the Zowe Project. -* -*/ - -export * as keyring from "../src/keyring"; diff --git a/src/secrets/src/keyring-test/.gitignore b/src/secrets/src/keyring/.gitignore similarity index 100% rename from src/secrets/src/keyring-test/.gitignore rename to src/secrets/src/keyring/.gitignore diff --git a/src/secrets/src/keyring-test/Cargo.lock b/src/secrets/src/keyring/Cargo.lock similarity index 99% rename from src/secrets/src/keyring-test/Cargo.lock rename to src/secrets/src/keyring/Cargo.lock index 482d67cc..5bfb7e31 100644 --- a/src/secrets/src/keyring-test/Cargo.lock +++ b/src/secrets/src/keyring/Cargo.lock @@ -240,7 +240,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa799dd5ed20a7e349f3b4639aa80d74549c81716d9ec4f994c9b5815598306" [[package]] -name = "keyring-test" +name = "keyring" version = "0.1.0" dependencies = [ "pyo3", @@ -481,6 +481,7 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "secrets_core" version = "0.1.0" +source = "git+https://github.com/zowe/zowe-cli.git?branch=feat/secrets/core-crate#a56f4c616c85cdf07a629ad1bf11b8c501f77b82" dependencies = [ "cfg-if", "core-foundation", diff --git a/src/secrets/src/keyring-test/Cargo.toml b/src/secrets/src/keyring/Cargo.toml similarity index 60% rename from src/secrets/src/keyring-test/Cargo.toml rename to src/secrets/src/keyring/Cargo.toml index bfb2b113..e150bd4a 100644 --- a/src/secrets/src/keyring-test/Cargo.toml +++ b/src/secrets/src/keyring/Cargo.toml @@ -1,13 +1,13 @@ [package] -name = "keyring-test" +name = "keyring" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib] -name = "keyring_test" +name = "keyring" crate-type = ["cdylib"] [dependencies] pyo3 = "0.19.0" -secrets_core = { path = "../core" } +secrets_core = { git = "https://github.com/zowe/zowe-cli.git", branch = "feat/secrets/core-crate" } diff --git a/src/secrets/src/keyring-test/pyproject.toml b/src/secrets/src/keyring/pyproject.toml similarity index 94% rename from src/secrets/src/keyring-test/pyproject.toml rename to src/secrets/src/keyring/pyproject.toml index 6bb30ff7..f765b5b6 100644 --- a/src/secrets/src/keyring-test/pyproject.toml +++ b/src/secrets/src/keyring/pyproject.toml @@ -3,7 +3,7 @@ requires = ["maturin>=1.3,<2.0"] build-backend = "maturin" [project] -name = "keyring-test" +name = "keyring" requires-python = ">=3.8" classifiers = [ "Programming Language :: Rust", diff --git a/src/secrets/src/keyring-test/src/lib.rs b/src/secrets/src/keyring/src/lib.rs similarity index 100% rename from src/secrets/src/keyring-test/src/lib.rs rename to src/secrets/src/keyring/src/lib.rs diff --git a/src/secrets/tsconfig.json b/src/secrets/tsconfig.json deleted file mode 100644 index 0b4bd5f7..00000000 --- a/src/secrets/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "./lib", - "types": [ - "node", - ] - }, - "include": [ - "./src" - ], - "exclude": [ - "./src/keyring" - ] -} \ No newline at end of file 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..e69de29b From 52a465eb6219940d42925c40f6a5b776ca52bea0 Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Tue, 31 Oct 2023 13:41:46 -0400 Subject: [PATCH 04/45] Fix working directory in GitHub workflow Signed-off-by: Timothy Johnson --- .github/workflows/secrets-sdk.yml | 49 ++++++++++++++++--------------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/.github/workflows/secrets-sdk.yml b/.github/workflows/secrets-sdk.yml index 2cd40371..01881206 100644 --- a/.github/workflows/secrets-sdk.yml +++ b/.github/workflows/secrets-sdk.yml @@ -15,10 +15,6 @@ on: pull_request: workflow_dispatch: -defaults: - run: - working-directory: packages/secrets/src/keyring - permissions: contents: read @@ -40,11 +36,12 @@ jobs: args: --release --out dist --find-interpreter sccache: "true" manylinux: auto + working-directory: src/secrets/src/keyring - name: Upload wheels uses: actions/upload-artifact@v3 with: name: wheels - path: dist + path: src/secrets/src/keyring/dist windows: runs-on: windows-latest @@ -63,11 +60,12 @@ jobs: target: ${{ matrix.target }} args: --release --out dist --find-interpreter sccache: "true" + working-directory: src/secrets/src/keyring - name: Upload wheels uses: actions/upload-artifact@v3 with: name: wheels - path: dist + path: src/secrets/src/keyring/dist macos: runs-on: macos-latest @@ -85,11 +83,12 @@ jobs: target: ${{ matrix.target }} args: --release --out dist --find-interpreter sccache: "true" + working-directory: src/secrets/src/keyring - name: Upload wheels uses: actions/upload-artifact@v3 with: name: wheels - path: dist + path: src/secrets/src/keyring/dist sdist: runs-on: ubuntu-latest @@ -100,25 +99,27 @@ jobs: with: command: sdist args: --out dist + working-directory: src/secrets/src/keyring - name: Upload sdist uses: actions/upload-artifact@v3 with: name: wheels - path: dist + path: src/secrets/src/keyring/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_API_TOKEN }} - with: - command: upload - args: --non-interactive --skip-existing * + # 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_API_TOKEN }} + # with: + # command: upload + # args: --non-interactive --skip-existing * + # working-directory: src/secrets/src/keyring From dcf52a475cbecc06e4c20db53ec8ada80c74a2bf Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Tue, 31 Oct 2023 14:17:29 -0400 Subject: [PATCH 05/45] Try adding script that configures cross Signed-off-by: Timothy Johnson --- .github/workflows/secrets-sdk.yml | 1 + src/secrets/scripts/configure-cross.sh | 31 ++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 src/secrets/scripts/configure-cross.sh diff --git a/.github/workflows/secrets-sdk.yml b/.github/workflows/secrets-sdk.yml index 01881206..1ef7a4d3 100644 --- a/.github/workflows/secrets-sdk.yml +++ b/.github/workflows/secrets-sdk.yml @@ -37,6 +37,7 @@ jobs: sccache: "true" manylinux: auto working-directory: src/secrets/src/keyring + before-script-linux: ../../scripts/configure-cross.sh ${{ matrix.target }} - name: Upload wheels uses: actions/upload-artifact@v3 with: diff --git a/src/secrets/scripts/configure-cross.sh b/src/secrets/scripts/configure-cross.sh new file mode 100644 index 00000000..00c3959c --- /dev/null +++ b/src/secrets/scripts/configure-cross.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +# Set environment variables needed for cross-compilation in current shell +set_env() { + export PKG_CONFIG_SYSROOT_DIR="${CHROOT:-/}" + export RUSTFLAGS="-L $CHROOT$1 $RUSTFLAGS" + export PKG_CONFIG_PATH="$CHROOT$1/pkgconfig" +} + +case "$1" in + "aarch64") + set_env "/usr/lib/aarch64-linux-gnu" + ;; + "armv7") + set_env "/usr/lib/arm-linux-gnueabihf" + ;; + "ppc64le") + set_env "/usr/lib/powerpc64le-unknown-linux-gnu" + ;; + "s390x") + set_env "/usr/lib/s390x-unknown-linux-gnu" + ;; + "x86") + set_env "/usr/lib/i386-linux-gnu" + ;; + "x86_64") + set_env "/usr/lib/x86_64-linux-gnu" + ;; + *) + ;; +esac From c5401a4f129f5fda9ee6a2c4703a2cd83abbfe04 Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Tue, 31 Oct 2023 14:27:19 -0400 Subject: [PATCH 06/45] Add executable permission to shell script Signed-off-by: Timothy Johnson --- .github/workflows/secrets-sdk.yml | 3 +++ src/secrets/scripts/configure-cross.sh | 0 2 files changed, 3 insertions(+) mode change 100644 => 100755 src/secrets/scripts/configure-cross.sh diff --git a/.github/workflows/secrets-sdk.yml b/.github/workflows/secrets-sdk.yml index 1ef7a4d3..b531a184 100644 --- a/.github/workflows/secrets-sdk.yml +++ b/.github/workflows/secrets-sdk.yml @@ -24,6 +24,7 @@ jobs: strategy: matrix: target: [x86_64, x86, aarch64, armv7, s390x, ppc64le] + fail-fast: false steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 @@ -49,6 +50,7 @@ jobs: strategy: matrix: target: [x64, x86] + fail-fast: false steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 @@ -73,6 +75,7 @@ jobs: strategy: matrix: target: [x86_64, aarch64] + fail-fast: false steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 diff --git a/src/secrets/scripts/configure-cross.sh b/src/secrets/scripts/configure-cross.sh old mode 100644 new mode 100755 From 6de5e87ccada1c33938cfa0b5aae61fd4d8f6e4a Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Tue, 31 Oct 2023 15:32:14 -0400 Subject: [PATCH 07/45] Install libsecret-1-dev in cross script Signed-off-by: Timothy Johnson --- src/secrets/scripts/configure-cross.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/secrets/scripts/configure-cross.sh b/src/secrets/scripts/configure-cross.sh index 00c3959c..ac95d023 100755 --- a/src/secrets/scripts/configure-cross.sh +++ b/src/secrets/scripts/configure-cross.sh @@ -7,7 +7,11 @@ set_env() { export PKG_CONFIG_PATH="$CHROOT$1/pkgconfig" } -case "$1" in +CROSS_DEB_ARCH=$1 +dpkg --add-architecture $CROSS_DEB_ARCH +apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y libsecret-1-dev:$CROSS_DEB_ARCH + +case "$CROSS_DEB_ARCH" in "aarch64") set_env "/usr/lib/aarch64-linux-gnu" ;; From 1e2f59151d72d294885e8533b4d27c7c5a572cf9 Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Tue, 31 Oct 2023 16:24:54 -0400 Subject: [PATCH 08/45] Try to use Ubuntu for cross-compilation Signed-off-by: Timothy Johnson --- .github/workflows/secrets-sdk.yml | 2 +- src/secrets/scripts/configure-cross.sh | 15 +++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/.github/workflows/secrets-sdk.yml b/.github/workflows/secrets-sdk.yml index b531a184..7cc6c89d 100644 --- a/.github/workflows/secrets-sdk.yml +++ b/.github/workflows/secrets-sdk.yml @@ -36,7 +36,7 @@ jobs: target: ${{ matrix.target }} args: --release --out dist --find-interpreter sccache: "true" - manylinux: auto + container: ghcr.io/rust-cross/manylinux2014-cross:${{ matrix.target }} working-directory: src/secrets/src/keyring before-script-linux: ../../scripts/configure-cross.sh ${{ matrix.target }} - name: Upload wheels diff --git a/src/secrets/scripts/configure-cross.sh b/src/secrets/scripts/configure-cross.sh index ac95d023..e4fce293 100755 --- a/src/secrets/scripts/configure-cross.sh +++ b/src/secrets/scripts/configure-cross.sh @@ -1,5 +1,11 @@ #!/bin/bash +# Install libsecret for the architecture we are compiling for +install_libs() { + dpkg --add-architecture $1 + apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y libsecret-1-dev:$1 +} + # Set environment variables needed for cross-compilation in current shell set_env() { export PKG_CONFIG_SYSROOT_DIR="${CHROOT:-/}" @@ -8,17 +14,17 @@ set_env() { } CROSS_DEB_ARCH=$1 -dpkg --add-architecture $CROSS_DEB_ARCH -apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y libsecret-1-dev:$CROSS_DEB_ARCH - -case "$CROSS_DEB_ARCH" in +case "$1" in "aarch64") + CROSS_DEB_ARCH=arm64 set_env "/usr/lib/aarch64-linux-gnu" ;; "armv7") + CROSS_DEB_ARCH=armhf set_env "/usr/lib/arm-linux-gnueabihf" ;; "ppc64le") + CROSS_DEB_ARCH=ppc64el set_env "/usr/lib/powerpc64le-unknown-linux-gnu" ;; "s390x") @@ -33,3 +39,4 @@ case "$CROSS_DEB_ARCH" in *) ;; esac +install_libs $CROSS_DEB_ARCH From 924018bc410c93b5a4632d4708d55f85bb35c039 Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Tue, 31 Oct 2023 16:31:36 -0400 Subject: [PATCH 09/45] Try again to fix architectures Signed-off-by: Timothy Johnson --- .github/workflows/secrets-sdk.yml | 2 +- src/secrets/scripts/configure-cross.sh | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/secrets-sdk.yml b/.github/workflows/secrets-sdk.yml index 7cc6c89d..eb7c6e72 100644 --- a/.github/workflows/secrets-sdk.yml +++ b/.github/workflows/secrets-sdk.yml @@ -23,7 +23,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - target: [x86_64, x86, aarch64, armv7, s390x, ppc64le] + target: [x86_64, i686, aarch64, armv7, s390x, ppc64le] fail-fast: false steps: - uses: actions/checkout@v3 diff --git a/src/secrets/scripts/configure-cross.sh b/src/secrets/scripts/configure-cross.sh index e4fce293..d165d574 100755 --- a/src/secrets/scripts/configure-cross.sh +++ b/src/secrets/scripts/configure-cross.sh @@ -13,30 +13,31 @@ set_env() { export PKG_CONFIG_PATH="$CHROOT$1/pkgconfig" } -CROSS_DEB_ARCH=$1 case "$1" in "aarch64") - CROSS_DEB_ARCH=arm64 + install_libs arm64 set_env "/usr/lib/aarch64-linux-gnu" ;; "armv7") - CROSS_DEB_ARCH=armhf + install_libs armhf set_env "/usr/lib/arm-linux-gnueabihf" ;; "ppc64le") - CROSS_DEB_ARCH=ppc64el + install_libs ppc64el set_env "/usr/lib/powerpc64le-unknown-linux-gnu" ;; "s390x") + install_libs s390x set_env "/usr/lib/s390x-unknown-linux-gnu" ;; "x86") + install_libs i386 set_env "/usr/lib/i386-linux-gnu" ;; "x86_64") + install_libs amd64 set_env "/usr/lib/x86_64-linux-gnu" ;; *) ;; esac -install_libs $CROSS_DEB_ARCH From 8a92eced13671117ea491530ac49ead9a07be473 Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Wed, 1 Nov 2023 08:41:17 -0400 Subject: [PATCH 10/45] Try using GITHUB_ENV for cross-compile options Signed-off-by: Timothy Johnson --- .github/workflows/secrets-sdk.yml | 13 +++++++++-- src/secrets/scripts/configure-cross.sh | 31 +++++++++----------------- 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/.github/workflows/secrets-sdk.yml b/.github/workflows/secrets-sdk.yml index eb7c6e72..2ad9610b 100644 --- a/.github/workflows/secrets-sdk.yml +++ b/.github/workflows/secrets-sdk.yml @@ -30,15 +30,24 @@ jobs: - uses: actions/setup-python@v4 with: python-version: "3.10" + - name: Set environment variables + run: src/secrets/scripts/configure-cross.sh - name: Build wheels uses: PyO3/maturin-action@v1 with: target: ${{ matrix.target }} args: --release --out dist --find-interpreter sccache: "true" - container: ghcr.io/rust-cross/manylinux2014-cross:${{ matrix.target }} + manylinux: auto + docker-options: --env-file $GITHUB_ENV working-directory: src/secrets/src/keyring - before-script-linux: ../../scripts/configure-cross.sh ${{ matrix.target }} + before-script-linux: | + if command -v yum &> /dev/null; then + yum update && yum install -y libsecret-devel.${{ env.CROSS_DEB_ARCH }} + else + dpkg --add-architecture ${{ env.CROSS_DEB_ARCH }} + apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y libsecret-1-dev:${{ env.CROSS_DEB_ARCH }} + fi - name: Upload wheels uses: actions/upload-artifact@v3 with: diff --git a/src/secrets/scripts/configure-cross.sh b/src/secrets/scripts/configure-cross.sh index d165d574..7cb4a581 100755 --- a/src/secrets/scripts/configure-cross.sh +++ b/src/secrets/scripts/configure-cross.sh @@ -1,42 +1,31 @@ #!/bin/bash -# Install libsecret for the architecture we are compiling for -install_libs() { - dpkg --add-architecture $1 - apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y libsecret-1-dev:$1 -} - # Set environment variables needed for cross-compilation in current shell set_env() { - export PKG_CONFIG_SYSROOT_DIR="${CHROOT:-/}" - export RUSTFLAGS="-L $CHROOT$1 $RUSTFLAGS" - export PKG_CONFIG_PATH="$CHROOT$1/pkgconfig" + echo "CROSS_DEB_ARCH=$1" >> $GITHUB_ENV + echo "PKG_CONFIG_SYSROOT_DIR=\"${CHROOT:-/}\"" >> $GITHUB_ENV + echo "RUSTFLAGS=\"-L $CHROOT$2 $RUSTFLAGS\"" >> $GITHUB_ENV + echo "PKG_CONFIG_PATH=\"$CHROOT$2/pkgconfig\"" >> $GITHUB_ENV } case "$1" in "aarch64") - install_libs arm64 - set_env "/usr/lib/aarch64-linux-gnu" + set_env arm64 "/usr/lib/aarch64-linux-gnu" ;; "armv7") - install_libs armhf - set_env "/usr/lib/arm-linux-gnueabihf" + set_env armhf "/usr/lib/arm-linux-gnueabihf" ;; "ppc64le") - install_libs ppc64el - set_env "/usr/lib/powerpc64le-unknown-linux-gnu" + set_env ppc64el "/usr/lib/powerpc64le-unknown-linux-gnu" ;; "s390x") - install_libs s390x - set_env "/usr/lib/s390x-unknown-linux-gnu" + set_env s390x "/usr/lib/s390x-unknown-linux-gnu" ;; "x86") - install_libs i386 - set_env "/usr/lib/i386-linux-gnu" + set_env i686 "/usr/lib/i386-linux-gnu" ;; "x86_64") - install_libs amd64 - set_env "/usr/lib/x86_64-linux-gnu" + set_env x86_64 "/usr/lib/x86_64-linux-gnu" ;; *) ;; From 36e8b107034df158ecbd863b2772eae9fbb52aa3 Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Wed, 1 Nov 2023 08:47:21 -0400 Subject: [PATCH 11/45] Fix passing env vars to Docker container Signed-off-by: Timothy Johnson --- .github/workflows/secrets-sdk.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/secrets-sdk.yml b/.github/workflows/secrets-sdk.yml index 2ad9610b..004479b1 100644 --- a/.github/workflows/secrets-sdk.yml +++ b/.github/workflows/secrets-sdk.yml @@ -39,14 +39,14 @@ jobs: args: --release --out dist --find-interpreter sccache: "true" manylinux: auto - docker-options: --env-file $GITHUB_ENV + docker-options: -e PKG_CONFIG_SYSROOT_DIR=${{ env.PKG_CONFIG_SYSROOT_DIR }} -e RUSTFLAGS=${{ env.RUSTFLAGS }} -e PKG_CONFIG_PATH=${{ env.PKG_CONFIG_PATH }} working-directory: src/secrets/src/keyring before-script-linux: | if command -v yum &> /dev/null; then yum update && yum install -y libsecret-devel.${{ env.CROSS_DEB_ARCH }} else dpkg --add-architecture ${{ env.CROSS_DEB_ARCH }} - apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y libsecret-1-dev:${{ env.CROSS_DEB_ARCH }} + apt-get update && apt-get install -y libsecret-1-dev:${{ env.CROSS_DEB_ARCH }} fi - name: Upload wheels uses: actions/upload-artifact@v3 From 1e01a099b582ac483da7a02e14198b12195058a4 Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Wed, 1 Nov 2023 08:57:43 -0400 Subject: [PATCH 12/45] Forgot to pass target to configure-cross script Signed-off-by: Timothy Johnson --- .github/workflows/secrets-sdk.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/secrets-sdk.yml b/.github/workflows/secrets-sdk.yml index 004479b1..cb35010c 100644 --- a/.github/workflows/secrets-sdk.yml +++ b/.github/workflows/secrets-sdk.yml @@ -23,7 +23,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - target: [x86_64, i686, aarch64, armv7, s390x, ppc64le] + target: [x86_64, x86, aarch64, armv7, s390x, ppc64le] fail-fast: false steps: - uses: actions/checkout@v3 @@ -31,7 +31,7 @@ jobs: with: python-version: "3.10" - name: Set environment variables - run: src/secrets/scripts/configure-cross.sh + run: src/secrets/scripts/configure-cross.sh ${{ matrix.target }} - name: Build wheels uses: PyO3/maturin-action@v1 with: From 1023951250e71c53047fdb3505e189e77cd501c1 Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Wed, 1 Nov 2023 09:08:28 -0400 Subject: [PATCH 13/45] Try to fix env vars in docker options Signed-off-by: Timothy Johnson --- .github/workflows/secrets-sdk.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/secrets-sdk.yml b/.github/workflows/secrets-sdk.yml index cb35010c..ec7fae4b 100644 --- a/.github/workflows/secrets-sdk.yml +++ b/.github/workflows/secrets-sdk.yml @@ -39,7 +39,7 @@ jobs: args: --release --out dist --find-interpreter sccache: "true" manylinux: auto - docker-options: -e PKG_CONFIG_SYSROOT_DIR=${{ env.PKG_CONFIG_SYSROOT_DIR }} -e RUSTFLAGS=${{ env.RUSTFLAGS }} -e PKG_CONFIG_PATH=${{ env.PKG_CONFIG_PATH }} + docker-options: -e PKG_CONFIG_SYSROOT_DIR -e PKG_CONFIG_PATH working-directory: src/secrets/src/keyring before-script-linux: | if command -v yum &> /dev/null; then From 64d671b63916f7e5953a2321f6eecc38af54b107 Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Wed, 1 Nov 2023 09:10:26 -0400 Subject: [PATCH 14/45] Don't set rust flags in configure-cross script Signed-off-by: Timothy Johnson --- src/secrets/scripts/configure-cross.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/secrets/scripts/configure-cross.sh b/src/secrets/scripts/configure-cross.sh index 7cb4a581..546ae2d9 100755 --- a/src/secrets/scripts/configure-cross.sh +++ b/src/secrets/scripts/configure-cross.sh @@ -4,7 +4,7 @@ set_env() { echo "CROSS_DEB_ARCH=$1" >> $GITHUB_ENV echo "PKG_CONFIG_SYSROOT_DIR=\"${CHROOT:-/}\"" >> $GITHUB_ENV - echo "RUSTFLAGS=\"-L $CHROOT$2 $RUSTFLAGS\"" >> $GITHUB_ENV + # echo "RUSTFLAGS=\"-L $CHROOT$2 $RUSTFLAGS\"" >> $GITHUB_ENV echo "PKG_CONFIG_PATH=\"$CHROOT$2/pkgconfig\"" >> $GITHUB_ENV } From d635e2fd3c74ffb88d3207241f7107273e4ff12c Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Wed, 1 Nov 2023 09:40:32 -0400 Subject: [PATCH 15/45] Install pkg-config in Docker containers Signed-off-by: Timothy Johnson --- .github/workflows/secrets-sdk.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/secrets-sdk.yml b/.github/workflows/secrets-sdk.yml index ec7fae4b..b83cf562 100644 --- a/.github/workflows/secrets-sdk.yml +++ b/.github/workflows/secrets-sdk.yml @@ -46,7 +46,7 @@ jobs: yum update && yum install -y libsecret-devel.${{ env.CROSS_DEB_ARCH }} else dpkg --add-architecture ${{ env.CROSS_DEB_ARCH }} - apt-get update && apt-get install -y libsecret-1-dev:${{ env.CROSS_DEB_ARCH }} + 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 From c1c9b7e921c517b854b8151550dce5d0a80dcbba Mon Sep 17 00:00:00 2001 From: Trae Yelovich Date: Wed, 1 Nov 2023 09:47:47 -0400 Subject: [PATCH 16/45] secrets: update errors to use secrets-core error info Signed-off-by: Trae Yelovich --- src/secrets/src/keyring/src/lib.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/secrets/src/keyring/src/lib.rs b/src/secrets/src/keyring/src/lib.rs index b2b7c846..6348178f 100644 --- a/src/secrets/src/keyring/src/lib.rs +++ b/src/secrets/src/keyring/src/lib.rs @@ -8,7 +8,7 @@ use secrets_core::*; 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("error in set_password")), + Err(e) => Err(PyValueError::new_err(format!("{:#?}", e))), } } @@ -16,7 +16,7 @@ fn set_password(service: String, account: String, password: String) -> PyResult< fn get_password(service: String, account: String) -> PyResult { match os::get_password(&service, &account) { Ok(pw) => Ok(pw.unwrap_or("".to_string())), - Err(e) => Err(PyValueError::new_err("error in get_password")), + Err(e) => Err(PyValueError::new_err(format!("{:#?}", e))), } } @@ -24,7 +24,7 @@ fn get_password(service: String, account: String) -> PyResult { fn delete_password(service: String, account: String) -> PyResult { match os::delete_password(&service, &account) { Ok(res) => Ok(res), - Err(e) => Err(PyValueError::new_err("error in delete_password")), + Err(e) => Err(PyValueError::new_err(format!("{:#?}", e))), } } @@ -35,7 +35,7 @@ fn find_password(service: String) -> PyResult { Some(val) => Ok(val), _ => Ok("".to_owned()), }, - Err(e) => Err(PyValueError::new_err("error in find_password")), + Err(e) => Err(PyValueError::new_err(format!("{:#?}", e))), } } @@ -44,7 +44,7 @@ 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("error in find_credentials")), + Err(e) => Err(PyValueError::new_err(format!("{:#?}", e))), } } From bebffa54f2f975ea040f723c82088a5041e9e78c Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Wed, 1 Nov 2023 09:50:22 -0400 Subject: [PATCH 17/45] Force amd64 version of pkg-config Signed-off-by: Timothy Johnson --- .github/workflows/secrets-sdk.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/secrets-sdk.yml b/.github/workflows/secrets-sdk.yml index b83cf562..70282d90 100644 --- a/.github/workflows/secrets-sdk.yml +++ b/.github/workflows/secrets-sdk.yml @@ -46,7 +46,7 @@ jobs: yum update && yum install -y libsecret-devel.${{ env.CROSS_DEB_ARCH }} else dpkg --add-architecture ${{ env.CROSS_DEB_ARCH }} - apt-get update && apt-get install -y libsecret-1-dev:${{ env.CROSS_DEB_ARCH }} pkg-config + apt-get update && apt-get install -y pkg-config:amd64 libsecret-1-dev:${{ env.CROSS_DEB_ARCH }} fi - name: Upload wheels uses: actions/upload-artifact@v3 From 03ef98b1b6bee5bcfcea03829e592747ea77e832 Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Wed, 1 Nov 2023 10:03:14 -0400 Subject: [PATCH 18/45] Try removing CROSS_DEB_ARCH Signed-off-by: Timothy Johnson --- .github/workflows/secrets-sdk.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/secrets-sdk.yml b/.github/workflows/secrets-sdk.yml index 70282d90..c9d2deda 100644 --- a/.github/workflows/secrets-sdk.yml +++ b/.github/workflows/secrets-sdk.yml @@ -43,10 +43,9 @@ jobs: working-directory: src/secrets/src/keyring before-script-linux: | if command -v yum &> /dev/null; then - yum update && yum install -y libsecret-devel.${{ env.CROSS_DEB_ARCH }} + yum update && yum install -y libsecret-devel pkgconfig else - dpkg --add-architecture ${{ env.CROSS_DEB_ARCH }} - apt-get update && apt-get install -y pkg-config:amd64 libsecret-1-dev:${{ env.CROSS_DEB_ARCH }} + apt-get update && apt-get install -y libsecret-1-dev pkg-config fi - name: Upload wheels uses: actions/upload-artifact@v3 From 6f2d47cc9b9776572ba734d7687f5724712aefd4 Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Wed, 1 Nov 2023 10:11:43 -0400 Subject: [PATCH 19/45] Try to fix rustflags env var definition Signed-off-by: Timothy Johnson --- src/secrets/scripts/configure-cross.sh | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/secrets/scripts/configure-cross.sh b/src/secrets/scripts/configure-cross.sh index 546ae2d9..aace7dfb 100755 --- a/src/secrets/scripts/configure-cross.sh +++ b/src/secrets/scripts/configure-cross.sh @@ -2,30 +2,29 @@ # Set environment variables needed for cross-compilation in current shell set_env() { - echo "CROSS_DEB_ARCH=$1" >> $GITHUB_ENV - echo "PKG_CONFIG_SYSROOT_DIR=\"${CHROOT:-/}\"" >> $GITHUB_ENV - # echo "RUSTFLAGS=\"-L $CHROOT$2 $RUSTFLAGS\"" >> $GITHUB_ENV - echo "PKG_CONFIG_PATH=\"$CHROOT$2/pkgconfig\"" >> $GITHUB_ENV + echo "PKG_CONFIG_SYSROOT_DIR=\"/\"" >> $GITHUB_ENV + echo "RUSTFLAGS=\"-L $1\"" >> $GITHUB_ENV + echo "PKG_CONFIG_PATH=\"$1/pkgconfig\"" >> $GITHUB_ENV } case "$1" in "aarch64") - set_env arm64 "/usr/lib/aarch64-linux-gnu" + set_env "/usr/lib/aarch64-linux-gnu" ;; "armv7") - set_env armhf "/usr/lib/arm-linux-gnueabihf" + set_env "/usr/lib/arm-linux-gnueabihf" ;; "ppc64le") - set_env ppc64el "/usr/lib/powerpc64le-unknown-linux-gnu" + set_env "/usr/lib/powerpc64le-unknown-linux-gnu" ;; "s390x") - set_env s390x "/usr/lib/s390x-unknown-linux-gnu" + set_env "/usr/lib/s390x-unknown-linux-gnu" ;; "x86") - set_env i686 "/usr/lib/i386-linux-gnu" + set_env "/usr/lib/i386-linux-gnu" ;; "x86_64") - set_env x86_64 "/usr/lib/x86_64-linux-gnu" + set_env "/usr/lib/x86_64-linux-gnu" ;; *) ;; From 1522236123d704566d03832cd18817984d43034f Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Wed, 1 Nov 2023 10:29:47 -0400 Subject: [PATCH 20/45] Remove quotes from vars eched to GITHUB_ENV Signed-off-by: Timothy Johnson --- src/secrets/scripts/configure-cross.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/secrets/scripts/configure-cross.sh b/src/secrets/scripts/configure-cross.sh index aace7dfb..4925cf9f 100755 --- a/src/secrets/scripts/configure-cross.sh +++ b/src/secrets/scripts/configure-cross.sh @@ -2,9 +2,9 @@ # Set environment variables needed for cross-compilation in current shell set_env() { - echo "PKG_CONFIG_SYSROOT_DIR=\"/\"" >> $GITHUB_ENV - echo "RUSTFLAGS=\"-L $1\"" >> $GITHUB_ENV - echo "PKG_CONFIG_PATH=\"$1/pkgconfig\"" >> $GITHUB_ENV + echo "PKG_CONFIG_SYSROOT_DIR=/" >> $GITHUB_ENV + echo "RUSTFLAGS=-L $1 $RUSTFLAGS" >> $GITHUB_ENV + echo "PKG_CONFIG_PATH=$1/pkgconfig" >> $GITHUB_ENV } case "$1" in From bdd9ce5035cbc8f46b3f1ec19bf55b4e5c28d65b Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Wed, 1 Nov 2023 10:51:00 -0400 Subject: [PATCH 21/45] Try to install libsecret for different arch Signed-off-by: Timothy Johnson --- .github/workflows/secrets-sdk.yml | 5 +++-- src/secrets/scripts/configure-cross.sh | 19 ++++++++++--------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/.github/workflows/secrets-sdk.yml b/.github/workflows/secrets-sdk.yml index c9d2deda..69294db9 100644 --- a/.github/workflows/secrets-sdk.yml +++ b/.github/workflows/secrets-sdk.yml @@ -43,9 +43,10 @@ jobs: working-directory: src/secrets/src/keyring before-script-linux: | if command -v yum &> /dev/null; then - yum update && yum install -y libsecret-devel pkgconfig + yum update && yum install -y libsecret-devel.${{ env.CROSS_DEB_ARCH }} pkgconfig else - apt-get update && apt-get install -y libsecret-1-dev pkg-config + dpkg --add-architecture ${{ env.CROSS_DEB_ARCH }} + 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 diff --git a/src/secrets/scripts/configure-cross.sh b/src/secrets/scripts/configure-cross.sh index 4925cf9f..893a8e61 100755 --- a/src/secrets/scripts/configure-cross.sh +++ b/src/secrets/scripts/configure-cross.sh @@ -1,30 +1,31 @@ #!/bin/bash -# Set environment variables needed for cross-compilation in current shell +# 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 $1 $RUSTFLAGS" >> $GITHUB_ENV - echo "PKG_CONFIG_PATH=$1/pkgconfig" >> $GITHUB_ENV + echo "RUSTFLAGS=-L $2 $RUSTFLAGS" >> $GITHUB_ENV + echo "PKG_CONFIG_PATH=$2/pkgconfig" >> $GITHUB_ENV } case "$1" in "aarch64") - set_env "/usr/lib/aarch64-linux-gnu" + set_env arm64 "/usr/lib/aarch64-linux-gnu" ;; "armv7") - set_env "/usr/lib/arm-linux-gnueabihf" + set_env armhf "/usr/lib/arm-linux-gnueabihf" ;; "ppc64le") - set_env "/usr/lib/powerpc64le-unknown-linux-gnu" + set_env ppc64el "/usr/lib/powerpc64le-unknown-linux-gnu" ;; "s390x") - set_env "/usr/lib/s390x-unknown-linux-gnu" + set_env s390x "/usr/lib/s390x-unknown-linux-gnu" ;; "x86") - set_env "/usr/lib/i386-linux-gnu" + set_env i686 "/usr/lib/i386-linux-gnu" ;; "x86_64") - set_env "/usr/lib/x86_64-linux-gnu" + set_env x86_64 "/usr/lib/x86_64-linux-gnu" ;; *) ;; From 505de34095a90ff760c7664ceaf33115c6d72284 Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Wed, 1 Nov 2023 11:04:26 -0400 Subject: [PATCH 22/45] Ignore missing apt packages Signed-off-by: Timothy Johnson --- .github/workflows/secrets-sdk.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/secrets-sdk.yml b/.github/workflows/secrets-sdk.yml index 69294db9..d93017ee 100644 --- a/.github/workflows/secrets-sdk.yml +++ b/.github/workflows/secrets-sdk.yml @@ -46,7 +46,7 @@ jobs: yum update && yum install -y libsecret-devel.${{ env.CROSS_DEB_ARCH }} pkgconfig else dpkg --add-architecture ${{ env.CROSS_DEB_ARCH }} - apt-get update && apt-get install -y libsecret-1-dev:${{ env.CROSS_DEB_ARCH }} pkg-config + apt-get update --fix-missing; apt-get install -y libsecret-1-dev:${{ env.CROSS_DEB_ARCH }} pkg-config fi - name: Upload wheels uses: actions/upload-artifact@v3 From f740f97be994cb56f2b662ba59b96cd9b62fd15e Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Wed, 1 Nov 2023 12:52:59 -0400 Subject: [PATCH 23/45] Ignore apt-get update errors Signed-off-by: Timothy Johnson --- .github/workflows/secrets-sdk.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/secrets-sdk.yml b/.github/workflows/secrets-sdk.yml index d93017ee..8064bcd2 100644 --- a/.github/workflows/secrets-sdk.yml +++ b/.github/workflows/secrets-sdk.yml @@ -46,7 +46,7 @@ jobs: yum update && yum install -y libsecret-devel.${{ env.CROSS_DEB_ARCH }} pkgconfig else dpkg --add-architecture ${{ env.CROSS_DEB_ARCH }} - apt-get update --fix-missing; apt-get install -y libsecret-1-dev:${{ env.CROSS_DEB_ARCH }} pkg-config + (apt-get update || true) && apt-get install -y libsecret-1-dev:${{ env.CROSS_DEB_ARCH }} pkg-config fi - name: Upload wheels uses: actions/upload-artifact@v3 From 15bd9d53c94a8bc7a4c9fc2f8708b700b20ef0f6 Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Wed, 1 Nov 2023 13:09:53 -0400 Subject: [PATCH 24/45] Try to fix apt sources for cross-compile Signed-off-by: Timothy Johnson --- .github/workflows/secrets-sdk.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/secrets-sdk.yml b/.github/workflows/secrets-sdk.yml index 8064bcd2..cca7a1fe 100644 --- a/.github/workflows/secrets-sdk.yml +++ b/.github/workflows/secrets-sdk.yml @@ -46,7 +46,10 @@ jobs: yum update && yum install -y libsecret-devel.${{ env.CROSS_DEB_ARCH }} pkgconfig else dpkg --add-architecture ${{ env.CROSS_DEB_ARCH }} - (apt-get update || true) && apt-get install -y libsecret-1-dev:${{ env.CROSS_DEB_ARCH }} pkg-config + 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 From 78a6660a48a3afba361c3c89ca8b1a696fe2b2cd Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Wed, 1 Nov 2023 13:16:39 -0400 Subject: [PATCH 25/45] Add libglib2.0-dev package for s390x Signed-off-by: Timothy Johnson --- .github/workflows/secrets-sdk.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/secrets-sdk.yml b/.github/workflows/secrets-sdk.yml index cca7a1fe..e3f630d8 100644 --- a/.github/workflows/secrets-sdk.yml +++ b/.github/workflows/secrets-sdk.yml @@ -49,7 +49,7 @@ jobs: 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 + apt-get update && apt-get install -y libglib2.0-dev:${{ env.CROSS_DEB_ARCH }} libsecret-1-dev:${{ env.CROSS_DEB_ARCH }} pkg-config fi - name: Upload wheels uses: actions/upload-artifact@v3 From 4bce88e31866dbad880bb2a65dd847a61472fc99 Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Wed, 1 Nov 2023 13:22:49 -0400 Subject: [PATCH 26/45] Fix invalid PKG_CONFIG_PATH Signed-off-by: Timothy Johnson --- .github/workflows/secrets-sdk.yml | 2 +- src/secrets/scripts/configure-cross.sh | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/secrets-sdk.yml b/.github/workflows/secrets-sdk.yml index e3f630d8..cca7a1fe 100644 --- a/.github/workflows/secrets-sdk.yml +++ b/.github/workflows/secrets-sdk.yml @@ -49,7 +49,7 @@ jobs: 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 libglib2.0-dev:${{ env.CROSS_DEB_ARCH }} libsecret-1-dev:${{ env.CROSS_DEB_ARCH }} pkg-config + 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 diff --git a/src/secrets/scripts/configure-cross.sh b/src/secrets/scripts/configure-cross.sh index 893a8e61..78caf762 100755 --- a/src/secrets/scripts/configure-cross.sh +++ b/src/secrets/scripts/configure-cross.sh @@ -16,10 +16,10 @@ case "$1" in set_env armhf "/usr/lib/arm-linux-gnueabihf" ;; "ppc64le") - set_env ppc64el "/usr/lib/powerpc64le-unknown-linux-gnu" + set_env ppc64el "/usr/lib/powerpc64le-linux-gnu" ;; "s390x") - set_env s390x "/usr/lib/s390x-unknown-linux-gnu" + set_env s390x "/usr/lib/s390x-linux-gnu" ;; "x86") set_env i686 "/usr/lib/i386-linux-gnu" From d6f512cac5fed80a3c4b99f0e57800579bb22ec5 Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Wed, 1 Nov 2023 13:57:39 -0400 Subject: [PATCH 27/45] Add py3.7 wheel and remove pypy ones Signed-off-by: Timothy Johnson --- .github/workflows/secrets-sdk.yml | 2 +- src/secrets/src/keyring/pyproject.toml | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/secrets-sdk.yml b/.github/workflows/secrets-sdk.yml index cca7a1fe..c37aa745 100644 --- a/.github/workflows/secrets-sdk.yml +++ b/.github/workflows/secrets-sdk.yml @@ -36,7 +36,7 @@ jobs: uses: PyO3/maturin-action@v1 with: target: ${{ matrix.target }} - args: --release --out dist --find-interpreter + args: --release --out dist --interpreter python3.{7..12} sccache: "true" manylinux: auto docker-options: -e PKG_CONFIG_SYSROOT_DIR -e PKG_CONFIG_PATH diff --git a/src/secrets/src/keyring/pyproject.toml b/src/secrets/src/keyring/pyproject.toml index f765b5b6..0fc19cf2 100644 --- a/src/secrets/src/keyring/pyproject.toml +++ b/src/secrets/src/keyring/pyproject.toml @@ -4,11 +4,10 @@ build-backend = "maturin" [project] name = "keyring" -requires-python = ">=3.8" +requires-python = ">=3.7" classifiers = [ "Programming Language :: Rust", "Programming Language :: Python :: Implementation :: CPython", - "Programming Language :: Python :: Implementation :: PyPy", ] dynamic = ["version"] From c9775947f9725b17f896351083ddd2cef123adc9 Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Thu, 2 Nov 2023 14:55:35 -0400 Subject: [PATCH 28/45] Make keyring a submodule of secrets sdk Signed-off-by: Timothy Johnson --- .github/workflows/secrets-sdk.yml | 26 +++++++++---------- .vscode/settings.json | 1 + requirements.txt | 1 + .../secrets_for_zowe_sdk}/.gitignore | 0 .../secrets_for_zowe_sdk}/Cargo.lock | 0 .../secrets_for_zowe_sdk}/Cargo.toml | 0 .../zowe/secrets_for_zowe_sdk/__init__.py | 0 .../secrets_for_zowe_sdk}/pyproject.toml | 2 +- .../secrets_for_zowe_sdk}/src/lib.rs | 22 +++++++++++----- 9 files changed, 31 insertions(+), 21 deletions(-) rename src/secrets/{src/keyring => zowe/secrets_for_zowe_sdk}/.gitignore (100%) rename src/secrets/{src/keyring => zowe/secrets_for_zowe_sdk}/Cargo.lock (100%) rename src/secrets/{src/keyring => zowe/secrets_for_zowe_sdk}/Cargo.toml (100%) delete mode 100644 src/secrets/zowe/secrets_for_zowe_sdk/__init__.py rename src/secrets/{src/keyring => zowe/secrets_for_zowe_sdk}/pyproject.toml (89%) rename src/secrets/{src/keyring => zowe/secrets_for_zowe_sdk}/src/lib.rs (65%) diff --git a/.github/workflows/secrets-sdk.yml b/.github/workflows/secrets-sdk.yml index c37aa745..0ba04740 100644 --- a/.github/workflows/secrets-sdk.yml +++ b/.github/workflows/secrets-sdk.yml @@ -23,7 +23,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - target: [x86_64, x86, aarch64, armv7, s390x, ppc64le] + target: [x86_64, x86, aarch64, armv7, s390x] fail-fast: false steps: - uses: actions/checkout@v3 @@ -36,11 +36,11 @@ jobs: uses: PyO3/maturin-action@v1 with: target: ${{ matrix.target }} - args: --release --out dist --interpreter python3.{7..12} + args: --release --out dist --interpreter python3.{7..12} --skip-auditwheel sccache: "true" manylinux: auto docker-options: -e PKG_CONFIG_SYSROOT_DIR -e PKG_CONFIG_PATH - working-directory: src/secrets/src/keyring + working-directory: src/secrets/zowe/secrets_for_zowe_sdk before-script-linux: | if command -v yum &> /dev/null; then yum update && yum install -y libsecret-devel.${{ env.CROSS_DEB_ARCH }} pkgconfig @@ -55,7 +55,7 @@ jobs: uses: actions/upload-artifact@v3 with: name: wheels - path: src/secrets/src/keyring/dist + path: src/secrets/zowe/secrets_for_zowe_sdk/dist windows: runs-on: windows-latest @@ -73,14 +73,14 @@ jobs: uses: PyO3/maturin-action@v1 with: target: ${{ matrix.target }} - args: --release --out dist --find-interpreter + args: --release --out dist --interpreter python3.{7..12} sccache: "true" - working-directory: src/secrets/src/keyring + working-directory: src/secrets/zowe/secrets_for_zowe_sdk - name: Upload wheels uses: actions/upload-artifact@v3 with: name: wheels - path: src/secrets/src/keyring/dist + path: src/secrets/zowe/secrets_for_zowe_sdk/dist macos: runs-on: macos-latest @@ -97,14 +97,14 @@ jobs: uses: PyO3/maturin-action@v1 with: target: ${{ matrix.target }} - args: --release --out dist --find-interpreter + args: --release --out dist --interpreter python3.{7..12} sccache: "true" - working-directory: src/secrets/src/keyring + working-directory: src/secrets/zowe/secrets_for_zowe_sdk - name: Upload wheels uses: actions/upload-artifact@v3 with: name: wheels - path: src/secrets/src/keyring/dist + path: src/secrets/zowe/secrets_for_zowe_sdk/dist sdist: runs-on: ubuntu-latest @@ -115,12 +115,12 @@ jobs: with: command: sdist args: --out dist - working-directory: src/secrets/src/keyring + working-directory: src/secrets/zowe/secrets_for_zowe_sdk - name: Upload sdist uses: actions/upload-artifact@v3 with: name: wheels - path: src/secrets/src/keyring/dist + path: src/secrets/zowe/secrets_for_zowe_sdk/dist # release: # name: Release @@ -138,4 +138,4 @@ jobs: # with: # command: upload # args: --non-interactive --skip-existing * - # working-directory: src/secrets/src/keyring + # working-directory: src/secrets/zowe/secrets_for_zowe_sdk diff --git a/.vscode/settings.json b/.vscode/settings.json index baf3b191..414fbd92 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/src/keyring/Cargo.toml"], "[python]": { "editor.codeActionsOnSave": { "source.organizeImports": true diff --git a/requirements.txt b/requirements.txt index c685de39..050d602a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,6 +12,7 @@ isort jsonschema==4.17.3 keyring lxml==4.9.3 +maturin mccabe==0.6.1 nose2==0.10.0 pycodestyle==2.6.0 diff --git a/src/secrets/src/keyring/.gitignore b/src/secrets/zowe/secrets_for_zowe_sdk/.gitignore similarity index 100% rename from src/secrets/src/keyring/.gitignore rename to src/secrets/zowe/secrets_for_zowe_sdk/.gitignore diff --git a/src/secrets/src/keyring/Cargo.lock b/src/secrets/zowe/secrets_for_zowe_sdk/Cargo.lock similarity index 100% rename from src/secrets/src/keyring/Cargo.lock rename to src/secrets/zowe/secrets_for_zowe_sdk/Cargo.lock diff --git a/src/secrets/src/keyring/Cargo.toml b/src/secrets/zowe/secrets_for_zowe_sdk/Cargo.toml similarity index 100% rename from src/secrets/src/keyring/Cargo.toml rename to src/secrets/zowe/secrets_for_zowe_sdk/Cargo.toml diff --git a/src/secrets/zowe/secrets_for_zowe_sdk/__init__.py b/src/secrets/zowe/secrets_for_zowe_sdk/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/secrets/src/keyring/pyproject.toml b/src/secrets/zowe/secrets_for_zowe_sdk/pyproject.toml similarity index 89% rename from src/secrets/src/keyring/pyproject.toml rename to src/secrets/zowe/secrets_for_zowe_sdk/pyproject.toml index 0fc19cf2..e78b6e8f 100644 --- a/src/secrets/src/keyring/pyproject.toml +++ b/src/secrets/zowe/secrets_for_zowe_sdk/pyproject.toml @@ -3,7 +3,7 @@ requires = ["maturin>=1.3,<2.0"] build-backend = "maturin" [project] -name = "keyring" +name = "zowe.secrets_for_zowe_sdk" requires-python = ">=3.7" classifiers = [ "Programming Language :: Rust", diff --git a/src/secrets/src/keyring/src/lib.rs b/src/secrets/zowe/secrets_for_zowe_sdk/src/lib.rs similarity index 65% rename from src/secrets/src/keyring/src/lib.rs rename to src/secrets/zowe/secrets_for_zowe_sdk/src/lib.rs index 6348178f..88205fe4 100644 --- a/src/secrets/src/keyring/src/lib.rs +++ b/src/secrets/zowe/secrets_for_zowe_sdk/src/lib.rs @@ -1,5 +1,5 @@ use pyo3::exceptions::PyValueError; -use pyo3::prelude::*; +use pyo3::{prelude::*, py_run}; extern crate secrets_core; use secrets_core::*; @@ -50,11 +50,19 @@ fn find_credentials(service: String) -> PyResult> { /// A Python module implemented in Rust. #[pymodule] -fn keyring_test(_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)?)?; +fn zowe_secrets_for_zowe_sdk(py: Python, module: &PyModule) -> PyResult<()> { + let submodule = PyModule::new(py, "zowe.secrets_for_zowe_sdk.keyring")?; + submodule.add_function(wrap_pyfunction!(get_password, submodule)?)?; + submodule.add_function(wrap_pyfunction!(set_password, submodule)?)?; + submodule.add_function(wrap_pyfunction!(delete_password, submodule)?)?; + submodule.add_function(wrap_pyfunction!(find_password, submodule)?)?; + submodule.add_function(wrap_pyfunction!(find_credentials, submodule)?)?; + // Hack from https://github.com/PyO3/pyo3/issues/1517 + py_run!( + py, + submodule, + "import sys; sys.modules['zowe.secrets_for_zowe_sdk.keyring'] = submodule" + ); + module.add_submodule(submodule)?; Ok(()) } From 7f588e983752cb2c57f7999c1e7c7a9ca4e93122 Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Thu, 2 Nov 2023 15:06:27 -0400 Subject: [PATCH 29/45] List interpreters for windows and macos Signed-off-by: Timothy Johnson --- .github/workflows/secrets-sdk.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/secrets-sdk.yml b/.github/workflows/secrets-sdk.yml index 0ba04740..9bcc7b7f 100644 --- a/.github/workflows/secrets-sdk.yml +++ b/.github/workflows/secrets-sdk.yml @@ -73,7 +73,7 @@ jobs: uses: PyO3/maturin-action@v1 with: target: ${{ matrix.target }} - args: --release --out dist --interpreter python3.{7..12} + args: --release --out dist --interpreter python3.7 python3.8 python3.9 python3.10 python3.11 python3.12 sccache: "true" working-directory: src/secrets/zowe/secrets_for_zowe_sdk - name: Upload wheels @@ -97,7 +97,7 @@ jobs: uses: PyO3/maturin-action@v1 with: target: ${{ matrix.target }} - args: --release --out dist --interpreter python3.{7..12} + args: --release --out dist --interpreter python3.7 python3.8 python3.9 python3.10 python3.11 python3.12 sccache: "true" working-directory: src/secrets/zowe/secrets_for_zowe_sdk - name: Upload wheels From 7524876b47b584abbc39f2dbed1f188c6a29223b Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Thu, 2 Nov 2023 15:19:07 -0400 Subject: [PATCH 30/45] Add windows arm64 build Signed-off-by: Timothy Johnson --- .github/workflows/secrets-sdk.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/secrets-sdk.yml b/.github/workflows/secrets-sdk.yml index 9bcc7b7f..bff66e02 100644 --- a/.github/workflows/secrets-sdk.yml +++ b/.github/workflows/secrets-sdk.yml @@ -61,7 +61,7 @@ jobs: runs-on: windows-latest strategy: matrix: - target: [x64, x86] + target: [x64, x86, aarch64] fail-fast: false steps: - uses: actions/checkout@v3 From 3b8e6c525aacb93696e4e06efad3b8e3945a79f8 Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Thu, 2 Nov 2023 18:57:47 -0400 Subject: [PATCH 31/45] Try abi3-py37 feature to fix windows arm64 build Signed-off-by: Timothy Johnson --- .github/workflows/secrets-sdk.yml | 9 ++--- .vscode/settings.json | 2 +- .../zowe/secrets_for_zowe_sdk/Cargo.lock | 34 +++++++++---------- .../zowe/secrets_for_zowe_sdk/Cargo.toml | 2 +- 4 files changed, 24 insertions(+), 23 deletions(-) diff --git a/.github/workflows/secrets-sdk.yml b/.github/workflows/secrets-sdk.yml index bff66e02..20734df9 100644 --- a/.github/workflows/secrets-sdk.yml +++ b/.github/workflows/secrets-sdk.yml @@ -36,7 +36,7 @@ jobs: uses: PyO3/maturin-action@v1 with: target: ${{ matrix.target }} - args: --release --out dist --interpreter python3.{7..12} --skip-auditwheel + args: --release --out dist --skip-auditwheel sccache: "true" manylinux: auto docker-options: -e PKG_CONFIG_SYSROOT_DIR -e PKG_CONFIG_PATH @@ -66,14 +66,15 @@ jobs: steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 + if: ${{ matrix.target != 'aarch64' }} with: - python-version: "3.10" + python-version: "3.7" architecture: ${{ matrix.target }} - name: Build wheels uses: PyO3/maturin-action@v1 with: target: ${{ matrix.target }} - args: --release --out dist --interpreter python3.7 python3.8 python3.9 python3.10 python3.11 python3.12 + args: --release --out dist sccache: "true" working-directory: src/secrets/zowe/secrets_for_zowe_sdk - name: Upload wheels @@ -97,7 +98,7 @@ jobs: uses: PyO3/maturin-action@v1 with: target: ${{ matrix.target }} - args: --release --out dist --interpreter python3.7 python3.8 python3.9 python3.10 python3.11 python3.12 + args: --release --out dist sccache: "true" working-directory: src/secrets/zowe/secrets_for_zowe_sdk - name: Upload wheels diff --git a/.vscode/settings.json b/.vscode/settings.json index 414fbd92..f2691f88 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,7 +1,7 @@ { "editor.formatOnSave": true, "python.formatting.provider": "black", - "rust-analyzer.linkedProjects": ["./src/secrets/src/keyring/Cargo.toml"], + "rust-analyzer.linkedProjects": ["./src/secrets/zowe/secrets_for_zowe_sdk/Cargo.toml"], "[python]": { "editor.codeActionsOnSave": { "source.organizeImports": true diff --git a/src/secrets/zowe/secrets_for_zowe_sdk/Cargo.lock b/src/secrets/zowe/secrets_for_zowe_sdk/Cargo.lock index 5bfb7e31..2f97c8e9 100644 --- a/src/secrets/zowe/secrets_for_zowe_sdk/Cargo.lock +++ b/src/secrets/zowe/secrets_for_zowe_sdk/Cargo.lock @@ -235,9 +235,9 @@ dependencies = [ [[package]] name = "indoc" -version = "1.0.9" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa799dd5ed20a7e349f3b4639aa80d74549c81716d9ec4f994c9b5815598306" +checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8" [[package]] name = "keyring" @@ -396,9 +396,9 @@ dependencies = [ [[package]] name = "pyo3" -version = "0.19.2" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e681a6cfdc4adcc93b4d3cf993749a4552018ee0a9b65fc0ccfad74352c72a38" +checksum = "04e8453b658fe480c3e70c8ed4e3d3ec33eb74988bd186561b0cc66b85c3bc4b" dependencies = [ "cfg-if", "indoc", @@ -413,9 +413,9 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.19.2" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "076c73d0bc438f7a4ef6fdd0c3bb4732149136abd952b110ac93e4edb13a6ba5" +checksum = "a96fe70b176a89cff78f2fa7b3c930081e163d5379b4dcdf993e3ae29ca662e5" dependencies = [ "once_cell", "target-lexicon", @@ -423,9 +423,9 @@ dependencies = [ [[package]] name = "pyo3-ffi" -version = "0.19.2" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e53cee42e77ebe256066ba8aa77eff722b3bb91f3419177cf4cd0f304d3284d9" +checksum = "214929900fd25e6604661ed9cf349727c8920d47deff196c4e28165a6ef2a96b" dependencies = [ "libc", "pyo3-build-config", @@ -433,25 +433,26 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.19.2" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfeb4c99597e136528c6dd7d5e3de5434d1ceaf487436a3f03b2d56b6fc9efd1" +checksum = "dac53072f717aa1bfa4db832b39de8c875b7c7af4f4a6fe93cdbf9264cf8383b" dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", - "syn 1.0.109", + "syn 2.0.38", ] [[package]] name = "pyo3-macros-backend" -version = "0.19.2" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "947dc12175c254889edc0c02e399476c2f652b4b9ebd123aa655c224de259536" +checksum = "7774b5a8282bd4f25f803b1f0d945120be959a36c72e08e7cd031c792fdfd424" dependencies = [ + "heck", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.38", ] [[package]] @@ -546,7 +547,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", - "quote", "unicode-ident", ] @@ -653,9 +653,9 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unindent" -version = "0.1.11" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1766d682d402817b5ac4490b3c3002d91dfa0d22812f341609f97b08757359c" +checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" [[package]] name = "version-compare" diff --git a/src/secrets/zowe/secrets_for_zowe_sdk/Cargo.toml b/src/secrets/zowe/secrets_for_zowe_sdk/Cargo.toml index e150bd4a..068d339f 100644 --- a/src/secrets/zowe/secrets_for_zowe_sdk/Cargo.toml +++ b/src/secrets/zowe/secrets_for_zowe_sdk/Cargo.toml @@ -9,5 +9,5 @@ name = "keyring" crate-type = ["cdylib"] [dependencies] -pyo3 = "0.19.0" +pyo3 = { version = "0.20.0", features = ["abi3-py37"] } secrets_core = { git = "https://github.com/zowe/zowe-cli.git", branch = "feat/secrets/core-crate" } From 5eca5709449a3f243e465cc53480c4e4c05f7598 Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Fri, 3 Nov 2023 11:22:02 -0400 Subject: [PATCH 32/45] Restructure secrets sdk as namespace package Signed-off-by: Timothy Johnson --- .github/workflows/secrets-sdk.yml | 18 +++++++-------- .vscode/settings.json | 2 +- docs/requirements.txt | 1 + requirements.txt | 1 + .../secrets_for_zowe_sdk => }/.gitignore | 0 .../secrets_for_zowe_sdk => }/Cargo.lock | 0 .../secrets_for_zowe_sdk => }/Cargo.toml | 0 .../secrets_for_zowe_sdk => }/pyproject.toml | 3 ++- .../secrets_for_zowe_sdk => }/src/lib.rs | 22 ++++++------------- .../zowe/secrets_for_zowe_sdk/__init__.py | 5 +++++ 10 files changed, 26 insertions(+), 26 deletions(-) rename src/secrets/{zowe/secrets_for_zowe_sdk => }/.gitignore (100%) rename src/secrets/{zowe/secrets_for_zowe_sdk => }/Cargo.lock (100%) rename src/secrets/{zowe/secrets_for_zowe_sdk => }/Cargo.toml (100%) rename src/secrets/{zowe/secrets_for_zowe_sdk => }/pyproject.toml (78%) rename src/secrets/{zowe/secrets_for_zowe_sdk => }/src/lib.rs (65%) create mode 100644 src/secrets/zowe/secrets_for_zowe_sdk/__init__.py diff --git a/.github/workflows/secrets-sdk.yml b/.github/workflows/secrets-sdk.yml index 20734df9..1f3e8e7f 100644 --- a/.github/workflows/secrets-sdk.yml +++ b/.github/workflows/secrets-sdk.yml @@ -40,7 +40,7 @@ jobs: sccache: "true" manylinux: auto docker-options: -e PKG_CONFIG_SYSROOT_DIR -e PKG_CONFIG_PATH - working-directory: src/secrets/zowe/secrets_for_zowe_sdk + 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 @@ -55,7 +55,7 @@ jobs: uses: actions/upload-artifact@v3 with: name: wheels - path: src/secrets/zowe/secrets_for_zowe_sdk/dist + path: src/secrets/dist windows: runs-on: windows-latest @@ -76,12 +76,12 @@ jobs: target: ${{ matrix.target }} args: --release --out dist sccache: "true" - working-directory: src/secrets/zowe/secrets_for_zowe_sdk + working-directory: src/secrets - name: Upload wheels uses: actions/upload-artifact@v3 with: name: wheels - path: src/secrets/zowe/secrets_for_zowe_sdk/dist + path: src/secrets/dist macos: runs-on: macos-latest @@ -100,12 +100,12 @@ jobs: target: ${{ matrix.target }} args: --release --out dist sccache: "true" - working-directory: src/secrets/zowe/secrets_for_zowe_sdk + working-directory: src/secrets - name: Upload wheels uses: actions/upload-artifact@v3 with: name: wheels - path: src/secrets/zowe/secrets_for_zowe_sdk/dist + path: src/secrets/dist sdist: runs-on: ubuntu-latest @@ -116,12 +116,12 @@ jobs: with: command: sdist args: --out dist - working-directory: src/secrets/zowe/secrets_for_zowe_sdk + working-directory: src/secrets - name: Upload sdist uses: actions/upload-artifact@v3 with: name: wheels - path: src/secrets/zowe/secrets_for_zowe_sdk/dist + path: src/secrets/dist # release: # name: Release @@ -139,4 +139,4 @@ jobs: # with: # command: upload # args: --non-interactive --skip-existing * - # working-directory: src/secrets/zowe/secrets_for_zowe_sdk + # working-directory: src/secrets diff --git a/.vscode/settings.json b/.vscode/settings.json index f2691f88..1233617b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,7 +1,7 @@ { "editor.formatOnSave": true, "python.formatting.provider": "black", - "rust-analyzer.linkedProjects": ["./src/secrets/zowe/secrets_for_zowe_sdk/Cargo.toml"], + "rust-analyzer.linkedProjects": ["./src/secrets/Cargo.toml"], "[python]": { "editor.codeActionsOnSave": { "source.organizeImports": true diff --git a/docs/requirements.txt b/docs/requirements.txt index 96f30abe..af02bfd9 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,6 +1,7 @@ sphinx_rtd_theme>=0.5.1 sphinxcontrib-spelling==5.4.0 -e ./src/core +-e ./src/secrets -e ./src/zos_console -e ./src/zos_files -e ./src/zos_jobs diff --git a/requirements.txt b/requirements.txt index 050d602a..7c640110 100644 --- a/requirements.txt +++ b/requirements.txt @@ -32,6 +32,7 @@ urllib3==1.26.18 wheel zipp==3.4.0 -e ./src/core +-e ./src/secrets -e ./src/zos_console -e ./src/zos_files -e ./src/zos_jobs diff --git a/src/secrets/zowe/secrets_for_zowe_sdk/.gitignore b/src/secrets/.gitignore similarity index 100% rename from src/secrets/zowe/secrets_for_zowe_sdk/.gitignore rename to src/secrets/.gitignore diff --git a/src/secrets/zowe/secrets_for_zowe_sdk/Cargo.lock b/src/secrets/Cargo.lock similarity index 100% rename from src/secrets/zowe/secrets_for_zowe_sdk/Cargo.lock rename to src/secrets/Cargo.lock diff --git a/src/secrets/zowe/secrets_for_zowe_sdk/Cargo.toml b/src/secrets/Cargo.toml similarity index 100% rename from src/secrets/zowe/secrets_for_zowe_sdk/Cargo.toml rename to src/secrets/Cargo.toml diff --git a/src/secrets/zowe/secrets_for_zowe_sdk/pyproject.toml b/src/secrets/pyproject.toml similarity index 78% rename from src/secrets/zowe/secrets_for_zowe_sdk/pyproject.toml rename to src/secrets/pyproject.toml index e78b6e8f..c18270f5 100644 --- a/src/secrets/zowe/secrets_for_zowe_sdk/pyproject.toml +++ b/src/secrets/pyproject.toml @@ -3,7 +3,7 @@ requires = ["maturin>=1.3,<2.0"] build-backend = "maturin" [project] -name = "zowe.secrets_for_zowe_sdk" +name = "zowe_secrets_for_zowe_sdk" requires-python = ">=3.7" classifiers = [ "Programming Language :: Rust", @@ -13,3 +13,4 @@ dynamic = ["version"] [tool.maturin] features = ["pyo3/extension-module"] +module-name = "zowe.secrets_for_zowe_sdk.keyring" diff --git a/src/secrets/zowe/secrets_for_zowe_sdk/src/lib.rs b/src/secrets/src/lib.rs similarity index 65% rename from src/secrets/zowe/secrets_for_zowe_sdk/src/lib.rs rename to src/secrets/src/lib.rs index 88205fe4..92e2277b 100644 --- a/src/secrets/zowe/secrets_for_zowe_sdk/src/lib.rs +++ b/src/secrets/src/lib.rs @@ -1,5 +1,5 @@ use pyo3::exceptions::PyValueError; -use pyo3::{prelude::*, py_run}; +use pyo3::prelude::*; extern crate secrets_core; use secrets_core::*; @@ -50,19 +50,11 @@ fn find_credentials(service: String) -> PyResult> { /// A Python module implemented in Rust. #[pymodule] -fn zowe_secrets_for_zowe_sdk(py: Python, module: &PyModule) -> PyResult<()> { - let submodule = PyModule::new(py, "zowe.secrets_for_zowe_sdk.keyring")?; - submodule.add_function(wrap_pyfunction!(get_password, submodule)?)?; - submodule.add_function(wrap_pyfunction!(set_password, submodule)?)?; - submodule.add_function(wrap_pyfunction!(delete_password, submodule)?)?; - submodule.add_function(wrap_pyfunction!(find_password, submodule)?)?; - submodule.add_function(wrap_pyfunction!(find_credentials, submodule)?)?; - // Hack from https://github.com/PyO3/pyo3/issues/1517 - py_run!( - py, - submodule, - "import sys; sys.modules['zowe.secrets_for_zowe_sdk.keyring'] = submodule" - ); - module.add_submodule(submodule)?; +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 From a6e58c9907830091d622b90b3220f8481c3983bc Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Fri, 3 Nov 2023 11:34:04 -0400 Subject: [PATCH 33/45] Replace keyring package with secrets sdk Signed-off-by: Timothy Johnson --- .github/workflows/sdk-build.yml | 69 ++++++++++--------- .github/workflows/secrets-sdk.yml | 4 +- requirements.txt | 1 - src/core/setup.py | 11 ++- .../core_for_zowe_sdk/credential_manager.py | 22 +----- .../zowe/core_for_zowe_sdk/zosmf_profile.py | 43 +----------- tests/unit/test_zowe_core.py | 38 +++++----- 7 files changed, 70 insertions(+), 118 deletions(-) diff --git a/.github/workflows/sdk-build.yml b/.github/workflows/sdk-build.yml index 8077ae66..8563510b 100644 --- a/.github/workflows/sdk-build.yml +++ b/.github/workflows/sdk-build.yml @@ -13,41 +13,44 @@ 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"] 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: Build keyring binary + run: maturin develop + working-directory: src/secrets + - 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 diff --git a/.github/workflows/secrets-sdk.yml b/.github/workflows/secrets-sdk.yml index 1f3e8e7f..904eb0dd 100644 --- a/.github/workflows/secrets-sdk.yml +++ b/.github/workflows/secrets-sdk.yml @@ -3,7 +3,7 @@ # # maturin generate-ci github # -name: CI +name: Secrets SDK CI on: push: @@ -68,7 +68,7 @@ jobs: - uses: actions/setup-python@v4 if: ${{ matrix.target != 'aarch64' }} with: - python-version: "3.7" + python-version: "3.10" architecture: ${{ matrix.target }} - name: Build wheels uses: PyO3/maturin-action@v1 diff --git a/requirements.txt b/requirements.txt index 7c640110..8794f15f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,6 @@ idna==2.10 importlib-metadata==3.6.0 isort jsonschema==4.17.3 -keyring lxml==4.9.3 maturin mccabe==0.6.1 diff --git a/src/core/setup.py b/src/core/setup.py index aed4a73a..303c18ae 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,12 @@ "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", + "pyyaml", + "requests", + "urllib3", + resolve_sdk_dep("secrets", "~=0.1.0"), + ], 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..b4f5133a 100644 --- a/src/core/zowe/core_for_zowe_sdk/credential_manager.py +++ b/src/core/zowe/core_for_zowe_sdk/credential_manager.py @@ -21,8 +21,7 @@ HAS_KEYRING = True try: - import keyring - + from zowe.secrets_for_zowe_sdk import keyring except ImportError: HAS_KEYRING = False @@ -76,11 +75,8 @@ def _retrieve_credential(service_name: str) -> Optional[str]: """ # 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: + 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}") @@ -92,16 +88,6 @@ def _retrieve_credential(service_name: str) -> Optional[str]: 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 - - if encoded_credential is not None and encoded_credential.endswith("\0"): - encoded_credential = encoded_credential[:-1] - return encoded_credential @staticmethod @@ -156,7 +142,6 @@ def save_secure_props() -> None: 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 @@ -167,9 +152,8 @@ def save_secure_props() -> None: 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) + keyring.set_password(f"{service_name}-{index}", field_name, chunk) else: # Credential length is within the maximum limit or not on win32, set it as a single keyring entry 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/tests/unit/test_zowe_core.py b/tests/unit/test_zowe_core.py index 5763cf51..32db351a 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 @@ -439,7 +439,7 @@ def test_load_secure_props(self, retrieve_cred_func): 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. @@ -468,7 +468,7 @@ def side_effect(*args, **kwargs): 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. @@ -493,7 +493,7 @@ def test_retrieve_credential(self, get_pass_func): get_pass_func.assert_any_call(f"{service_name}-2", 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. @@ -504,7 +504,7 @@ def test_retrieve_credential_encoding_errors(self, get_pass_func): get_pass_func.assert_called_with(f"{service_name}-1", f"{constants['ZoweAccountName']}-1") @mock.patch("sys.platform", "win32") - @mock.patch("keyring.set_password") + @mock.patch("zowe.secrets_for_zowe_sdk.keyring.set_password") @mock.patch("zowe.core_for_zowe_sdk.CredentialManager._retrieve_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): @@ -538,7 +538,7 @@ def test_save_secure_props_normal_credential(self, delete_pass_func, retrieve_cr @mock.patch("sys.platform", "win32") @mock.patch("zowe.core_for_zowe_sdk.CredentialManager._retrieve_credential") - @mock.patch("keyring.set_password") + @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 @@ -578,7 +578,7 @@ def test_save_secure_props_exceed_limit(self, delete_pass_func, set_pass_func, r expected_calls.append(mock.call(service_names, field_name, password)) 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 +602,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 +627,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 +652,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 +786,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 +826,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. From 7bff9bb1840a2757f069714b9eb8bdc357f975bd Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Fri, 3 Nov 2023 11:40:05 -0400 Subject: [PATCH 34/45] Try to fix maturin and add py3.12 Signed-off-by: Timothy Johnson --- .github/workflows/sdk-build.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/sdk-build.yml b/.github/workflows/sdk-build.yml index 8563510b..6e600369 100644 --- a/.github/workflows/sdk-build.yml +++ b/.github/workflows/sdk-build.yml @@ -13,7 +13,7 @@ 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 }} @@ -29,7 +29,7 @@ jobs: python -m pip install --upgrade pip pip install -r requirements.txt - name: Build keyring binary - run: maturin develop + run: maturin build working-directory: src/secrets - name: Lint with flake8 run: | @@ -45,7 +45,7 @@ jobs: coverage xml - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 - if: ${{ matrix.python-version == '3.11' }} + if: ${{ matrix.python-version == '3.12' }} with: directory: ./ env_vars: OS,PYTHON From 3e798cd341ce04fded490b71c1becfd4ac0d3525 Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Fri, 3 Nov 2023 12:07:37 -0400 Subject: [PATCH 35/45] Fix unit tests for credential manager Signed-off-by: Timothy Johnson --- requirements.txt | 2 +- .../core_for_zowe_sdk/credential_manager.py | 15 +++------ tests/unit/test_zowe_core.py | 31 +++++++------------ 3 files changed, 17 insertions(+), 31 deletions(-) diff --git a/requirements.txt b/requirements.txt index 8794f15f..936111a9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ coverage==5.4 deepmerge==1.1.0 flake8==3.8.4 idna==2.10 -importlib-metadata==3.6.0 +importlib-metadata==6.5.0 isort jsonschema==4.17.3 lxml==4.9.3 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 b4f5133a..2665251c 100644 --- a/src/core/zowe/core_for_zowe_sdk/credential_manager.py +++ b/src/core/zowe/core_for_zowe_sdk/credential_manager.py @@ -79,14 +79,14 @@ def _retrieve_credential(service_name: str) -> Optional[str]: 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"{constants['ZoweAccountName']}-{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}") + temp_value = keyring.get_password(service_name, f"{constants['ZoweAccountName']}-{index}") return encoded_credential @@ -106,10 +106,7 @@ 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": @@ -117,9 +114,7 @@ def delete_credential(service_name: str, account_name: str) -> None: 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 @@ -153,7 +148,7 @@ def save_secure_props() -> None: # Set the individual chunks as separate keyring entries for index, chunk in enumerate(chunks, start=1): field_name = f"{constants['ZoweAccountName']}-{index}" - keyring.set_password(f"{service_name}-{index}", field_name, chunk) + 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 diff --git a/tests/unit/test_zowe_core.py b/tests/unit/test_zowe_core.py index 32db351a..2e504c8b 100644 --- a/tests/unit/test_zowe_core.py +++ b/tests/unit/test_zowe_core.py @@ -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 @@ -474,23 +474,21 @@ 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] + expected_password1 = "password" retrieve_credential1 = credential_manager._retrieve_credential(constants["ZoweServiceName"]) 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() + expected_password2 = "part1part2" retrieve_credential2 = credential_manager._retrieve_credential(constants["ZoweServiceName"]) retrieve_credential2 = retrieve_credential2[:-1] 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("zowe.secrets_for_zowe_sdk.keyring.get_password", side_effect=[None, None]) @@ -498,10 +496,9 @@ 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"]) 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("zowe.secrets_for_zowe_sdk.keyring.set_password") @@ -512,8 +509,6 @@ def test_save_secure_props_normal_credential(self, delete_pass_func, retrieve_cr 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 +529,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("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") @@ -573,9 +566,7 @@ def test_save_secure_props_exceed_limit(self, delete_pass_func, set_pass_func, r ] 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("zowe.secrets_for_zowe_sdk.keyring.get_password", side_effect=keyring_get_password) From bab3ed5d5d06638c245e14ada59d02014397ea99 Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Fri, 3 Nov 2023 12:19:02 -0400 Subject: [PATCH 36/45] Update flake8 version and change workflow triggers Signed-off-by: Timothy Johnson --- .github/workflows/secrets-sdk.yml | 14 ++++++++------ requirements.txt | 10 +++++----- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/.github/workflows/secrets-sdk.yml b/.github/workflows/secrets-sdk.yml index 904eb0dd..0e5900e7 100644 --- a/.github/workflows/secrets-sdk.yml +++ b/.github/workflows/secrets-sdk.yml @@ -7,13 +7,15 @@ name: Secrets SDK CI on: push: - branches: - - main - - master - tags: - - "*" + paths: + - "src/secrets/**" + - ".github/workflows/secrets-sdk.yml" pull_request: - workflow_dispatch: + paths: + - "src/secrets/**" +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true permissions: contents: read diff --git a/requirements.txt b/requirements.txt index 936111a9..f4d36111 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==6.5.0 +importlib-metadata==3.6.0;python_version<"3.8" isort jsonschema==4.17.3 lxml==4.9.3 maturin -mccabe==0.6.1 +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 From 71cbbce9f4edff2ba36067ea316d81ea19a0b40d Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Fri, 3 Nov 2023 12:46:29 -0400 Subject: [PATCH 37/45] Add Rust to ReadTheDocs config Signed-off-by: Timothy Johnson --- .readthedocs.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.readthedocs.yml b/.readthedocs.yml index 3bd5116b..ca746b6f 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -9,6 +9,7 @@ build: os: ubuntu-22.04 tools: python: "3" + rust: "1.70" # Build documentation in the docs/ directory with Sphinx sphinx: From 3114108c7a1604ab4ec507d48f6b09ff0a54154d Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Fri, 3 Nov 2023 13:09:04 -0400 Subject: [PATCH 38/45] Remove secrets sdk from doc requirements Signed-off-by: Timothy Johnson --- .github/workflows/sdk-build.yml | 3 --- .readthedocs.yml | 1 - docs/requirements.txt | 3 +-- requirements.txt | 2 +- src/core/setup.py | 6 +++++- 5 files changed, 7 insertions(+), 8 deletions(-) diff --git a/.github/workflows/sdk-build.yml b/.github/workflows/sdk-build.yml index 6e600369..b90cf85f 100644 --- a/.github/workflows/sdk-build.yml +++ b/.github/workflows/sdk-build.yml @@ -28,9 +28,6 @@ jobs: run: | python -m pip install --upgrade pip pip install -r requirements.txt - - name: Build keyring binary - run: maturin build - working-directory: src/secrets - name: Lint with flake8 run: | # stop the build if there are Python syntax errors or undefined names diff --git a/.readthedocs.yml b/.readthedocs.yml index ca746b6f..3bd5116b 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -9,7 +9,6 @@ build: os: ubuntu-22.04 tools: python: "3" - rust: "1.70" # Build documentation in the docs/ directory with Sphinx sphinx: diff --git a/docs/requirements.txt b/docs/requirements.txt index af02bfd9..5601d12c 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,7 +1,6 @@ sphinx_rtd_theme>=0.5.1 -sphinxcontrib-spelling==5.4.0 +sphinxcontrib-spelling==8.0.0 -e ./src/core --e ./src/secrets -e ./src/zos_console -e ./src/zos_files -e ./src/zos_jobs diff --git a/requirements.txt b/requirements.txt index f4d36111..a5a442e4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -30,7 +30,7 @@ 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 diff --git a/src/core/setup.py b/src/core/setup.py index 303c18ae..2711489d 100644 --- a/src/core/setup.py +++ b/src/core/setup.py @@ -21,10 +21,14 @@ ], install_requires=[ "commentjson", + "deepmerge", + "jsonschema", "pyyaml", "requests", "urllib3", - resolve_sdk_dep("secrets", "~=0.1.0"), ], + extras_require={ + "secrets": [resolve_sdk_dep("secrets", "~=0.1.0")] + }, packages=find_namespace_packages(include=["zowe.*"]), ) From 70d62eea0cc20fde0a77d7b4a2c219e39a78d616 Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Fri, 3 Nov 2023 14:11:47 -0400 Subject: [PATCH 39/45] Remove logging call in credential manager Signed-off-by: Timothy Johnson --- src/core/zowe/core_for_zowe_sdk/credential_manager.py | 3 --- 1 file changed, 3 deletions(-) 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 2665251c..328df49c 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 @@ -73,8 +72,6 @@ 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) encoded_credential = keyring.get_password(service_name, constants["ZoweAccountName"]) if encoded_credential is None and sys.platform == "win32": # Retrieve the secure value with an index From 82eaf4a75113f8074f52cf633ba62a682a7fc878 Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Mon, 6 Nov 2023 09:50:06 -0500 Subject: [PATCH 40/45] Enable release workflows to publish prerelease to PyPI Signed-off-by: Timothy Johnson --- .github/workflows/dev-release.yaml | 67 +++++++++++++++++++++++++ .github/workflows/dev-release.yaml.old | 69 -------------------------- .github/workflows/secrets-sdk.yml | 34 ++++++------- src/secrets/Cargo.lock | 2 +- src/secrets/Cargo.toml | 2 +- 5 files changed, 86 insertions(+), 88 deletions(-) create mode 100644 .github/workflows/dev-release.yaml delete mode 100644 .github/workflows/dev-release.yaml.old 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/secrets-sdk.yml b/.github/workflows/secrets-sdk.yml index 0e5900e7..8db3c6ac 100644 --- a/.github/workflows/secrets-sdk.yml +++ b/.github/workflows/secrets-sdk.yml @@ -125,20 +125,20 @@ jobs: 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_API_TOKEN }} - # with: - # command: upload - # args: --non-interactive --skip-existing * - # working-directory: src/secrets + 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/src/secrets/Cargo.lock b/src/secrets/Cargo.lock index 2f97c8e9..04130c4b 100644 --- a/src/secrets/Cargo.lock +++ b/src/secrets/Cargo.lock @@ -241,7 +241,7 @@ checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8" [[package]] name = "keyring" -version = "0.1.0" +version = "1.0.0-dev10" dependencies = [ "pyo3", "secrets_core", diff --git a/src/secrets/Cargo.toml b/src/secrets/Cargo.toml index 068d339f..1552d198 100644 --- a/src/secrets/Cargo.toml +++ b/src/secrets/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "keyring" -version = "0.1.0" +version = "1.0.0-dev10" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From 9f676db74a268c96d48bc7b8cc87cf66c19bbf71 Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Mon, 6 Nov 2023 09:53:09 -0500 Subject: [PATCH 41/45] Fix secrets sdk version constraint Signed-off-by: Timothy Johnson --- src/core/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/setup.py b/src/core/setup.py index 2711489d..5919f70f 100644 --- a/src/core/setup.py +++ b/src/core/setup.py @@ -28,7 +28,7 @@ "urllib3", ], extras_require={ - "secrets": [resolve_sdk_dep("secrets", "~=0.1.0")] + "secrets": [resolve_sdk_dep("secrets", "~=" + __version__)] }, packages=find_namespace_packages(include=["zowe.*"]), ) From fa37fceb9d5ac3411b625f7f36e293a961f4fa32 Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Mon, 6 Nov 2023 15:43:50 -0500 Subject: [PATCH 42/45] Add package metadata and update changelog Signed-off-by: Timothy Johnson --- CHANGELOG.md | 23 ++++++++++++++--------- src/secrets/pyproject.toml | 2 ++ 2 files changed, 16 insertions(+), 9 deletions(-) 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/src/secrets/pyproject.toml b/src/secrets/pyproject.toml index c18270f5..131125da 100644 --- a/src/secrets/pyproject.toml +++ b/src/secrets/pyproject.toml @@ -4,6 +4,8 @@ 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", From f2f361a34137ca4214e02b0898a87dbd99bd2304 Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Mon, 6 Nov 2023 16:42:56 -0500 Subject: [PATCH 43/45] Fix Python binding and split out _set_credential method Signed-off-by: Timothy Johnson --- .../core_for_zowe_sdk/credential_manager.py | 86 +++++++++---------- src/secrets/src/lib.rs | 11 +-- tests/unit/test_zowe_core.py | 33 ++++--- 3 files changed, 63 insertions(+), 67 deletions(-) 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 328df49c..383c6884 100644 --- a/src/core/zowe/core_for_zowe_sdk/credential_manager.py +++ b/src/core/zowe/core_for_zowe_sdk/credential_manager.py @@ -43,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 @@ -59,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. @@ -72,23 +91,40 @@ def _retrieve_credential(service_name: str) -> Optional[str]: str The retrieved encoded credential """ - encoded_credential = keyring.get_password(service_name, constants["ZoweAccountName"]) + 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(service_name, 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(service_name, f"{constants['ZoweAccountName']}-{index}") + temp_value = keyring.get_password(service_name, f"{account_name}-{index}") 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. @@ -114,39 +150,3 @@ def delete_credential(service_name: str, account_name: str) -> None: 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: - # 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): - field_name = f"{constants['ZoweAccountName']}-{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, constants["ZoweAccountName"], encoded_credential) diff --git a/src/secrets/src/lib.rs b/src/secrets/src/lib.rs index 92e2277b..aa012451 100644 --- a/src/secrets/src/lib.rs +++ b/src/secrets/src/lib.rs @@ -13,9 +13,9 @@ fn set_password(service: String, account: String, password: String) -> PyResult< } #[pyfunction] -fn get_password(service: String, account: String) -> PyResult { +fn get_password(service: String, account: String) -> PyResult> { match os::get_password(&service, &account) { - Ok(pw) => Ok(pw.unwrap_or("".to_string())), + Ok(pw) => Ok(pw), Err(e) => Err(PyValueError::new_err(format!("{:#?}", e))), } } @@ -29,12 +29,9 @@ fn delete_password(service: String, account: String) -> PyResult { } #[pyfunction] -fn find_password(service: String) -> PyResult { +fn find_password(service: String) -> PyResult> { match os::find_password(&service) { - Ok(res) => match res { - Some(val) => Ok(val), - _ => Ok("".to_owned()), - }, + Ok(res) => Ok(res), Err(e) => Err(PyValueError::new_err(format!("{:#?}", e))), } } diff --git a/tests/unit/test_zowe_core.py b/tests/unit/test_zowe_core.py index 2e504c8b..b1ae03d2 100644 --- a/tests/unit/test_zowe_core.py +++ b/tests/unit/test_zowe_core.py @@ -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,14 +427,13 @@ 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) @@ -460,7 +460,7 @@ 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"), @@ -477,13 +477,13 @@ def test_retrieve_credential(self, get_pass_func): # Scenario 1: Retrieve password directly expected_password1 = "password" - retrieve_credential1 = credential_manager._retrieve_credential(constants["ZoweServiceName"]) + retrieve_credential1 = credential_manager._get_credential(constants["ZoweServiceName"], constants["ZoweAccountName"]) self.assertEqual(retrieve_credential1, expected_password1) get_pass_func.assert_called_with(constants["ZoweServiceName"], constants["ZoweAccountName"]) # Scenario 2: Retrieve password in parts expected_password2 = "part1part2" - retrieve_credential2 = credential_manager._retrieve_credential(constants["ZoweServiceName"]) + retrieve_credential2 = credential_manager._get_credential(constants["ZoweServiceName"], constants["ZoweAccountName"]) retrieve_credential2 = retrieve_credential2[:-1] self.assertEqual(retrieve_credential2, expected_password2) get_pass_func.assert_any_call(constants["ZoweServiceName"], constants["ZoweAccountName"]) @@ -496,14 +496,14 @@ def test_retrieve_credential_encoding_errors(self, get_pass_func): """ Test the _retrieve_credential method for handling encoding errors and None values. """ - result = CredentialManager._retrieve_credential(constants["ZoweServiceName"]) + result = CredentialManager._get_credential(constants["ZoweServiceName"], constants["ZoweAccountName"]) self.assertIsNone(result) get_pass_func.assert_called_with(constants["ZoweServiceName"], f"{constants['ZoweAccountName']}-1") @mock.patch("sys.platform", "win32") - @mock.patch("zowe.secrets_for_zowe_sdk.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. @@ -532,9 +532,9 @@ def test_save_secure_props_normal_credential(self, delete_pass_func, retrieve_cr 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("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") + @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): # Setup - copy profile to fake filesystem created by pyfakefs cwd_up_dir_path = os.path.dirname(CWD) @@ -548,9 +548,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 @@ -562,7 +561,7 @@ 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}" From 917ebd745dd17d85e7beb2d11a6bfda1e924b358 Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Tue, 7 Nov 2023 12:01:48 -0500 Subject: [PATCH 44/45] Fix nul byte not trimmed from win32 creds Signed-off-by: Timothy Johnson --- src/core/zowe/core_for_zowe_sdk/credential_manager.py | 4 +++- tests/unit/test_zowe_core.py | 3 +-- 2 files changed, 4 insertions(+), 3 deletions(-) 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 383c6884..3459619c 100644 --- a/src/core/zowe/core_for_zowe_sdk/credential_manager.py +++ b/src/core/zowe/core_for_zowe_sdk/credential_manager.py @@ -104,6 +104,9 @@ def _get_credential(service_name: str, account_name: str) -> Optional[str]: index += 1 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] + return encoded_credential @staticmethod @@ -146,7 +149,6 @@ def _delete_credential(service_name: str, account_name: str) -> None: index = 1 while True: field_name = f"{account_name}-{index}" - service_name = f"{service_name}-{index}" if not keyring.delete_password(service_name, field_name): break index += 1 diff --git a/tests/unit/test_zowe_core.py b/tests/unit/test_zowe_core.py index b1ae03d2..202fab0e 100644 --- a/tests/unit/test_zowe_core.py +++ b/tests/unit/test_zowe_core.py @@ -463,7 +463,7 @@ def side_effect(*args, **kwargs): 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) @@ -484,7 +484,6 @@ def test_retrieve_credential(self, get_pass_func): # Scenario 2: Retrieve password in parts expected_password2 = "part1part2" retrieve_credential2 = credential_manager._get_credential(constants["ZoweServiceName"], constants["ZoweAccountName"]) - retrieve_credential2 = retrieve_credential2[:-1] self.assertEqual(retrieve_credential2, expected_password2) get_pass_func.assert_any_call(constants["ZoweServiceName"], constants["ZoweAccountName"]) get_pass_func.assert_any_call(constants["ZoweServiceName"], f"{constants['ZoweAccountName']}-1") From f21170e15e89cd61ef668f20a701dc2f6af9d1c7 Mon Sep 17 00:00:00 2001 From: Timothy Johnson Date: Tue, 14 Nov 2023 09:14:48 -0500 Subject: [PATCH 45/45] Update commit hash for secrets_core package Signed-off-by: Timothy Johnson --- src/secrets/Cargo.lock | 2 +- src/secrets/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/secrets/Cargo.lock b/src/secrets/Cargo.lock index 04130c4b..8d9fbdee 100644 --- a/src/secrets/Cargo.lock +++ b/src/secrets/Cargo.lock @@ -482,7 +482,7 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "secrets_core" version = "0.1.0" -source = "git+https://github.com/zowe/zowe-cli.git?branch=feat/secrets/core-crate#a56f4c616c85cdf07a629ad1bf11b8c501f77b82" +source = "git+https://github.com/zowe/zowe-cli.git?branch=master#1b4d492d35ab44145e823821245a7e08de7d9192" dependencies = [ "cfg-if", "core-foundation", diff --git a/src/secrets/Cargo.toml b/src/secrets/Cargo.toml index 1552d198..3003a8af 100644 --- a/src/secrets/Cargo.toml +++ b/src/secrets/Cargo.toml @@ -10,4 +10,4 @@ crate-type = ["cdylib"] [dependencies] pyo3 = { version = "0.20.0", features = ["abi3-py37"] } -secrets_core = { git = "https://github.com/zowe/zowe-cli.git", branch = "feat/secrets/core-crate" } +secrets_core = { git = "https://github.com/zowe/zowe-cli.git", branch = "master" }