././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1641083381.8178484 keyring-23.5.0/0000755000175100001710000000000000000000000012611 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/.coveragerc0000644000175100001710000000014300000000000014730 0ustar00runnerdocker[run] omit = # leading `*/` for pytest-dev/pytest-cov#456 */.tox/* [report] show_missing = True ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/.editorconfig0000644000175100001710000000033000000000000015262 0ustar00runnerdockerroot = true [*] charset = utf-8 indent_style = tab indent_size = 4 insert_final_newline = true end_of_line = lf [*.py] indent_style = space max_line_length = 88 [*.{yml,yaml}] indent_style = space indent_size = 2 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/.flake80000644000175100001710000000021000000000000013755 0ustar00runnerdocker[flake8] max-line-length = 88 # jaraco/skeleton#34 max-complexity = 10 extend-ignore = # Black creates whitespace before colon E203 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1641083381.8138483 keyring-23.5.0/.github/0000755000175100001710000000000000000000000014151 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/.github/FUNDING.yml0000644000175100001710000000002700000000000015765 0ustar00runnerdockertidelift: pypi/PROJECT ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1641083381.8138483 keyring-23.5.0/.github/ISSUE_TEMPLATE/0000755000175100001710000000000000000000000016334 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/.github/ISSUE_TEMPLATE/bug_report.md0000644000175100001710000000106300000000000021026 0ustar00runnerdocker--- name: Bug report about: Create a report to help us improve title: '' labels: '' assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error **Expected behavior** A clear and concise description of what you expected to happen. **Environment** - OS: [e.g. macOS] ``` $ pip list | grep keyring ... $ keyring --list-backends ... ``` **Additional context** Add any other context about the problem here. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/.github/dependabot.yml0000644000175100001710000000022400000000000016777 0ustar00runnerdockerversion: 2 updates: - package-ecosystem: "pip" directory: "/" schedule: interval: "daily" allow: - dependency-type: "all" ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1641083381.8138483 keyring-23.5.0/.github/workflows/0000755000175100001710000000000000000000000016206 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/.github/workflows/main.yml0000644000175100001710000000210000000000000017646 0ustar00runnerdockername: tests on: [push, pull_request] jobs: test: strategy: matrix: python: - 3.7 - 3.9 - "3.10" platform: - ubuntu-latest - macos-latest - windows-latest runs-on: ${{ matrix.platform }} steps: - uses: actions/checkout@v2 - name: Setup Python uses: actions/setup-python@v2 with: python-version: ${{ matrix.python }} - name: Install tox run: | python -m pip install tox - name: Run tests run: tox release: needs: test if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Setup Python uses: actions/setup-python@v2 with: python-version: "3.10" - name: Install tox run: | python -m pip install tox - name: Release run: tox -e release env: TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/.gitignore0000644000175100001710000000134100000000000014600 0ustar00runnerdocker# Created by https://www.gitignore.io ### Python ### # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] # C extensions *.so # Distribution / packaging .Python env/ build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ *.egg-info/ .installed.cfg *.egg # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .cache nosetests.xml coverage.xml # Translations *.mo *.pot # Django stuff: *.log # Sphinx documentation docs/_build/ # PyBuilder target/ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/.pre-commit-config.yaml0000644000175100001710000000012100000000000017064 0ustar00runnerdockerrepos: - repo: https://github.com/psf/black rev: 20.8b1 hooks: - id: black ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/.readthedocs.yml0000644000175100001710000000011700000000000015676 0ustar00runnerdockerversion: 2 python: install: - path: . extra_requirements: - docs ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/.travis-macos0000644000175100001710000000024400000000000015222 0ustar00runnerdocker# Perform the manual steps on osx to install python and activate an environment brew update brew upgrade python export PATH=/usr/local/opt/python/libexec/bin:$PATH ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/CHANGES.rst0000644000175100001710000010464700000000000014427 0ustar00runnerdockerv23.5.0 ------- * Require Python 3.7 or later. v23.4.1 ------- * #551: Fixed docs warnings. v23.4.0 ------- * #549: EnvironCredential now allows for equality comparison. v23.3.0 ------- * #529: macOS backend is no longer viable if the API module cannot be loaded. Prevents "symbol not found" errors on macOS 11 (Big Sur) and later when a "universal2" binary is not used (available for Python 3.8.7 and later). * #547: Tests no longer attempt to run macOS backends even on macOS when the backend is non-viable. * #542: Change get_credential to return generic Credential. v23.2.1 ------- * #530: In libsecret tests, use a session collection to allow tests to pass on Debian. v23.2.0 ------- * #521: Add libsecret backend. v23.1.0 ------- * #519: macOS backend APIs updated to newer, non-legacy APIs. v23.0.1 ------- * #504: Better error with invalid parameter to init_keyring. * #505: Nicer documentation for headless Docker. v23.0.0 ------- * Backends now all invoke ``set_properties_from_env`` on self in the initializer. Derived backends should be sure to invoke ``super().__init__()``. v22.4.0 ------- * Use new entry points API from importlib_metadata 3.6. v22.3.0 ------- * Added redundant type declarations for accessor functions in ``keyring.core``. v22.2.0 ------- * #487: Restored ``Keyring`` in ``OS_X`` module with deprecation warning for users specifying the backend by name. v22.1.0 ------- * Added type declaration for ``keyring.core.get_keyring()``. v22.0.1 ------- * #486: Restored ``keyring.backends.OS_X`` module (with no functionality) to mask errors when older keyring versions are present until underlying issue is addressed and available in importlib_metadata. v22.0.0 ------- * Renamed macOS backend from ``OS_X`` to ``macOS``. Any users specifying the backend by name will need to use the new name ``keyring.backends.macOS``. v21.8.0 ------- * #438: For better interoperability with other applications, ``Windows`` backend now attempts to decode passwords using UTF-8 if UTF-16 decoding fails. Passwords are still stored as UTF-16. v21.7.0 ------- * #437: Package now declares typing support. v21.6.0 ------- * #403: Keyring no longer eagerly initializes the backend on import, but instead defers the backend initialization until a keyring is accessed. Any callers reliant on this early initialization behavior may need to call ``keyring.core.init_backend()`` to explicitly initialize the detected backend. v21.5.0 ------- * #474: SecretService and KWallet backends are now disabled if the relevant names are not available on D-Bus. Keyring should now be much more responsive in these environments. * #463: Fixed regression in KWallet ``get_credential`` where a simple string was returned instead of a SimpleCredential. v21.4.0 ------- * #431: KWallet backend now supports ``get_credential``. v21.3.1 ------- * #445: Suppress errors when ``sys.argv`` is not a list of at least one element. v21.3.0 ------- * #440: Keyring now honors XDG_CONFIG_HOME as ``~/.config``. * #452: SecretService ``get_credential`` now returns ``None`` for unmatched query. v21.2.1 ------- * #426: Restored lenience on startup when entry point metadata is missing. * #423: Avoid RecursionError when initializing backends when a limit is supplied. v21.2.0 ------- * #372: Chainer now deterministically resolves at a lower priority than the Fail keyring (when there are no backends to chain). * #372: Fail keyring now raises a ``NoKeyringError`` for easier selectability. * #405: Keyring now logs at DEBUG rather than INFO during backend startup. v21.1.1 ------- * Refreshed package metadata. v21.1.0 ------- * #380: In SecretService backend, close connections after using them. v21.0.0 ------- * Require Python 3.6 or later. v20.0.1 ------- * #417: Fix TypeError when backend fails to initialize. v20.0.0 ------- * Extracted ``keyring.testing`` package to contain supporting functionality for plugin backends. ``keyring.tests`` has been removed from the package. v19.3.0 ------- * Switch to `importlib.metadata `_ for loading entry points. Removes one dependency on Python 3.8. * Added new ``KeyringBackend.set_properties_from_env``. * #382: Add support for alternate persistence scopes for Windows backend. Set ``.persist`` to "local machine" or "session" to enable the alternate scopes or "enterprise" to use the default scope. * #404: Improve import times when a backend is specifically configured by lazily calling ``get_all_keyring``. 19.2.0 ------ * Add support for get_credential() with the SecretService backend. 19.1.0 ------ * #369: macOS Keyring now honors a ``KEYCHAIN_PATH`` environment variable. If set, Keyring will use that keychain instead of the default. 19.0.2 ------ * Refresh package skeleton. * Adopt `black `_ code style. 19.0.1 ------ * Merge with 18.0.1. 18.0.1 ------ * #386: ExceptionInfo no longer retains a reference to the traceback. 19.0.0 ------ * #383: Drop support for EOL Python 2.7 - 3.4. 18.0.0 ------ * #375: On macOS, the backend now raises a ``KeyringLocked`` when access to the keyring is denied (on get or set) instead of ``PasswordSetError`` or ``KeyringError``. Any API users may need to account for this change, probably by catching the parent ``KeyringError``. Additionally, the error message from the underying error is now included in any errors that occur. 17.1.1 ------ * #368: Update packaging technique to avoid 0.0.0 releases. 17.1.0 ------ * #366: When calling ``keyring.core.init_backend``, if any limit function is supplied, it is saved and later honored by the ``ChainerBackend`` as well. 17.0.0 ------ * #345: Remove application attribute from stored passwords using SecretService, addressing regression introduced in 10.5.0 (#292). Impacted Linux keyrings will once again prompt for a password for "Python program". 16.1.1 ------ * #362: Fix error on import due to circular imports on Python 3.4. 16.1.0 ------ * Refactor ChainerBackend, introduced in 16.0 to function as any other backend, activating when relevant. 16.0.2 ------ * #319: In Windows backend, trap all exceptions when attempting to import pywin32. 16.0.1 ------ * #357: Once again allow all positive, non-zero priority keyrings to participate. 16.0.0 ------ * #323: Fix race condition in delete_password on Windows. * #352: All suitable backends (priority 1 and greater) are allowed to participate. 15.2.0 ------ * #350: Added new API for ``get_credentials``, for backends that can resolve both a username and password for a service. 15.1.0 ------ * #340: Add the Null keyring, disabled by default. * #340: Added ``--disable`` option to command-line interface. * #340: Now honor a ``PYTHON_KEYRING_BACKEND`` environment variable to select a backend. Environments may set to ``keyring.backends.null.Keyring`` to disable keyring. 15.0.0 ------ Removed deprecated ``keyring.util.escape`` module. Fixed warning about using deprecated Abstract Base Classes from collections module. 14.0.0 ------ Removed ``getpassbackend`` module and alias in ``keyring.get_pass_get_password``. Instead, just use:: keyring.get_password(getpass.getuser(), 'Python') 13.2.1 ------ * #335: Fix regression in command line client. 13.2.0 ------ * Keyring command-line interface now reads the password directly from stdin if stdin is connected to a pipe. 13.1.0 ------ * #329: Improve output of ``keyring --list-backends``. 13.0.0 ------ * #327: In kwallet backend, if the collection or item is locked, a ``KeyringLocked`` exception is raised. Clients expecting a None response from ``get_password`` under this condition will need to catch this exception. Additionally, an ``InitError`` is now raised if the connection cannot be established to the DBus. * #298: In kwallet backend, when checking an existing handle, verify that it is still valid or create a new connection. 12.2.1 ------ * Fixed issue in SecretService. Ref #226. 12.2.0 ------ * #322: Fix AttributeError when ``escape.__builtins__`` is a dict. * Deprecated ``keyring.util.escape`` module. If you use this module or encounter the warning (on the latest release of your packages), please `file a ticket `_. 12.1.0 ------ * Unpin SecretStorage on Python 3.5+. Requires that Setuptools 17.1 be used. Note that the special handling will be unnecessary once Pip 9 can be assumed (as it will exclude SecretStorage 3 in non-viable environments). 12.0.2 ------ * Pin SecretStorage to 2.x. 12.0.1 ------ * #314: No changes except to rebuild. 12.0.0 ------ * #310: Keyring now loads all backends through entry points. For most users, this release will be fully compatible. Some users may experience compatibility issues if entrypoints is not installed (as declared) or the metadata on which entrypoints relies is unavailable. For that reason, the package is released with a major version bump. 11.1.0 ------ * #312: Use ``entrypoints`` instead of pkg_resources to avoid performance hit loading pkg_resources. Adds a dependency on ``entrypoints``. 11.0.0 ------ * #294: No longer expose ``keyring.__version__`` (added in 8.1) to avoid performance hit loading pkg_resources. 10.6.0 ------ * #299: Keyring exceptions are now derived from a base ``keyring.errors.KeyringError``. 10.5.1 ------ * #296: Prevent AttributeError on import when Debian has created broken dbus installs. 10.5.0 ------ * #287: Added ``--list-backends`` option to command-line interface. * Removed ``logger`` from ``keyring``. See #291 for related request. * #292: Set the appid for SecretService & KWallet to something meaningful. 10.4.0 ------ * #279: In Kwallet, pass mainloop to SessionBus. * #278: Unpin pywin32-ctypes, but blacklist known incompatible versions. 10.3.3 ------ * #278: Pin to pywin32-ctypes 0.0.1 to avoid apparent breakage introduced in 0.1.0. 10.3.2 ------ * #267: More leniently unescape lowercased characters as they get re-cased by ConfigParser. 10.3.1 ------ * #266: Use private compatibility model rather than six to avoid the dependency. 10.3 ---- * #264: Implement devpi hook for supplying a password when logging in with `devpi `_ client. * #260: For macOS, added initial API support for internet passwords. 10.2 ---- * #259: Allow to set a custom application attribute for SecretService backend. 10.1 ---- * #253: Backends now expose a '.name' attribute suitable for identifying each backend to users. 10.0.2 ------ * #247: Restored console script. 10.0.1 ------ * Update readme to reflect test recommendations. 10.0 ---- * Drop support for Python 3.2. * Test suite now uses tox instead of pytest-runner. Test requirements are now defined in tests/requirements.txt. 9.3.1 ----- * Link to the new Gitter chat room is now in the readme. * Issue #235: ``kwallet`` backend now returns string objects instead of ``dbus.String`` objects, for less surprising reprs. * Minor doc fixes. 9.3 --- * Issue #161: In SecretService backend, unlock individual entries. 9.2.1 ----- * Issue #230: Don't rely on dbus-python and instead defer to SecretStorage to describe the installation requirements. 9.2 --- * Issue #231 via #233: On Linux, ``secretstorage`` is now a declared dependency, allowing recommended keyring to work simply after installation. 9.1 --- * Issue #83 via #229: ``kwallet`` backend now stores the service name as a folder name in the backend rather than storing all passwords in a Python folder. 9.0 --- * Issue #217: Once again, the OS X backend uses the Framework API for invoking the Keychain service. As a result, applications utilizing this API will be authorized per application, rather than relying on the authorization of the 'security' application. Consequently, users will be prompted to authorize the system Python executable and also new Python executables, such as those created by virtualenv. #260: No longer does the keyring honor the ``store`` attribute on the keyring. Only application passwords are accessible. 8.7 --- * Changelog now links to issues and provides dates of releases. 8.6 --- * Issue #217: Add warning in OS Keyring when 'store' is set to 'internet' to determine if this feature is used in the wild. 8.5.1 ----- * Pull Request #216: Kwallet backend now has lower priority than the preferred SecretService backend, now that the desktop check is no longer in place. 8.5 --- * Issue #168: Now prefer KF5 Kwallet to KF4. Users relying on KF4 must use prior releases. 8.4 --- * Pull Request #209: Better error message when no backend is available (indicating keyrings.alt as a quick workaround). * Pull Request #208: Fix pywin32-ctypes package name in requirements. 8.3 --- * Issue #207: Library now requires win32ctypes on Windows systems, which will be installed automatically by Setuptools 0.7 or Pip 6 (or later). * Actually removed QtKwallet, which was meant to be dropped in 8.0 but somehow remained. 8.2 --- * Update readme to include how-to use with Linux non-graphical environments. 8.1 --- * Issue #197: Add ``__version__`` attribute to keyring module. 8.0 --- * Issue #117: Removed all but the preferred keyring backends for each of the major desktop platforms: - keyring.backends.kwallet.DBusKeyring - keyring.backends.OS_X.Keyring - keyring.backends.SecretService.Keyring - keyring.backends.Windows.WinVaultKeyring All other keyrings have been moved to a new package, `keyrings.alt `_ and backward-compatibility aliases removed. To retain availability of these less preferred keyrings, include that package in your installation (install both keyring and keyrings.alt). As these keyrings have moved, any keyrings indicated explicitly in configuration will need to be updated to replace "keyring.backends." with "keyrings.alt.". For example, "keyring.backends.file.PlaintextKeyring" becomes "keyrings.alt.file.PlaintextKeyring". 7.3.1 ----- * Issue #194: Redirect away from docs until they have something more than the changelog. Users seeking the changelog will want to follow the `direct link `_. 7.3 --- * Issue #117: Added support for filtering which backends are acceptable. To limit to only loading recommended keyrings (those with priority >= 1), call:: keyring.core.init_backend(limit=keyring.core.recommended) 7.2 --- * Pull Request #190: OS X backend now exposes a ``keychain`` attribute, which if set will be used by ``get_password`` when retrieving passwords. Useful in environments such as when running under cron where the default keychain is not the same as the default keychain in a login session. Example usage:: keyring.get_keyring().keychain = '/path/to/login.keychain' pw = keyring.get_password(...) 7.1 --- * Issue #186: Removed preference for keyrings based on ``XDG_CURRENT_DESKTOP`` as these values are to varied to be a reliable indicator of which keyring implementation might be preferable. 7.0.2 ----- * Issue #187: Restore ``Keyring`` name in ``kwallet`` backend. Users of keyring 6.1 or later should prefer an explicit reference to DBusKeyring or QtKeyring instead. 7.0.1 ----- * Issue #183 and Issue #185: Gnome keyring no longer relies on environment variables, but instead relies on the GnomeKeyring library to determine viability. 7.0 --- * Issue #99: Keyring now expects the config file to be located in the XDG_CONFIG_HOME rather than XDG_DATA_HOME and will fail to start if the config is found in the old location but not the new. On systems where the two locations are distinct, simply copy or symlink the config to remain compatible with older versions or move the file to work only with 7.0 and later. * Replaced Pull Request #182 with a conditional SessionBus construction, based on subsequent discussion. 6.1.1 ----- * Pull Request #182: Prevent DBus from indicating as a viable backend when no viable X DISPLAY variable is present. 6.1 --- * Pull Request #174: Add DBus backend for KWallet, preferred to Qt backend. Theoretically, it should be auto-detected based on available libraries and interchangeable with the Qt backend. 6.0 --- * Drop support for Python 2.6. 5.7.1 ----- * Updated project metadata to match Github hosting and generally refreshed the metadata structure to match practices with other projects. 5.7 --- * Issue #177: Resolve default keyring name on Gnome using the API. * Issue #145: Add workaround for password exposure through process status for most passwords containing simple characters. 5.6 --- * Allow keyring to be invoked from command-line with ``python -m keyring``. 5.5.1 ----- * Issue #156: Fixed test failures in ``pyfs`` keyring related to 0.5 release. 5.5 --- * Pull Request #176: Use recommended mechanism for checking GnomeKeyring version. 5.4 --- * Prefer setuptools_scm to hgtools. 5.3 --- * Prefer hgtools to setuptools_scm due to `setuptools_scm #21 `_. 5.2 --- * Prefer setuptools_scm to hgtools. 5.1 --- * Host project at Github (`repo `_). 5.0 --- * Version numbering is now derived from the code repository tags via `hgtools `_. * Build and install now requires setuptools. 4.1.1 ----- * The entry point group must look like a module name, so the group is now "keyring.backends". 4.1 --- * Added preliminary support for loading keyring backends through ``setuptools entry points``, specifically "keyring backends". 4.0 --- * Removed ``keyring_path`` parameter from ``load_keyring``. See release notes for 3.0.3 for more details. * Issue #22: Removed support for loading the config from the current directory. The config file must now be located in the platform-specific config location. 3.8 --- * Issue #22: Deprecated loading of config from current directory. Support for loading the config in this manner will be removed in a future version. * Issue #131: Keyring now will prefer `pywin32-ctypes `_ to pywin32 if available. 3.7 --- * Gnome keyring no longer relies on the GNOME_KEYRING_CONTROL environment variable. * Issue #140: Restore compatibility for older versions of PyWin32. 3.6 --- * `Pull Request #1 (github) `_: Add support for packages that wish to bundle keyring by using relative imports throughout. 3.5 --- * Issue #49: Give the backend priorities a 1.5 multiplier bump when an XDG_CURRENT_DESKTOP environment variable matches the keyring's target environment. * Issue #99: Clarified documentation on location of config and data files. Prepared the code base to treat the two differently on Unix-based systems. For now, the behavior is unchanged. 3.4 --- * Extracted FileBacked and Encrypted base classes. * Add a pyinstaller hook to expose backend modules. Ref #124 * Pull request #41: Use errno module instead of hardcoding error codes. * SecretService backend: correctly handle cases when user dismissed the collection creation or unlock prompt. 3.3 --- * Pull request #40: KWallet backend will now honor the ``KDE_FULL_SESSION`` environment variable as found on openSUSE. 3.2.1 ----- * SecretService backend: use a different function to check that the backend is functional. The default collection may not exist, but the collection will remain usable in that case. Also, make the error message more verbose. Resolves https://bugs.launchpad.net/bugs/1242412. 3.2 --- * Issue #120: Invoke KeyringBackend.priority during load_keyring to ensure that any keyring loaded is actually viable (or raises an informative exception). * File keyring: - Issue #123: fix removing items. - Correctly escape item name when removing. - Use with statement when working with files. * Add a test for removing one item in group. * Issue #81: Added experimental support for third-party backends. See `keyring.core._load_library_extensions` for information on supplying a third-party backend. 3.1 --- * All code now runs natively on both Python 2 and Python 3, no 2to3 conversion is required. * Testsuite: clean up, and make more use of unittest2 methods. 3.0.5 ----- * Issue #114: Fix logic in pyfs detection. 3.0.4 ----- * Issue #114: Fix detection of pyfs under Mercurial Demand Import. 3.0.3 ----- * Simplified the implementation of ``keyring.core.load_keyring``. It now uses ``__import__`` instead of loading modules explicitly. The ``keyring_path`` parameter to ``load_keyring`` is now deprecated. Callers should instead ensure their module is available on ``sys.path`` before calling ``load_keyring``. Keyring still honors ``keyring-path``. This change fixes Issue #113 in which the explicit module loading of keyring modules was breaking package-relative imports. 3.0.2 ----- * Renamed ``keyring.util.platform`` to ``keyring.util.platform_``. As reported in Issue #112 and `mercurial_keyring #31 `_ and in `Mercurial itself `_, Mercurial's Demand Import does not honor ``absolute_import`` directives, so it's not possible to have a module with the same name as another top-level module. A patch is in place to fix this issue upstream, but to support older Mercurial versions, this patch will remain for some time. 3.0.1 ----- * Ensure that modules are actually imported even in Mercurial's Demand Import environment. 3.0 --- * Removed support for Python 2.5. * Removed names in ``keyring.backend`` moved in 1.1 and previously retained for compatibility. 2.1.1 ----- * Restored Python 2.5 compatibility (lost in 2.0). 2.1 --- * Issue #10: Added a 'store' attribute to the OS X Keyring, enabling custom instances of the KeyringBackend to use another store, such as the 'internet' store. For example:: keys = keyring.backends.OS_X.Keyring() keys.store = 'internet' keys.set_password(system, user, password) keys.get_password(system, user) The default for all instances can be set in the class:: keyring.backends.OS_X.Keyring.store = 'internet' * GnomeKeyring: fix availability checks, and make sure the warning message from pygobject is not printed. * Fixes to GnomeKeyring and SecretService tests. 2.0.3 ----- * Issue #112: Backend viability/priority checks now are more aggressive about module presence checking, requesting ``__name__`` from imported modules to force the demand importer to actually attempt the import. 2.0.2 ----- * Issue #111: Windows backend isn't viable on non-Windows platforms. 2.0.1 ----- * Issue #110: Fix issues with ``Windows.RegistryKeyring``. 2.0 --- * Issue #80: Prioritized backend support. The primary interface for Keyring backend classes has been refactored to now emit a 'priority' based on the current environment (operating system, libraries available, etc). These priorities provide an indication of the applicability of that backend for the current environment. Users are still welcome to specify a particular backend in configuration, but the default behavior should now be to select the most appropriate backend by default. 1.6.1 ----- * Only include pytest-runner in 'setup requirements' when ptr invocation is indicated in the command-line (Issue #105). 1.6 --- * GNOME Keyring backend: - Use the same attributes (``username`` / ``service``) as the SecretService backend uses, allow searching for old ones for compatibility. - Also set ``application`` attribute. - Correctly handle all types of errors, not only ``CANCELLED`` and ``NO_MATCH``. - Avoid printing warnings to stderr when GnomeKeyring is not available. * Secret Service backend: - Use a better label for passwords, the same as GNOME Keyring backend uses. 1.5 --- * SecretService: allow deleting items created using previous python-keyring versions. Before the switch to secretstorage, python-keyring didn't set "application" attribute. Now in addition to supporting searching for items without that attribute, python-keyring also supports deleting them. * Use ``secretstorage.get_default_collection`` if it's available. On secretstorage 1.0 or later, python-keyring now tries to create the default collection if it doesn't exist, instead of just raising the error. * Improvements for tests, including fix for Issue #102. 1.4 --- * Switch GnomeKeyring backend to use native libgnome-keyring via GObject Introspection, not the obsolete python-gnomekeyring module. 1.3 --- * Use the `SecretStorage library `_ to implement the Secret Service backend (instead of using dbus directly). Now the keyring supports prompting for and deleting passwords. Fixes #69, #77, and #93. * Catch `gnomekeyring.IOError` per the issue `reported in Nova client `_. * Issue #92 Added support for delete_password on Mac OS X Keychain. 1.2.3 ----- * Fix for Encrypted File backend on Python 3. * Issue #97 Improved support for PyPy. 1.2.2 ----- * Fixed handling situations when user cancels kwallet dialog or denies access for the app. 1.2.1 ----- * Fix for kwallet delete. * Fix for OS X backend on Python 3. * Issue #84: Fix for Google backend on Python 3 (use of raw_input not caught by 2to3). 1.2 --- * Implemented delete_password on most keyrings. Keyring 2.0 will require delete_password to implement a Keyring. Fixes #79. 1.1.2 ----- * Issue #78: pyfilesystem backend now works on Windows. 1.1.1 ----- * Fixed MANIFEST.in so .rst files are included. 1.1 --- This is the last build that will support installation in a pure-distutils mode. Subsequent releases will require setuptools/distribute to install. Python 3 installs have always had this requirement (for 2to3 install support), but starting with the next minor release (1.2+), setuptools will be required. Additionally, this release has made some substantial refactoring in an attempt to modularize the backends. An attempt has been made to maintain 100% backward-compatibility, although if your library does anything fancy with module structure or clasess, some tweaking may be necessary. The backward-compatible references will be removed in 2.0, so the 1.1+ releases represent a transitional implementation which should work with both legacy and updated module structure. * Added a console-script 'keyring' invoking the command-line interface. * Deprecated _ExtensionKeyring. * Moved PasswordSetError and InitError to an `errors` module (references kept for backward-compatibility). * Moved concrete backend implementations into their own modules (references kept for backward compatibility): - OSXKeychain -> backends.OS_X.Keyring - GnomeKeyring -> backends.Gnome.Keyring - SecretServiceKeyring -> backends.SecretService.Keyring - KDEKWallet -> backends.kwallet.Keyring - BasicFileKeyring -> backends.file.BaseKeyring - CryptedFileKeyring -> backends.file.EncryptedKeyring - UncryptedFileKeyring -> backends.file.PlaintextKeyring - Win32CryptoKeyring -> backends.Windows.EncryptedKeyring - WinVaultKeyring -> backends.Windows.WinVaultKeyring - Win32CryptoRegistry -> backends.Windows.RegistryKeyring - select_windows_backend -> backends.Windows.select_windows_backend - GoogleDocsKeyring -> backends.Google.DocsKeyring - Credential -> keyring.credentials.Credential - BaseCredential -> keyring.credentials.SimpleCredential - EnvironCredential -> keyring.credentials.EnvironCredential - GoogleEnvironCredential -> backends.Google.EnvironCredential - BaseKeyczarCrypter -> backends.keyczar.BaseCrypter - KeyczarCrypter -> backends.keyczar.Crypter - EnvironKeyczarCrypter -> backends.keyczar.EnvironCrypter - EnvironGoogleDocsKeyring -> backends.Google.KeyczarDocsKeyring - BasicPyfilesystemKeyring -> backends.pyfs.BasicKeyring - UnencryptedPyfilesystemKeyring -> backends.pyfs.PlaintextKeyring - EncryptedPyfilesystemKeyring -> backends.pyfs.EncryptedKeyring - EnvironEncryptedPyfilesystemKeyring -> backends.pyfs.KeyczarKeyring - MultipartKeyringWrapper -> backends.multi.MultipartKeyringWrapper * Officially require Python 2.5 or greater (although unofficially, this requirement has been in place since 0.10). 1.0 --- This backward-incompatible release attempts to remove some cruft from the codebase that's accumulated over the versions. * Removed legacy file relocation support. `keyring` no longer supports loading configuration or file-based backends from ~. If upgrading from 0.8 or later, the files should already have been migrated to their new proper locations. If upgrading from 0.7.x or earlier, the files will have to be migrated manually. * Removed CryptedFileKeyring migration support. To maintain an existing CryptedFileKeyring, one must first upgrade to 0.9.2 or later and access the keyring before upgrading to 1.0 to retain the existing keyring. * File System backends now create files without group and world permissions. Fixes #67. 0.10.1 ------ * Merged 0.9.3 to include fix for #75. 0.10 ---- * Add support for using `Keyczar `_ to encrypt keyrings. Keyczar is "an open source cryptographic toolkit designed to make it easier and safer for developers to use cryptography in their applications." * Added support for storing keyrings on Google Docs or any other filesystem supported by pyfilesystem. * Fixed issue in Gnome Keyring when unicode is passed as the service name, username, or password. * Tweaked SecretService code to pass unicode to DBus, as unicode is the preferred format. * Issue #71 - Fixed logic in CryptedFileKeyring. * Unencrypted keyring file will be saved with user read/write (and not group or world read/write). 0.9.3 ----- * Ensure migration is run when get_password is called. Fixes #75. Thanks to Marc Deslauriers for reporting the bug and supplying the patch. 0.9.2 ----- * Keyring 0.9.1 introduced a whole different storage format for the CryptedFileKeyring, but this introduced some potential compatibility issues. This release incorporates the security updates but reverts to the INI file format for storage, only encrypting the passwords and leaving the service and usernames in plaintext. Subsequent releases may incorporate a new keyring to implement a whole-file encrypted version. Fixes #64. * The CryptedFileKeyring now requires simplejson for Python 2.5 clients. 0.9.1 ----- * Fix for issue where SecretServiceBackend.set_password would raise a UnicodeError on Python 3 or when a unicode password was provided on Python 2. * CryptedFileKeyring now uses PBKDF2 to derive the key from the user's password and a random hash. The IV is chosen randomly as well. All the stored passwords are encrypted at once. Any keyrings using the old format will be automatically converted to the new format (but will no longer be compatible with 0.9 and earlier). The user's password is no longer limited to 32 characters. PyCrypto 2.5 or greater is now required for this keyring. 0.9 --- * Add support for GTK 3 and secret service D-Bus. Fixes #52. * Issue #60 - Use correct method for decoding. 0.8.1 ----- * Fix regression in keyring lib on Windows XP where the LOCALAPPDATA environment variable is not present. 0.8 --- * Mac OS X keyring backend now uses subprocess calls to the `security` command instead of calling the API, which with the latest updates, no longer allows Python to invoke from a virtualenv. Fixes issue #13. * When using file-based storage, the keyring files are no longer stored in the user's home directory, but are instead stored in platform-friendly locations (`%localappdata%\Python Keyring` on Windows and according to the freedesktop.org Base Dir Specification (`$XDG_DATA_HOME/python_keyring` or `$HOME/.local/share/python_keyring`) on other operating systems). This fixes #21. *Backward Compatibility Notice* Due to the new storage location for file-based keyrings, keyring 0.8 supports backward compatibility by automatically moving the password files to the updated location. In general, users can upgrade to 0.8 and continue to operate normally. Any applications that customize the storage location or make assumptions about the storage location will need to take this change into consideration. Additionally, after upgrading to 0.8, it is not possible to downgrade to 0.7 without manually moving configuration files. In 1.0, the backward compatibility will be removed. 0.7.1 ----- * Removed non-ASCII characters from README and CHANGES docs (required by distutils if we're to include them in the long_description). Fixes #55. 0.7 --- * Python 3 is now supported. All tests now pass under Python 3.2 on Windows and Linux (although Linux backend support is limited). Fixes #28. * Extension modules on Mac and Windows replaced by pure-Python ctypes implementations. Thanks to Jerome Laheurte. * WinVaultKeyring now supports multiple passwords for the same service. Fixes #47. * Most of the tests don't require user interaction anymore. * Entries stored in Gnome Keyring appears now with a meaningful name if you try to browser your keyring (for ex. with Seahorse) * Tests from Gnome Keyring no longer pollute the user own keyring. * `keyring.util.escape` now accepts only unicode strings. Don't try to encode strings passed to it. 0.6.2 ----- * fix compiling on OSX with XCode 4.0 0.6.1 ----- * Gnome keyring should not be used if there is no DISPLAY or if the dbus is not around (https://bugs.launchpad.net/launchpadlib/+bug/752282). * Added `keyring.http` for facilitating HTTP Auth using keyring. * Add a utility to access the keyring from the command line. 0.5.1 ----- * Remove a spurious KDE debug message when using KWallet * Fix a bug that caused an exception if the user canceled the KWallet dialog (https://bitbucket.org/kang/python-keyring-lib/issue/37/user-canceling-of-kde-wallet-dialogs). 0.5 --- * Now using the existing Gnome and KDE python libs instead of custom C++ code. * Using the getpass module instead of custom code 0.4 --- * Fixed the setup script (some subdirs were not included in the release.) 0.3 --- * Fixed keyring.core when the user doesn't have a cfg, or is not properly configured. * Fixed escaping issues for usernames with non-ascii characters 0.2 --- * Add support for Python 2.4+ http://bitbucket.org/kang/python-keyring-lib/issue/2 * Fix the bug in KDE Kwallet extension compiling http://bitbucket.org/kang/python-keyring-lib/issue/3 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/Dockerfile0000644000175100001710000000104400000000000014602 0ustar00runnerdockerFROM mcr.microsoft.com/windows/servercore:ltsc2019 RUN powershell -c "Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iwr https://chocolatey.org/install.ps1 -UseBasicParsing | iex" RUN choco feature enable -n allowGlobalConfirmation RUN choco install git python RUN python -m pip install -U pip pipx RUN setx path "%path%;C:\Users\ContainerAdministrator\.local\bin" RUN pipx install tox RUN setx TOX_WORK_DIR C:\tox CMD powershell ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/LICENSE0000644000175100001710000000203200000000000013613 0ustar00runnerdockerCopyright Jason R. Coombs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1641083381.8178484 keyring-23.5.0/PKG-INFO0000644000175100001710000004344000000000000013713 0ustar00runnerdockerMetadata-Version: 2.1 Name: keyring Version: 23.5.0 Summary: Store and access your passwords safely. Home-page: https://github.com/jaraco/keyring Author: Kang Zhang Author-email: jobo.zh@gmail.com Maintainer: Jason R. Coombs Maintainer-email: jaraco@jaraco.com License: UNKNOWN Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Python Software Foundation License Classifier: License :: OSI Approved :: MIT License Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3 :: Only Requires-Python: >=3.7 Provides-Extra: testing Provides-Extra: docs License-File: LICENSE .. image:: https://img.shields.io/pypi/v/keyring.svg :target: `PyPI link`_ .. image:: https://img.shields.io/pypi/pyversions/keyring.svg :target: `PyPI link`_ .. _PyPI link: https://pypi.org/project/keyring .. image:: https://github.com/jaraco/keyring/workflows/tests/badge.svg :target: https://github.com/jaraco/keyring/actions?query=workflow%3A%22tests%22 :alt: tests .. image:: https://img.shields.io/badge/code%20style-black-000000.svg :target: https://github.com/psf/black :alt: Code style: Black .. image:: https://readthedocs.org/projects/keyring/badge/?version=latest :target: https://keyring.readthedocs.io/en/latest/?badge=latest .. image:: https://img.shields.io/badge/skeleton-2021-informational :target: https://blog.jaraco.com/skeleton .. image:: https://tidelift.com/badges/package/pypi/keyring :target: https://tidelift.com/subscription/pkg/pypi-keyring?utm_source=pypi-keyring&utm_medium=readme .. image:: https://badges.gitter.im/jaraco/keyring.svg :alt: Join the chat at https://gitter.im/jaraco/keyring :target: https://gitter.im/jaraco/keyring?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge The Python keyring library provides an easy way to access the system keyring service from python. It can be used in any application that needs safe password storage. These recommended keyring backends are supported: * macOS `Keychain `_ * Freedesktop `Secret Service `_ supports many DE including GNOME (requires `secretstorage `_) * KDE4 & KDE5 `KWallet `_ (requires `dbus `_) * `Windows Credential Locker `_ Other keyring implementations are available through `Third-Party Backends`_. Installation - Linux ==================== On Linux, the KWallet backend relies on dbus-python_, which does not always install correctly when using pip (compilation is needed). For best results, install dbus-python as a system package. .. _dbus-python: https://gitlab.freedesktop.org/dbus/dbus-python Compatibility - macOS ===================== macOS keychain support macOS 11 (Big Sur) and later requires Python 3.8.7 or later with the "universal2" binary. See `#525 `_ for details. Using Keyring ============= The basic usage of keyring is pretty simple: just call ``keyring.set_password`` and ``keyring.get_password``:: >>> import keyring >>> keyring.set_password("system", "username", "password") >>> keyring.get_password("system", "username") 'password' Command-line Utility -------------------- Keyring supplies a ``keyring`` command which is installed with the package. After installing keyring in most environments, the command should be available for setting, getting, and deleting passwords. For more information on usage, invoke with no arguments or with ``--help`` as so:: $ keyring --help $ keyring set system username Password for 'username' in 'system': $ keyring get system username password The command-line functionality is also exposed as an executable package, suitable for invoking from Python like so:: $ python -m keyring --help $ python -m keyring set system username Password for 'username' in 'system': $ python -m keyring get system username password Configuring =========== The python keyring lib contains implementations for several backends. The library will attempt to automatically choose the most suitable backend for the current environment. Users may also specify the preferred keyring in a config file or by calling the ``set_keyring()`` function. Config file path ---------------- The configuration is stored in a file named "keyringrc.cfg" found in a platform-specific location. To determine where the config file is stored, run the following:: python -c "import keyring.util.platform_; print(keyring.util.platform_.config_root())" Some keyrings also store the keyring data in the file system. To determine where the data files are stored, run:: python -c "import keyring.util.platform_; print(keyring.util.platform_.data_root())" Config file content ------------------- To specify a keyring backend, set the **default-keyring** option to the full path of the class for that backend, such as ``keyring.backends.OS_X.Keyring``. If **keyring-path** is indicated, keyring will add that path to the Python module search path before loading the backend. For example, this config might be used to load the ``SimpleKeyring`` from the ``simplekeyring`` module in the ``./demo`` directory (not implemented):: [backend] default-keyring=simplekeyring.SimpleKeyring keyring-path=demo Third-Party Backends ==================== In addition to the backends provided by the core keyring package for the most common and secure use cases, there are additional keyring backend implementations available for other use-cases. Simply install them to make them available: - `keyrings.cryptfile `_ - Encrypted text file storage. - `keyring_jeepney `__ - a pure Python backend using the secret service DBus API for desktop Linux. - `keyrings.alt `_ - "alternate", possibly-insecure backends, originally part of the core package, but available for opt-in. - `gsheet-keyring `_ - a backend that stores secrets in a Google Sheet. For use with `ipython-secrets `_. - `bitwarden-keyring `_ - a backend that stores secrets in the `BitWarden `_ password manager. - `sagecipher `_ - an encryption backend which uses the ssh agent protocol's signature operation to derive the cipher key. - `keyrings.osx_keychain_keys `_ - OSX keychain key-management, for private, public and symmetric keys. Write your own keyring backend ============================== The interface for the backend is defined by ``keyring.backend.KeyringBackend``. Every backend should derive from that base class and define a ``priority`` attribute and three functions: ``get_password()``, ``set_password()``, and ``delete_password()``. The ``get_credential()`` function may be defined if desired. See the ``backend`` module for more detail on the interface of this class. Keyring employs entry points to allow any third-party package to implement backends without any modification to the keyring itself. Those interested in creating new backends are encouraged to create new, third-party packages in the ``keyrings`` namespace, in a manner modeled by the `keyrings.alt package `_. See the ``setup.cfg`` file in that project for a hints on how to create the requisite entry points. Backends that prove essential may be considered for inclusion in the core library, although the ease of installing these third-party packages should mean that extensions may be readily available. To create an extension for Keyring, please submit a pull request to have your extension mentioned as an available extension. Runtime Configuration ===================== Keyring additionally allows programmatic configuration of the backend calling the api ``set_keyring()``. The indicated backend will subsequently be used to store and retrieve passwords. To invoke ``set_keyring``:: # define a new keyring class which extends the KeyringBackend import keyring.backend class TestKeyring(keyring.backend.KeyringBackend): """A test keyring which always outputs same password """ priority = 1 def set_password(self, servicename, username, password): pass def get_password(self, servicename, username): return "password from TestKeyring" def delete_password(self, servicename, username): pass # set the keyring for keyring lib keyring.set_keyring(TestKeyring()) # invoke the keyring lib try: keyring.set_password("demo-service", "tarek", "passexample") print("password stored successfully") except keyring.errors.PasswordSetError: print("failed to store password") print("password", keyring.get_password("demo-service", "tarek")) Disabling Keyring ================= In many cases, uninstalling keyring will never be necessary. Especially on Windows and macOS, the behavior of keyring is usually degenerate, meaning it will return empty values to the caller, allowing the caller to fall back to some other behavior. In some cases, the default behavior of keyring is undesirable and it would be preferable to disable the keyring behavior altogether. There are several mechanisms to disable keyring: - Uninstall keyring. Most applications are tolerant to keyring not being installed. Uninstalling keyring should cause those applications to fall back to the behavior without keyring. This approach affects that Python environment where keyring would otherwise have been installed. - Configure the Null keyring in the environment. Set ``PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring`` in the environment, and the ``Null`` (degenerate) backend will be used. This approach affects all uses of Keyring where that variable is set. - Permanently configure the Null keyring for the user by running ``keyring --disable`` or ``python -m keyring --disable``. This approach affects all uses of keyring for that user. Altering Keyring Behavior ========================= Keyring provides a mechanism to alter the keyring's behavior through environment variables. Each backend implements a ``KeyringBackend.set_properties_from_env``, which when invoked will find all environment variables beginning with ``KEYRING_PROPERTY_{NAME}`` and will set a property for each ``{NAME.lower()}`` on the keyring. This method is invoked during initialization for the default/configured keyring. This mechanism may be used to set some useful values on various keyrings, including: - keychain; macOS, path to an alternate keychain file - appid; Linux/SecretService, alternate ID for the application Using Keyring on Ubuntu 16.04 ============================= The following is a complete transcript for installing keyring in a virtual environment on Ubuntu 16.04. No config file was used:: $ sudo apt install python3-venv libdbus-glib-1-dev $ cd /tmp $ pyvenv py3 $ source py3/bin/activate $ pip install -U pip $ pip install secretstorage dbus-python $ pip install keyring $ python >>> import keyring >>> keyring.get_keyring() >>> keyring.set_password("system", "username", "password") >>> keyring.get_password("system", "username") 'password' Using Keyring on headless Linux systems ======================================= It is possible to use the SecretService backend on Linux systems without X11 server available (only D-Bus is required). In this case: * Install the `GNOME Keyring`_ daemon. * Start a D-Bus session, e.g. run ``dbus-run-session -- sh`` and run the following commands inside that shell. * Run ``gnome-keyring-daemon`` with ``--unlock`` option. The description of that option says: Read a password from stdin, and use it to unlock the login keyring or create it if the login keyring does not exist. When that command is started, enter a password into stdin and press Ctrl+D (end of data). After that, the daemon will fork into background (use ``--foreground`` option to block). * Now you can use the SecretService backend of Keyring. Remember to run your application in the same D-Bus session as the daemon. .. _GNOME Keyring: https://wiki.gnome.org/Projects/GnomeKeyring Using Keyring on headless Linux systems in a Docker container ============================================================= It is possible to use keyring with the SecretService backend in Docker containers as well. All you need to do is install the necessary dependencies and add the `--privileged` flag to avoid any `Operation not permitted` errors when attempting to unlock the system's keyring. The following is a complete transcript for installing keyring on a Ubuntu 18:04 container:: docker run -it -d --privileged ubuntu:18.04 $ apt-get update $ apt install -y gnome-keyring python3-venv python3-dev $ python3 -m venv venv $ source venv/bin/activate # source a virtual environment to avoid polluting your system $ pip3 install --upgrade pip $ pip3 install keyring $ dbus-run-session -- sh # this will drop you into a new D-bus shell $ echo 'somecredstorepass' | gnome-keyring-daemon --unlock # unlock the system's keyring $ python >>> import keyring >>> keyring.get_keyring() >>> keyring.set_password("system", "username", "password") >>> keyring.get_password("system", "username") 'password' Integration =========== API --- The keyring lib has a few functions: * ``get_keyring()``: Return the currently-loaded keyring implementation. * ``get_password(service, username)``: Returns the password stored in the active keyring. If the password does not exist, it will return None. * ``get_credential(service, username)``: Return a credential object stored in the active keyring. This object contains at least ``username`` and ``password`` attributes for the specified service, where the returned ``username`` may be different from the argument. * ``set_password(service, username, password)``: Store the password in the keyring. * ``delete_password(service, username)``: Delete the password stored in keyring. If the password does not exist, it will raise an exception. In all cases, the parameters (``service``, ``username``, ``password``) should be Unicode text. Exceptions ---------- The keyring lib raises following exceptions: * ``keyring.errors.KeyringError``: Base Error class for all exceptions in keyring lib. * ``keyring.errors.InitError``: Raised when the keyring cannot be initialized. * ``keyring.errors.PasswordSetError``: Raised when password cannot be set in the keyring. * ``keyring.errors.PasswordDeleteError``: Raised when the password cannot be deleted in the keyring. Get Involved ============ Python keyring lib is an open community project and eagerly welcomes contributors. * Repository: https://github.com/jaraco/keyring/ * Bug Tracker: https://github.com/jaraco/keyring/issues/ * Mailing list: http://groups.google.com/group/python-keyring For Enterprise ============== Available as part of the Tidelift Subscription. This project and the maintainers of thousands of other packages are working with Tidelift to deliver one enterprise subscription that covers all of the open source you use. `Learn more `_. Security Contact ================ To report a security vulnerability, please use the `Tidelift security contact `_. Tidelift will coordinate the fix and disclosure. Security Considerations ======================= Each builtin backend may have security considerations to understand before using this library. Authors of tools or libraries utilizing ``keyring`` are encouraged to consider these concerns. As with any list of known security concerns, this list is not exhaustive. Additional issues can be added as needed. - macOS Keychain - Any Python script or application can access secrets created by ``keyring`` from that same Python executable without the operating system prompting the user for a password. To cause any specific secret to prompt for a password every time it is accessed, locate the credential using the ``Keychain Access`` application, and in the ``Access Control`` settings, remove ``Python`` from the list of allowed applications. - Freedesktop Secret Service - No analysis has been performed - KDE4 & KDE5 KWallet - No analysis has been performed - Windows Credential Locker - No analysis has been performed Making Releases =============== This project makes use of automated releases continuous integration. The simple workflow is to tag a commit and push it to Github. If it passes tests in CI, it will be automatically deployed to PyPI. Other things to consider when making a release: - Check that the changelog is current for the intended release. Running Tests ============= Tests are continuously run in Github Actions. To run the tests locally, install and invoke `tox `_. Background ========== The project was based on Tarek Ziade's idea in `this post`_. Kang Zhang initially carried it out as a `Google Summer of Code`_ project, and Tarek mentored Kang on this project. .. _this post: http://tarekziade.wordpress.com/2009/03/27/pycon-hallway-session-1-a-keyring-library-for-python/ .. _Google Summer of Code: http://socghop.appspot.com/ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/README.rst0000644000175100001710000004212200000000000014301 0ustar00runnerdocker.. image:: https://img.shields.io/pypi/v/keyring.svg :target: `PyPI link`_ .. image:: https://img.shields.io/pypi/pyversions/keyring.svg :target: `PyPI link`_ .. _PyPI link: https://pypi.org/project/keyring .. image:: https://github.com/jaraco/keyring/workflows/tests/badge.svg :target: https://github.com/jaraco/keyring/actions?query=workflow%3A%22tests%22 :alt: tests .. image:: https://img.shields.io/badge/code%20style-black-000000.svg :target: https://github.com/psf/black :alt: Code style: Black .. image:: https://readthedocs.org/projects/keyring/badge/?version=latest :target: https://keyring.readthedocs.io/en/latest/?badge=latest .. image:: https://img.shields.io/badge/skeleton-2021-informational :target: https://blog.jaraco.com/skeleton .. image:: https://tidelift.com/badges/package/pypi/keyring :target: https://tidelift.com/subscription/pkg/pypi-keyring?utm_source=pypi-keyring&utm_medium=readme .. image:: https://badges.gitter.im/jaraco/keyring.svg :alt: Join the chat at https://gitter.im/jaraco/keyring :target: https://gitter.im/jaraco/keyring?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge The Python keyring library provides an easy way to access the system keyring service from python. It can be used in any application that needs safe password storage. These recommended keyring backends are supported: * macOS `Keychain `_ * Freedesktop `Secret Service `_ supports many DE including GNOME (requires `secretstorage `_) * KDE4 & KDE5 `KWallet `_ (requires `dbus `_) * `Windows Credential Locker `_ Other keyring implementations are available through `Third-Party Backends`_. Installation - Linux ==================== On Linux, the KWallet backend relies on dbus-python_, which does not always install correctly when using pip (compilation is needed). For best results, install dbus-python as a system package. .. _dbus-python: https://gitlab.freedesktop.org/dbus/dbus-python Compatibility - macOS ===================== macOS keychain support macOS 11 (Big Sur) and later requires Python 3.8.7 or later with the "universal2" binary. See `#525 `_ for details. Using Keyring ============= The basic usage of keyring is pretty simple: just call ``keyring.set_password`` and ``keyring.get_password``:: >>> import keyring >>> keyring.set_password("system", "username", "password") >>> keyring.get_password("system", "username") 'password' Command-line Utility -------------------- Keyring supplies a ``keyring`` command which is installed with the package. After installing keyring in most environments, the command should be available for setting, getting, and deleting passwords. For more information on usage, invoke with no arguments or with ``--help`` as so:: $ keyring --help $ keyring set system username Password for 'username' in 'system': $ keyring get system username password The command-line functionality is also exposed as an executable package, suitable for invoking from Python like so:: $ python -m keyring --help $ python -m keyring set system username Password for 'username' in 'system': $ python -m keyring get system username password Configuring =========== The python keyring lib contains implementations for several backends. The library will attempt to automatically choose the most suitable backend for the current environment. Users may also specify the preferred keyring in a config file or by calling the ``set_keyring()`` function. Config file path ---------------- The configuration is stored in a file named "keyringrc.cfg" found in a platform-specific location. To determine where the config file is stored, run the following:: python -c "import keyring.util.platform_; print(keyring.util.platform_.config_root())" Some keyrings also store the keyring data in the file system. To determine where the data files are stored, run:: python -c "import keyring.util.platform_; print(keyring.util.platform_.data_root())" Config file content ------------------- To specify a keyring backend, set the **default-keyring** option to the full path of the class for that backend, such as ``keyring.backends.OS_X.Keyring``. If **keyring-path** is indicated, keyring will add that path to the Python module search path before loading the backend. For example, this config might be used to load the ``SimpleKeyring`` from the ``simplekeyring`` module in the ``./demo`` directory (not implemented):: [backend] default-keyring=simplekeyring.SimpleKeyring keyring-path=demo Third-Party Backends ==================== In addition to the backends provided by the core keyring package for the most common and secure use cases, there are additional keyring backend implementations available for other use-cases. Simply install them to make them available: - `keyrings.cryptfile `_ - Encrypted text file storage. - `keyring_jeepney `__ - a pure Python backend using the secret service DBus API for desktop Linux. - `keyrings.alt `_ - "alternate", possibly-insecure backends, originally part of the core package, but available for opt-in. - `gsheet-keyring `_ - a backend that stores secrets in a Google Sheet. For use with `ipython-secrets `_. - `bitwarden-keyring `_ - a backend that stores secrets in the `BitWarden `_ password manager. - `sagecipher `_ - an encryption backend which uses the ssh agent protocol's signature operation to derive the cipher key. - `keyrings.osx_keychain_keys `_ - OSX keychain key-management, for private, public and symmetric keys. Write your own keyring backend ============================== The interface for the backend is defined by ``keyring.backend.KeyringBackend``. Every backend should derive from that base class and define a ``priority`` attribute and three functions: ``get_password()``, ``set_password()``, and ``delete_password()``. The ``get_credential()`` function may be defined if desired. See the ``backend`` module for more detail on the interface of this class. Keyring employs entry points to allow any third-party package to implement backends without any modification to the keyring itself. Those interested in creating new backends are encouraged to create new, third-party packages in the ``keyrings`` namespace, in a manner modeled by the `keyrings.alt package `_. See the ``setup.cfg`` file in that project for a hints on how to create the requisite entry points. Backends that prove essential may be considered for inclusion in the core library, although the ease of installing these third-party packages should mean that extensions may be readily available. To create an extension for Keyring, please submit a pull request to have your extension mentioned as an available extension. Runtime Configuration ===================== Keyring additionally allows programmatic configuration of the backend calling the api ``set_keyring()``. The indicated backend will subsequently be used to store and retrieve passwords. To invoke ``set_keyring``:: # define a new keyring class which extends the KeyringBackend import keyring.backend class TestKeyring(keyring.backend.KeyringBackend): """A test keyring which always outputs same password """ priority = 1 def set_password(self, servicename, username, password): pass def get_password(self, servicename, username): return "password from TestKeyring" def delete_password(self, servicename, username): pass # set the keyring for keyring lib keyring.set_keyring(TestKeyring()) # invoke the keyring lib try: keyring.set_password("demo-service", "tarek", "passexample") print("password stored successfully") except keyring.errors.PasswordSetError: print("failed to store password") print("password", keyring.get_password("demo-service", "tarek")) Disabling Keyring ================= In many cases, uninstalling keyring will never be necessary. Especially on Windows and macOS, the behavior of keyring is usually degenerate, meaning it will return empty values to the caller, allowing the caller to fall back to some other behavior. In some cases, the default behavior of keyring is undesirable and it would be preferable to disable the keyring behavior altogether. There are several mechanisms to disable keyring: - Uninstall keyring. Most applications are tolerant to keyring not being installed. Uninstalling keyring should cause those applications to fall back to the behavior without keyring. This approach affects that Python environment where keyring would otherwise have been installed. - Configure the Null keyring in the environment. Set ``PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring`` in the environment, and the ``Null`` (degenerate) backend will be used. This approach affects all uses of Keyring where that variable is set. - Permanently configure the Null keyring for the user by running ``keyring --disable`` or ``python -m keyring --disable``. This approach affects all uses of keyring for that user. Altering Keyring Behavior ========================= Keyring provides a mechanism to alter the keyring's behavior through environment variables. Each backend implements a ``KeyringBackend.set_properties_from_env``, which when invoked will find all environment variables beginning with ``KEYRING_PROPERTY_{NAME}`` and will set a property for each ``{NAME.lower()}`` on the keyring. This method is invoked during initialization for the default/configured keyring. This mechanism may be used to set some useful values on various keyrings, including: - keychain; macOS, path to an alternate keychain file - appid; Linux/SecretService, alternate ID for the application Using Keyring on Ubuntu 16.04 ============================= The following is a complete transcript for installing keyring in a virtual environment on Ubuntu 16.04. No config file was used:: $ sudo apt install python3-venv libdbus-glib-1-dev $ cd /tmp $ pyvenv py3 $ source py3/bin/activate $ pip install -U pip $ pip install secretstorage dbus-python $ pip install keyring $ python >>> import keyring >>> keyring.get_keyring() >>> keyring.set_password("system", "username", "password") >>> keyring.get_password("system", "username") 'password' Using Keyring on headless Linux systems ======================================= It is possible to use the SecretService backend on Linux systems without X11 server available (only D-Bus is required). In this case: * Install the `GNOME Keyring`_ daemon. * Start a D-Bus session, e.g. run ``dbus-run-session -- sh`` and run the following commands inside that shell. * Run ``gnome-keyring-daemon`` with ``--unlock`` option. The description of that option says: Read a password from stdin, and use it to unlock the login keyring or create it if the login keyring does not exist. When that command is started, enter a password into stdin and press Ctrl+D (end of data). After that, the daemon will fork into background (use ``--foreground`` option to block). * Now you can use the SecretService backend of Keyring. Remember to run your application in the same D-Bus session as the daemon. .. _GNOME Keyring: https://wiki.gnome.org/Projects/GnomeKeyring Using Keyring on headless Linux systems in a Docker container ============================================================= It is possible to use keyring with the SecretService backend in Docker containers as well. All you need to do is install the necessary dependencies and add the `--privileged` flag to avoid any `Operation not permitted` errors when attempting to unlock the system's keyring. The following is a complete transcript for installing keyring on a Ubuntu 18:04 container:: docker run -it -d --privileged ubuntu:18.04 $ apt-get update $ apt install -y gnome-keyring python3-venv python3-dev $ python3 -m venv venv $ source venv/bin/activate # source a virtual environment to avoid polluting your system $ pip3 install --upgrade pip $ pip3 install keyring $ dbus-run-session -- sh # this will drop you into a new D-bus shell $ echo 'somecredstorepass' | gnome-keyring-daemon --unlock # unlock the system's keyring $ python >>> import keyring >>> keyring.get_keyring() >>> keyring.set_password("system", "username", "password") >>> keyring.get_password("system", "username") 'password' Integration =========== API --- The keyring lib has a few functions: * ``get_keyring()``: Return the currently-loaded keyring implementation. * ``get_password(service, username)``: Returns the password stored in the active keyring. If the password does not exist, it will return None. * ``get_credential(service, username)``: Return a credential object stored in the active keyring. This object contains at least ``username`` and ``password`` attributes for the specified service, where the returned ``username`` may be different from the argument. * ``set_password(service, username, password)``: Store the password in the keyring. * ``delete_password(service, username)``: Delete the password stored in keyring. If the password does not exist, it will raise an exception. In all cases, the parameters (``service``, ``username``, ``password``) should be Unicode text. Exceptions ---------- The keyring lib raises following exceptions: * ``keyring.errors.KeyringError``: Base Error class for all exceptions in keyring lib. * ``keyring.errors.InitError``: Raised when the keyring cannot be initialized. * ``keyring.errors.PasswordSetError``: Raised when password cannot be set in the keyring. * ``keyring.errors.PasswordDeleteError``: Raised when the password cannot be deleted in the keyring. Get Involved ============ Python keyring lib is an open community project and eagerly welcomes contributors. * Repository: https://github.com/jaraco/keyring/ * Bug Tracker: https://github.com/jaraco/keyring/issues/ * Mailing list: http://groups.google.com/group/python-keyring For Enterprise ============== Available as part of the Tidelift Subscription. This project and the maintainers of thousands of other packages are working with Tidelift to deliver one enterprise subscription that covers all of the open source you use. `Learn more `_. Security Contact ================ To report a security vulnerability, please use the `Tidelift security contact `_. Tidelift will coordinate the fix and disclosure. Security Considerations ======================= Each builtin backend may have security considerations to understand before using this library. Authors of tools or libraries utilizing ``keyring`` are encouraged to consider these concerns. As with any list of known security concerns, this list is not exhaustive. Additional issues can be added as needed. - macOS Keychain - Any Python script or application can access secrets created by ``keyring`` from that same Python executable without the operating system prompting the user for a password. To cause any specific secret to prompt for a password every time it is accessed, locate the credential using the ``Keychain Access`` application, and in the ``Access Control`` settings, remove ``Python`` from the list of allowed applications. - Freedesktop Secret Service - No analysis has been performed - KDE4 & KDE5 KWallet - No analysis has been performed - Windows Credential Locker - No analysis has been performed Making Releases =============== This project makes use of automated releases continuous integration. The simple workflow is to tag a commit and push it to Github. If it passes tests in CI, it will be automatically deployed to PyPI. Other things to consider when making a release: - Check that the changelog is current for the intended release. Running Tests ============= Tests are continuously run in Github Actions. To run the tests locally, install and invoke `tox `_. Background ========== The project was based on Tarek Ziade's idea in `this post`_. Kang Zhang initially carried it out as a `Google Summer of Code`_ project, and Tarek mentored Kang on this project. .. _this post: http://tarekziade.wordpress.com/2009/03/27/pycon-hallway-session-1-a-keyring-library-for-python/ .. _Google Summer of Code: http://socghop.appspot.com/ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/conftest.py0000644000175100001710000000075000000000000015012 0ustar00runnerdockerimport ctypes collect_ignore = ["hook-keyring.backend.py"] def macos_api_ignore(): """ Starting with macOS 11, the security API becomes non-viable except on universal2 binaries. Ref #525. """ try: ctypes.CDLL(ctypes.util.find_library('Security')).SecItemAdd return False except Exception: return True collect_ignore.extend(['keyring/backends/macOS/api.py'] * macos_api_ignore()) collect_ignore.append('keyring/devpi_client.py') ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1641083381.8138483 keyring-23.5.0/docs/0000755000175100001710000000000000000000000013541 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/docs/conf.py0000644000175100001710000000204500000000000015041 0ustar00runnerdocker#!/usr/bin/env python3 # -*- coding: utf-8 -*- # flake8: noqa extensions = ['sphinx.ext.autodoc', 'jaraco.packaging.sphinx', 'rst.linker'] master_doc = "index" link_files = { '../CHANGES.rst': dict( using=dict(GH='https://github.com'), replace=[ dict( pattern=r'(Issue #|\B#)(?P\d+)', url='{package_url}/issues/{issue}', ), dict( pattern=r'(?m:^((?Pv?\d+(\.\d+){1,2}))\n[-=]+\n)', with_scm='{text}\n{rev[timestamp]:%d %b %Y}\n', ), dict( pattern=r'PEP[- ](?P\d+)', url='https://www.python.org/dev/peps/pep-{pep_number:0>4}/', ), ], ) } # Be strict about any broken references: nitpicky = True # Include Python intersphinx mapping to prevent failures # jaraco/skeleton#51 extensions += ['sphinx.ext.intersphinx'] intersphinx_mapping = { 'python': ('https://docs.python.org/3', None), } extensions += ['jaraco.tidelift'] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/docs/history.rst0000644000175100001710000000012100000000000015766 0ustar00runnerdocker:tocdepth: 2 .. _changes: History ******* .. include:: ../CHANGES (links).rst ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/docs/index.rst0000644000175100001710000000115500000000000015404 0ustar00runnerdockerWelcome to |project| documentation! =================================== .. include:: ../README.rst .. toctree:: :maxdepth: 1 history .. tidelift-referral-banner:: .. automodule:: keyring :members: :undoc-members: :show-inheritance: .. automodule:: keyring.backend :members: :undoc-members: :show-inheritance: .. automodule:: keyring.core :members: :undoc-members: :show-inheritance: .. automodule:: keyring.credentials :members: :undoc-members: :show-inheritance: Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/docs/troubleshooting config.md0000644000175100001710000000302300000000000020536 0ustar00runnerdocker# Troubleshooting Config What do you do if you've set up your config file but ## Double-check location When keyring runs, it invokes init_backend which attempts to load a backend either as indicated by the environment, the config, or using heuristics [based on priorities](https://github.com/jaraco/keyring/blob/053e79bc101c45af2b86fb2c323bfb3e96a083cc/keyring/core.py#L93-L97). Assuming no config is detected from environment variables (it might be worth double-checking), when loading from config, it determines the filename by calling [`keyring.util._platform.config_root()`](https://github.com/jaraco/keyring/blob/053e79bc101c45af2b86fb2c323bfb3e96a083cc/keyring/core.py#L152), which as you can see varies by platform. Unless you're on Mac or Windows, the [Linux behavior is used](https://github.com/jaraco/keyring/blob/053e79bc101c45af2b86fb2c323bfb3e96a083cc/keyring/util/platform_.py#L53-L62). That setting also is dependent on the environment. If `XDG_CONFIG_HOME` is configured, that is where it will be looking for the config file. If not, it will look for it in `~/.local/share` (however that resolves using `os.expanduser`). The config file is always named "python_keyring". All of this is to say that there are many variables that go into detecting where the config file should be located. To directly detect where the config file is expected, in the Python environment in which your application is run, you should execute `keyring.util.platform_.config_root()`. That should definitively indicate where you should put the config file. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/hook-keyring.backend.py0000644000175100001710000000025500000000000017161 0ustar00runnerdocker# Used by pyinstaller to expose hidden imports import importlib_metadata as metadata hiddenimports = [ep.value for ep in metadata.entry_points(group='keyring.backends')] ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1641083381.8138483 keyring-23.5.0/keyring/0000755000175100001710000000000000000000000014261 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/keyring/__init__.py0000644000175100001710000000041700000000000016374 0ustar00runnerdockerfrom .core import ( set_keyring, get_keyring, set_password, get_password, delete_password, get_credential, ) __all__ = ( 'set_keyring', 'get_keyring', 'set_password', 'get_password', 'delete_password', 'get_credential', ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/keyring/__main__.py0000644000175100001710000000010700000000000016351 0ustar00runnerdockerif __name__ == '__main__': from keyring import cli cli.main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/keyring/backend.py0000644000175100001710000001435000000000000016225 0ustar00runnerdocker""" Keyring implementation support """ import os import abc import logging import operator from typing import Optional import importlib_metadata as metadata from . import credentials, errors, util from .util import properties log = logging.getLogger(__name__) by_priority = operator.attrgetter('priority') _limit = None class KeyringBackendMeta(abc.ABCMeta): """ A metaclass that's both an ABCMeta and a type that keeps a registry of all (non-abstract) types. """ def __init__(cls, name, bases, dict): super().__init__(name, bases, dict) if not hasattr(cls, '_classes'): cls._classes = set() classes = cls._classes if not cls.__abstractmethods__: classes.add(cls) class KeyringBackend(metaclass=KeyringBackendMeta): """The abstract base class of the keyring, every backend must implement this interface. """ def __init__(self): self.set_properties_from_env() # @abc.abstractproperty def priority(cls): """ Each backend class must supply a priority, a number (float or integer) indicating the priority of the backend relative to all other backends. The priority need not be static -- it may (and should) vary based attributes of the environment in which is runs (platform, available packages, etc.). A higher number indicates a higher priority. The priority should raise a RuntimeError with a message indicating the underlying cause if the backend is not suitable for the current environment. As a rule of thumb, a priority between zero but less than one is suitable, but a priority of one or greater is recommended. """ @properties.ClassProperty @classmethod def viable(cls): with errors.ExceptionRaisedContext() as exc: cls.priority return not exc @classmethod def get_viable_backends(cls): """ Return all subclasses deemed viable. """ return filter(operator.attrgetter('viable'), cls._classes) @properties.ClassProperty @classmethod def name(cls): """ The keyring name, suitable for display. The name is derived from module and class name. """ parent, sep, mod_name = cls.__module__.rpartition('.') mod_name = mod_name.replace('_', ' ') return ' '.join([mod_name, cls.__name__]) def __str__(self): keyring_class = type(self) return "{}.{} (priority: {:g})".format( keyring_class.__module__, keyring_class.__name__, keyring_class.priority ) @abc.abstractmethod def get_password(self, service: str, username: str) -> Optional[str]: """Get password of the username for the service""" return None @abc.abstractmethod def set_password(self, service: str, username: str, password: str) -> None: """Set password for the username of the service. If the backend cannot store passwords, raise PasswordSetError. """ raise errors.PasswordSetError("reason") # for backward-compatibility, don't require a backend to implement # delete_password # @abc.abstractmethod def delete_password(self, service: str, username: str) -> None: """Delete the password for the username of the service. If the backend cannot delete passwords, raise PasswordDeleteError. """ raise errors.PasswordDeleteError("reason") # for backward-compatibility, don't require a backend to implement # get_credential # @abc.abstractmethod def get_credential( self, service: str, username: Optional[str], ) -> Optional[credentials.Credential]: """Gets the username and password for the service. Returns a Credential instance. The *username* argument is optional and may be omitted by the caller or ignored by the backend. Callers must use the returned username. """ # The default implementation requires a username here. if username is not None: password = self.get_password(service, username) if password is not None: return credentials.SimpleCredential(username, password) return None def set_properties_from_env(self): """For all KEYRING_PROPERTY_* env var, set that property.""" def parse(item): key, value = item pre, sep, name = key.partition('KEYRING_PROPERTY_') return sep and (name.lower(), value) props = filter(None, map(parse, os.environ.items())) for name, value in props: setattr(self, name, value) class Crypter: """Base class providing encryption and decryption""" @abc.abstractmethod def encrypt(self, value): """Encrypt the value.""" pass @abc.abstractmethod def decrypt(self, value): """Decrypt the value.""" pass class NullCrypter(Crypter): """A crypter that does nothing""" def encrypt(self, value): return value def decrypt(self, value): return value def _load_plugins(): """ Locate all setuptools entry points by the name 'keyring backends' and initialize them. Any third-party library may register an entry point by adding the following to their setup.cfg:: [options.entry_points] keyring.backends = plugin_name = mylib.mymodule:initialize_func `plugin_name` can be anything, and is only used to display the name of the plugin at initialization time. `initialize_func` is optional, but will be invoked if callable. """ for ep in metadata.entry_points(group='keyring.backends'): try: log.debug('Loading %s', ep.name) init_func = ep.load() if callable(init_func): init_func() except Exception: log.exception(f"Error initializing plugin {ep}.") @util.once def get_all_keyring(): """ Return a list of all implemented keyrings that can be constructed without parameters. """ _load_plugins() viable_classes = KeyringBackend.get_viable_backends() rings = util.suppress_exceptions(viable_classes, exceptions=TypeError) return list(rings) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1641083381.8138483 keyring-23.5.0/keyring/backends/0000755000175100001710000000000000000000000016033 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/keyring/backends/OS_X.py0000644000175100001710000000033600000000000017217 0ustar00runnerdocker""" Backward-compatibility shim for users referencing the module by name. Ref #487. """ import warnings from .macOS import Keyring __all__ = ['Keyring'] warnings.warn("OS_X module is deprecated.", DeprecationWarning) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/keyring/backends/SecretService.py0000644000175100001710000001120300000000000021150 0ustar00runnerdockerfrom contextlib import closing import logging from ..util import properties from ..backend import KeyringBackend from ..credentials import SimpleCredential from ..errors import ( InitError, PasswordDeleteError, ExceptionRaisedContext, KeyringLocked, ) try: import secretstorage import secretstorage.exceptions as exceptions except ImportError: pass except AttributeError: # See https://github.com/jaraco/keyring/issues/296 pass log = logging.getLogger(__name__) class Keyring(KeyringBackend): """Secret Service Keyring""" appid = 'Python keyring library' @properties.ClassProperty @classmethod def priority(cls): with ExceptionRaisedContext() as exc: secretstorage.__name__ if exc: raise RuntimeError("SecretStorage required") if secretstorage.__version_tuple__ < (3, 2): raise RuntimeError("SecretStorage 3.2 or newer required") try: with closing(secretstorage.dbus_init()) as connection: if not secretstorage.check_service_availability(connection): raise RuntimeError( "The Secret Service daemon is neither running nor " "activatable through D-Bus" ) except exceptions.SecretStorageException as e: raise RuntimeError("Unable to initialize SecretService: %s" % e) return 5 def get_preferred_collection(self): """If self.preferred_collection contains a D-Bus path, the collection at that address is returned. Otherwise, the default collection is returned. """ bus = secretstorage.dbus_init() try: if hasattr(self, 'preferred_collection'): collection = secretstorage.Collection(bus, self.preferred_collection) else: collection = secretstorage.get_default_collection(bus) except exceptions.SecretStorageException as e: raise InitError("Failed to create the collection: %s." % e) if collection.is_locked(): collection.unlock() if collection.is_locked(): # User dismissed the prompt raise KeyringLocked("Failed to unlock the collection!") return collection def unlock(self, item): if hasattr(item, 'unlock'): item.unlock() if item.is_locked(): # User dismissed the prompt raise KeyringLocked('Failed to unlock the item!') def get_password(self, service, username): """Get password of the username for the service""" collection = self.get_preferred_collection() with closing(collection.connection): items = collection.search_items({"username": username, "service": service}) for item in items: self.unlock(item) return item.get_secret().decode('utf-8') def set_password(self, service, username, password): """Set password for the username of the service""" collection = self.get_preferred_collection() attributes = { "application": self.appid, "service": service, "username": username, } label = "Password for '{}' on '{}'".format(username, service) with closing(collection.connection): collection.create_item(label, attributes, password, replace=True) def delete_password(self, service, username): """Delete the stored password (only the first one)""" collection = self.get_preferred_collection() with closing(collection.connection): items = collection.search_items({"username": username, "service": service}) for item in items: return item.delete() raise PasswordDeleteError("No such password!") def get_credential(self, service, username): """Gets the first username and password for a service. Returns a Credential instance The username can be omitted, but if there is one, it will use get_password and return a SimpleCredential containing the username and password Otherwise, it will return the first username and password combo that it finds. """ query = {"service": service} if username: query["username"] = username collection = self.get_preferred_collection() with closing(collection.connection): items = collection.search_items(query) for item in items: self.unlock(item) username = item.get_attributes().get("username") return SimpleCredential(username, item.get_secret().decode('utf-8')) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/keyring/backends/Windows.py0000644000175100001710000001327500000000000020047 0ustar00runnerdockerimport logging from ..util import properties from ..backend import KeyringBackend from ..credentials import SimpleCredential from ..errors import PasswordDeleteError, ExceptionRaisedContext with ExceptionRaisedContext() as missing_deps: try: # prefer pywin32-ctypes from win32ctypes.pywin32 import pywintypes from win32ctypes.pywin32 import win32cred # force demand import to raise ImportError win32cred.__name__ except ImportError: # fallback to pywin32 import pywintypes import win32cred # force demand import to raise ImportError win32cred.__name__ log = logging.getLogger(__name__) class Persistence: def __get__(self, keyring, type=None): return getattr(keyring, '_persist', win32cred.CRED_PERSIST_ENTERPRISE) def __set__(self, keyring, value): """ Set the persistence value on the Keyring. Value may be one of the win32cred.CRED_PERSIST_* constants or a string representing one of those constants. For example, 'local machine' or 'session'. """ if isinstance(value, str): attr = 'CRED_PERSIST_' + value.replace(' ', '_').upper() value = getattr(win32cred, attr) setattr(keyring, '_persist', value) class DecodingCredential(dict): @property def value(self): """ Attempt to decode the credential blob as UTF-16 then UTF-8. """ cred = self['CredentialBlob'] try: return cred.decode('utf-16') except UnicodeDecodeError: decoded_cred_utf8 = cred.decode('utf-8') log.warning( "Retrieved an UTF-8 encoded credential. Please be aware that " "this library only writes credentials in UTF-16." ) return decoded_cred_utf8 class WinVaultKeyring(KeyringBackend): """ WinVaultKeyring stores encrypted passwords using the Windows Credential Manager. Requires pywin32 This backend does some gymnastics to simulate multi-user support, which WinVault doesn't support natively. See https://github.com/jaraco/keyring/issues/47#issuecomment-75763152 for details on the implementation, but here's the gist: Passwords are stored under the service name unless there is a collision (another password with the same service name but different user name), in which case the previous password is moved into a compound name: {username}@{service} """ persist = Persistence() @properties.ClassProperty @classmethod def priority(cls): """ If available, the preferred backend on Windows. """ if missing_deps: raise RuntimeError("Requires Windows and pywin32") return 5 @staticmethod def _compound_name(username, service): return f'{username}@{service}' def get_password(self, service, username): # first attempt to get the password under the service name res = self._get_password(service) if not res or res['UserName'] != username: # It wasn't found so attempt to get it with the compound name res = self._get_password(self._compound_name(username, service)) if not res: return None return res.value def _get_password(self, target): try: res = win32cred.CredRead( Type=win32cred.CRED_TYPE_GENERIC, TargetName=target ) except pywintypes.error as e: if e.winerror == 1168 and e.funcname == 'CredRead': # not found return None raise return DecodingCredential(res) def set_password(self, service, username, password): existing_pw = self._get_password(service) if existing_pw: # resave the existing password using a compound target existing_username = existing_pw['UserName'] target = self._compound_name(existing_username, service) self._set_password( target, existing_username, existing_pw.value, ) self._set_password(service, username, str(password)) def _set_password(self, target, username, password): credential = dict( Type=win32cred.CRED_TYPE_GENERIC, TargetName=target, UserName=username, CredentialBlob=password, Comment="Stored using python-keyring", Persist=self.persist, ) win32cred.CredWrite(credential, 0) def delete_password(self, service, username): compound = self._compound_name(username, service) deleted = False for target in service, compound: existing_pw = self._get_password(target) if existing_pw and existing_pw['UserName'] == username: deleted = True self._delete_password(target) if not deleted: raise PasswordDeleteError(service) def _delete_password(self, target): try: win32cred.CredDelete(Type=win32cred.CRED_TYPE_GENERIC, TargetName=target) except pywintypes.error as e: if e.winerror == 1168 and e.funcname == 'CredDelete': # not found return raise def get_credential(self, service, username): res = None # get the credentials associated with the provided username if username: res = self._get_password(self._compound_name(username, service)) # get any first password under the service name if not res: res = self._get_password(service) if not res: return None return SimpleCredential(res['UserName'], res.value) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/keyring/backends/__init__.py0000644000175100001710000000000000000000000020132 0ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/keyring/backends/chainer.py0000644000175100001710000000422600000000000020022 0ustar00runnerdocker""" Keyring Chainer - iterates over other viable backends to discover passwords in each. """ from .. import backend from ..util import properties from . import fail class ChainerBackend(backend.KeyringBackend): """ >>> ChainerBackend() """ # override viability as 'priority' cannot be determined # until other backends have been constructed viable = True @properties.ClassProperty @classmethod def priority(cls): """ If there are backends to chain, high priority Otherwise very low priority since our operation when empty is the same as null. """ return 10 if len(cls.backends) > 1 else (fail.Keyring.priority - 1) @properties.ClassProperty @classmethod def backends(cls): """ Discover all keyrings for chaining. """ def allow(keyring): limit = backend._limit or bool return ( not isinstance(keyring, ChainerBackend) and limit(keyring) and keyring.priority > 0 ) allowed = filter(allow, backend.get_all_keyring()) return sorted(allowed, key=backend.by_priority, reverse=True) def get_password(self, service, username): for keyring in self.backends: password = keyring.get_password(service, username) if password is not None: return password def set_password(self, service, username, password): for keyring in self.backends: try: return keyring.set_password(service, username, password) except NotImplementedError: pass def delete_password(self, service, username): for keyring in self.backends: try: return keyring.delete_password(service, username) except NotImplementedError: pass def get_credential(self, service, username): for keyring in self.backends: credential = keyring.get_credential(service, username) if credential is not None: return credential ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/keyring/backends/fail.py0000644000175100001710000000150400000000000017320 0ustar00runnerdockerfrom ..backend import KeyringBackend from ..errors import NoKeyringError class Keyring(KeyringBackend): """ Keyring that raises error on every operation. >>> kr = Keyring() >>> kr.get_password('svc', 'user') Traceback (most recent call last): ... keyring.errors.NoKeyringError: ...No recommended backend... """ priority = 0 def get_password(self, service, username, password=None): msg = ( "No recommended backend was available. Install a recommended 3rd " "party backend package; or, install the keyrings.alt package if " "you want to use the non-recommended backends. See " "https://pypi.org/project/keyring for details." ) raise NoKeyringError(msg) set_password = delete_password = get_password # type: ignore ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/keyring/backends/kwallet.py0000644000175100001710000001333400000000000020054 0ustar00runnerdockerimport sys import os import contextlib from ..backend import KeyringBackend from ..credentials import SimpleCredential from ..errors import PasswordDeleteError from ..errors import PasswordSetError, InitError, KeyringLocked from ..util import properties try: import dbus from dbus.mainloop.glib import DBusGMainLoop except ImportError: pass except AttributeError: # See https://github.com/jaraco/keyring/issues/296 pass def _id_from_argv(): """ Safely infer an app id from sys.argv. """ allowed = AttributeError, IndexError, TypeError with contextlib.suppress(allowed): return sys.argv[0] class DBusKeyring(KeyringBackend): """ KDE KWallet 5 via D-Bus """ appid = _id_from_argv() or 'Python keyring library' wallet = None bus_name = 'org.kde.kwalletd5' object_path = '/modules/kwalletd5' @properties.ClassProperty @classmethod def priority(cls): if 'dbus' not in globals(): raise RuntimeError('python-dbus not installed') try: bus = dbus.SessionBus(mainloop=DBusGMainLoop()) except dbus.DBusException as exc: raise RuntimeError(exc.get_dbus_message()) if not ( bus.name_has_owner(cls.bus_name) or cls.bus_name in bus.list_activatable_names() ): raise RuntimeError( "The KWallet daemon is neither running nor activatable through D-Bus" ) if "KDE" in os.getenv("XDG_CURRENT_DESKTOP", "").split(":"): return 5.1 return 4.9 def __init__(self, *arg, **kw): super().__init__(*arg, **kw) self.handle = -1 def _migrate(self, service): old_folder = 'Python' entry_list = [] if self.iface.hasFolder(self.handle, old_folder, self.appid): entry_list = self.iface.readPasswordList( self.handle, old_folder, '*@*', self.appid ) for entry in entry_list.items(): key = entry[0] password = entry[1] username, service = key.rsplit('@', 1) ret = self.iface.writePassword( self.handle, service, username, password, self.appid ) if ret == 0: self.iface.removeEntry(self.handle, old_folder, key, self.appid) entry_list = self.iface.readPasswordList( self.handle, old_folder, '*', self.appid ) if not entry_list: self.iface.removeFolder(self.handle, old_folder, self.appid) def connected(self, service): if self.handle >= 0: if self.iface.isOpen(self.handle): return True bus = dbus.SessionBus(mainloop=DBusGMainLoop()) wId = 0 try: remote_obj = bus.get_object(self.bus_name, self.object_path) self.iface = dbus.Interface(remote_obj, 'org.kde.KWallet') self.handle = self.iface.open(self.iface.networkWallet(), wId, self.appid) except dbus.DBusException as e: raise InitError('Failed to open keyring: %s.' % e) if self.handle < 0: return False self._migrate(service) return True def get_password(self, service, username): """Get password of the username for the service""" if not self.connected(service): # the user pressed "cancel" when prompted to unlock their keyring. raise KeyringLocked("Failed to unlock the keyring!") if not self.iface.hasEntry(self.handle, service, username, self.appid): return None password = self.iface.readPassword(self.handle, service, username, self.appid) return str(password) def get_credential(self, service, username): """Gets the first username and password for a service. Returns a Credential instance The username can be omitted, but if there is one, it will forward to get_password. Otherwise, it will return the first username and password combo that it finds. """ if username is not None: return super().get_credential(service, username) if not self.connected(service): # the user pressed "cancel" when prompted to unlock their keyring. raise KeyringLocked("Failed to unlock the keyring!") for username in self.iface.entryList(self.handle, service, self.appid): password = self.iface.readPassword( self.handle, service, username, self.appid ) return SimpleCredential(str(username), str(password)) def set_password(self, service, username, password): """Set password for the username of the service""" if not self.connected(service): # the user pressed "cancel" when prompted to unlock their keyring. raise PasswordSetError("Cancelled by user") self.iface.writePassword(self.handle, service, username, password, self.appid) def delete_password(self, service, username): """Delete the password for the username of the service.""" if not self.connected(service): # the user pressed "cancel" when prompted to unlock their keyring. raise PasswordDeleteError("Cancelled by user") if not self.iface.hasEntry(self.handle, service, username, self.appid): raise PasswordDeleteError("Password not found") self.iface.removeEntry(self.handle, service, username, self.appid) class DBusKeyringKWallet4(DBusKeyring): """ KDE KWallet 4 via D-Bus """ bus_name = 'org.kde.kwalletd' object_path = '/modules/kwalletd' @properties.ClassProperty @classmethod def priority(cls): return super().priority - 1 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/keyring/backends/libsecret.py0000644000175100001710000001345700000000000020373 0ustar00runnerdockerimport logging from ..util import properties from ..backend import KeyringBackend from ..credentials import SimpleCredential from ..errors import ( PasswordDeleteError, PasswordSetError, ExceptionRaisedContext, KeyringLocked, ) available = False try: import gi from gi.repository import Gio from gi.repository import GLib gi.require_version('Secret', '1') from gi.repository import Secret available = True except (AttributeError, ImportError, ValueError): pass log = logging.getLogger(__name__) class Keyring(KeyringBackend): """libsecret Keyring""" appid = 'Python keyring library' if available: schema = Secret.Schema.new( "org.freedesktop.Secret.Generic", Secret.SchemaFlags.NONE, { "application": Secret.SchemaAttributeType.STRING, "service": Secret.SchemaAttributeType.STRING, "username": Secret.SchemaAttributeType.STRING, }, ) collection = Secret.COLLECTION_DEFAULT @properties.ClassProperty @classmethod def priority(cls): with ExceptionRaisedContext() as exc: Secret.__name__ if exc: raise RuntimeError("libsecret required") return 4.8 def get_password(self, service, username): """Get password of the username for the service""" attributes = { "application": self.appid, "service": service, "username": username, } try: items = Secret.password_search_sync( self.schema, attributes, Secret.SearchFlags.UNLOCK, None ) except GLib.Error as error: quark = GLib.quark_try_string('g-io-error-quark') if error.matches(quark, Gio.IOErrorEnum.FAILED): raise KeyringLocked('Failed to unlock the item!') from error raise for item in items: try: return item.retrieve_secret_sync().get_text() except GLib.Error as error: quark = GLib.quark_try_string('secret-error') if error.matches(quark, Secret.Error.IS_LOCKED): raise KeyringLocked('Failed to unlock the item!') from error raise def set_password(self, service, username, password): """Set password for the username of the service""" attributes = { "application": self.appid, "service": service, "username": username, } label = "Password for '{}' on '{}'".format(username, service) try: stored = Secret.password_store_sync( self.schema, attributes, self.collection, label, password, None ) except GLib.Error as error: quark = GLib.quark_try_string('secret-error') if error.matches(quark, Secret.Error.IS_LOCKED): raise KeyringLocked("Failed to unlock the collection!") from error quark = GLib.quark_try_string('g-io-error-quark') if error.matches(quark, Gio.IOErrorEnum.FAILED): raise KeyringLocked("Failed to unlock the collection!") from error raise if not stored: raise PasswordSetError("Failed to store password!") def delete_password(self, service, username): """Delete the stored password (only the first one)""" attributes = { "application": self.appid, "service": service, "username": username, } try: items = Secret.password_search_sync( self.schema, attributes, Secret.SearchFlags.UNLOCK, None ) except GLib.Error as error: quark = GLib.quark_try_string('g-io-error-quark') if error.matches(quark, Gio.IOErrorEnum.FAILED): raise KeyringLocked('Failed to unlock the item!') from error raise for item in items: try: removed = Secret.password_clear_sync( self.schema, item.get_attributes(), None ) except GLib.Error as error: quark = GLib.quark_try_string('secret-error') if error.matches(quark, Secret.Error.IS_LOCKED): raise KeyringLocked('Failed to unlock the item!') from error raise return removed raise PasswordDeleteError("No such password!") def get_credential(self, service, username): """Get the first username and password for a service. Return a Credential instance The username can be omitted, but if there is one, it will use get_password and return a SimpleCredential containing the username and password Otherwise, it will return the first username and password combo that it finds. """ query = {"service": service} if username: query["username"] = username try: items = Secret.password_search_sync( self.schema, query, Secret.SearchFlags.UNLOCK, None ) except GLib.Error as error: quark = GLib.quark_try_string('g-io-error-quark') if error.matches(quark, Gio.IOErrorEnum.FAILED): raise KeyringLocked('Failed to unlock the item!') from error raise for item in items: username = item.get_attributes().get("username") try: return SimpleCredential( username, item.retrieve_secret_sync().get_text() ) except GLib.Error as error: quark = GLib.quark_try_string('secret-error') if error.matches(quark, Secret.Error.IS_LOCKED): raise KeyringLocked('Failed to unlock the item!') from error raise ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1641083381.8138483 keyring-23.5.0/keyring/backends/macOS/0000755000175100001710000000000000000000000017035 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/keyring/backends/macOS/__init__.py0000644000175100001710000000405000000000000021145 0ustar00runnerdockerimport platform import os from ...backend import KeyringBackend from ...errors import PasswordSetError from ...errors import PasswordDeleteError from ...errors import KeyringLocked from ...errors import KeyringError from ...util import properties try: from . import api except Exception: pass class Keyring(KeyringBackend): """macOS Keychain""" keychain = os.environ.get('KEYCHAIN_PATH') "Path to keychain file, overriding default" @properties.ClassProperty @classmethod def priority(cls): """ Preferred for all macOS environments. """ if platform.system() != 'Darwin': raise RuntimeError("macOS required") if 'api' not in globals(): raise RuntimeError("Security API unavailable") return 5 def set_password(self, service, username, password): if username is None: username = '' try: api.set_generic_password(self.keychain, service, username, password) except api.KeychainDenied as e: raise KeyringLocked("Can't store password on keychain: " "{}".format(e)) except api.Error as e: raise PasswordSetError("Can't store password on keychain: " "{}".format(e)) def get_password(self, service, username): if username is None: username = '' try: return api.find_generic_password(self.keychain, service, username) except api.NotFound: pass except api.KeychainDenied as e: raise KeyringLocked("Can't get password from keychain: " "{}".format(e)) except api.Error as e: raise KeyringError("Can't get password from keychain: " "{}".format(e)) def delete_password(self, service, username): if username is None: username = '' try: return api.delete_generic_password(self.keychain, service, username) except api.Error as e: raise PasswordDeleteError( "Can't delete password in keychain: " "{}".format(e) ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/keyring/backends/macOS/api.py0000644000175100001710000001040500000000000020160 0ustar00runnerdockerimport ctypes from ctypes import ( c_void_p, c_uint32, c_int32, byref, ) from ctypes.util import find_library OS_status = c_int32 class error: item_not_found = -25300 keychain_denied = -128 sec_auth_failed = -25293 plist_missing = -67030 sec_interaction_not_allowed = -25308 _sec = ctypes.CDLL(find_library('Security')) _core = ctypes.CDLL(find_library('CoreServices')) _found = ctypes.CDLL(find_library('Foundation')) CFDictionaryCreate = _found.CFDictionaryCreate CFDictionaryCreate.restype = c_void_p CFDictionaryCreate.argtypes = ( c_void_p, c_void_p, c_void_p, c_int32, c_void_p, c_void_p, ) CFStringCreateWithCString = _found.CFStringCreateWithCString CFStringCreateWithCString.restype = c_void_p CFStringCreateWithCString.argtypes = [c_void_p, c_void_p, c_uint32] CFNumberCreate = _found.CFNumberCreate CFNumberCreate.restype = c_void_p CFNumberCreate.argtypes = [c_void_p, c_uint32, ctypes.c_void_p] SecItemAdd = _sec.SecItemAdd SecItemAdd.restype = OS_status SecItemAdd.argtypes = (c_void_p, c_void_p) SecItemCopyMatching = _sec.SecItemCopyMatching SecItemCopyMatching.restype = OS_status SecItemCopyMatching.argtypes = (c_void_p, c_void_p) SecItemDelete = _sec.SecItemDelete SecItemDelete.restype = OS_status SecItemDelete.argtypes = (c_void_p,) CFDataGetBytePtr = _found.CFDataGetBytePtr CFDataGetBytePtr.restype = c_void_p CFDataGetBytePtr.argtypes = (c_void_p,) CFDataGetLength = _found.CFDataGetLength CFDataGetLength.restype = c_int32 CFDataGetLength.argtypes = (c_void_p,) def k_(s): return c_void_p.in_dll(_sec, s) def create_cfbool(b): return CFNumberCreate(None, 0x9, ctypes.byref(c_int32(1 if b else 0))) # int32 def create_cfstr(s): return CFStringCreateWithCString( None, s.encode('utf8'), 0x08000100 ) # kCFStringEncodingUTF8 def create_query(**kwargs): return CFDictionaryCreate( None, (c_void_p * len(kwargs))(*[k_(k) for k in kwargs.keys()]), (c_void_p * len(kwargs))( *[create_cfstr(v) if isinstance(v, str) else v for v in kwargs.values()] ), len(kwargs), _found.kCFTypeDictionaryKeyCallBacks, _found.kCFTypeDictionaryValueCallBacks, ) def cfstr_to_str(data): return ctypes.string_at(CFDataGetBytePtr(data), CFDataGetLength(data)).decode( 'utf-8' ) class Error(Exception): @classmethod def raise_for_status(cls, status): if status == 0: return if status == error.item_not_found: raise NotFound(status, "Item not found") if status == error.keychain_denied: raise KeychainDenied(status, "Keychain Access Denied") if status == error.sec_auth_failed or status == error.plist_missing: raise SecAuthFailure( status, "Security Auth Failure: make sure " "python is signed with codesign util", ) raise cls(status, "Unknown Error") class NotFound(Error): pass class KeychainDenied(Error): pass class SecAuthFailure(Error): pass def find_generic_password(kc_name, service, username, not_found_ok=False): q = create_query( kSecClass=k_('kSecClassGenericPassword'), kSecMatchLimit=k_('kSecMatchLimitOne'), kSecAttrService=service, kSecAttrAccount=username, kSecReturnData=create_cfbool(True), ) data = c_void_p() status = SecItemCopyMatching(q, byref(data)) if status == error.item_not_found and not_found_ok: return Error.raise_for_status(status) return cfstr_to_str(data) def set_generic_password(name, service, username, password): if find_generic_password(name, service, username, not_found_ok=True): delete_generic_password(name, service, username) q = create_query( kSecClass=k_('kSecClassGenericPassword'), kSecAttrService=service, kSecAttrAccount=username, kSecValueData=password, ) status = SecItemAdd(q, None) Error.raise_for_status(status) def delete_generic_password(name, service, username): q = create_query( kSecClass=k_('kSecClassGenericPassword'), kSecAttrService=service, kSecAttrAccount=username, ) status = SecItemDelete(q) Error.raise_for_status(status) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/keyring/backends/null.py0000644000175100001710000000055000000000000017357 0ustar00runnerdockerfrom ..backend import KeyringBackend class Keyring(KeyringBackend): """ Keyring that return None on every operation. >>> kr = Keyring() >>> kr.get_password('svc', 'user') """ priority = -1 def get_password(self, service, username, password=None): pass set_password = delete_password = get_password # type: ignore ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/keyring/cli.py0000755000175100001710000000752400000000000015415 0ustar00runnerdocker#!/usr/bin/env python """Simple command line interface to get/set password from a keyring""" import getpass import argparse import sys from . import core from . import backend from . import set_keyring, get_password, set_password, delete_password class CommandLineTool: def __init__(self): self.parser = argparse.ArgumentParser() self.parser.add_argument( "-p", "--keyring-path", dest="keyring_path", default=None, help="Path to the keyring backend", ) self.parser.add_argument( "-b", "--keyring-backend", dest="keyring_backend", default=None, help="Name of the keyring backend", ) self.parser.add_argument( "--list-backends", action="store_true", help="List keyring backends and exit", ) self.parser.add_argument( "--disable", action="store_true", help="Disable keyring and exit" ) self.parser.add_argument( 'operation', help="get|set|del", nargs="?", ) self.parser.add_argument( 'service', nargs="?", ) self.parser.add_argument( 'username', nargs="?", ) def run(self, argv): args = self.parser.parse_args(argv) vars(self).update(vars(args)) if args.list_backends: for k in backend.get_all_keyring(): print(k) return if args.disable: core.disable() return self._check_args() self._load_spec_backend() method = getattr(self, f'do_{self.operation}', self.invalid_op) return method() def _check_args(self): if self.operation: if self.service is None or self.username is None: self.parser.error(f"{self.operation} requires service and username") def do_get(self): password = get_password(self.service, self.username) if password is None: raise SystemExit(1) print(password) def do_set(self): password = self.input_password( f"Password for '{self.username}' in '{self.service}': " ) set_password(self.service, self.username, password) def do_del(self): delete_password(self.service, self.username) def invalid_op(self): self.parser.error("Specify operation 'get', 'del', or 'set'.") def _load_spec_backend(self): if self.keyring_backend is None: return try: if self.keyring_path: sys.path.insert(0, self.keyring_path) set_keyring(core.load_keyring(self.keyring_backend)) except (Exception,) as exc: # Tons of things can go wrong here: # ImportError when using "fjkljfljkl" # AttributeError when using "os.path.bar" # TypeError when using "__builtins__.str" # So, we play on the safe side, and catch everything. self.parser.error(f"Unable to load specified keyring: {exc}") def input_password(self, prompt): """Retrieve password from input.""" return self.pass_from_pipe() or getpass.getpass(prompt) @classmethod def pass_from_pipe(cls): """Return password from pipe if not on TTY, else False.""" is_pipe = not sys.stdin.isatty() return is_pipe and cls.strip_last_newline(sys.stdin.read()) @staticmethod def strip_last_newline(str): """Strip one last newline, if present.""" return str[: -str.endswith('\n')] def main(argv=None): """Main command line interface.""" if argv is None: argv = sys.argv[1:] cli = CommandLineTool() return cli.run(argv) if __name__ == '__main__': sys.exit(main()) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/keyring/core.py0000644000175100001710000001213700000000000015567 0ustar00runnerdocker""" Core API functions and initialization routines. """ import configparser import os import sys import logging import typing from . import backend, credentials from .util import platform_ as platform from .backends import fail log = logging.getLogger(__name__) _keyring_backend = None def set_keyring(keyring): """Set current keyring backend.""" global _keyring_backend if not isinstance(keyring, backend.KeyringBackend): raise TypeError("The keyring must be an instance of KeyringBackend") _keyring_backend = keyring def get_keyring() -> backend.KeyringBackend: """Get current keyring backend.""" if _keyring_backend is None: init_backend() return typing.cast(backend.KeyringBackend, _keyring_backend) def disable(): """ Configure the null keyring as the default. """ root = platform.config_root() try: os.makedirs(root) except OSError: pass filename = os.path.join(root, 'keyringrc.cfg') if os.path.exists(filename): msg = f"Refusing to overwrite {filename}" raise RuntimeError(msg) with open(filename, 'w') as file: file.write('[backend]\ndefault-keyring=keyring.backends.null.Keyring') def get_password(service_name: str, username: str) -> typing.Optional[str]: """Get password from the specified service.""" return get_keyring().get_password(service_name, username) def set_password(service_name: str, username: str, password: str) -> None: """Set password for the user in the specified service.""" get_keyring().set_password(service_name, username, password) def delete_password(service_name: str, username: str) -> None: """Delete the password for the user in the specified service.""" get_keyring().delete_password(service_name, username) def get_credential( service_name: str, username: typing.Optional[str] ) -> typing.Optional[credentials.Credential]: """Get a Credential for the specified service.""" return get_keyring().get_credential(service_name, username) def recommended(backend): return backend.priority >= 1 def init_backend(limit=None): """ Load a detected backend. """ set_keyring(_detect_backend(limit)) def _detect_backend(limit=None): """ Return a keyring specified in the config file or infer the best available. Limit, if supplied, should be a callable taking a backend and returning True if that backend should be included for consideration. """ # save the limit for the chainer to honor backend._limit = limit return ( load_env() or load_config() or max( # all keyrings passing the limit filter filter(limit, backend.get_all_keyring()), default=fail.Keyring(), key=backend.by_priority, ) ) def _load_keyring_class(keyring_name): """ Load the keyring class indicated by name. These popular names are tested to ensure their presence. >>> popular_names = [ ... 'keyring.backends.Windows.WinVaultKeyring', ... 'keyring.backends.macOS.Keyring', ... 'keyring.backends.kwallet.DBusKeyring', ... 'keyring.backends.SecretService.Keyring', ... ] >>> list(map(_load_keyring_class, popular_names)) [...] """ module_name, sep, class_name = keyring_name.rpartition('.') __import__(module_name) module = sys.modules[module_name] return getattr(module, class_name) def load_keyring(keyring_name): """ Load the specified keyring by name (a fully-qualified name to the keyring, such as 'keyring.backends.file.PlaintextKeyring') """ class_ = _load_keyring_class(keyring_name) # invoke the priority to ensure it is viable, or raise a RuntimeError class_.priority return class_() def load_env(): """Load a keyring configured in the environment variable.""" try: return load_keyring(os.environ['PYTHON_KEYRING_BACKEND']) except KeyError: pass def load_config(): """Load a keyring using the config file in the config root.""" filename = 'keyringrc.cfg' keyring_cfg = os.path.join(platform.config_root(), filename) if not os.path.exists(keyring_cfg): return config = configparser.RawConfigParser() config.read(keyring_cfg) _load_keyring_path(config) # load the keyring class name, and then load this keyring try: if config.has_section("backend"): keyring_name = config.get("backend", "default-keyring").strip() else: raise configparser.NoOptionError('backend', 'default-keyring') except (configparser.NoOptionError, ImportError): logger = logging.getLogger('keyring') logger.warning( "Keyring config file contains incorrect values.\n" + "Config file: %s" % keyring_cfg ) return return load_keyring(keyring_name) def _load_keyring_path(config): "load the keyring-path option (if present)" try: path = config.get("backend", "keyring-path").strip() sys.path.insert(0, path) except (configparser.NoOptionError, configparser.NoSectionError): pass ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/keyring/credentials.py0000644000175100001710000000307100000000000017131 0ustar00runnerdockerimport os import abc class Credential(metaclass=abc.ABCMeta): """Abstract class to manage credentials""" @abc.abstractproperty def username(self): return None @abc.abstractproperty def password(self): return None class SimpleCredential(Credential): """Simple credentials implementation""" def __init__(self, username, password): self._username = username self._password = password @property def username(self): return self._username @property def password(self): return self._password class EnvironCredential(Credential): """ Source credentials from environment variables. Actual sourcing is deferred until requested. Supports comparison by equality. >>> e1 = EnvironCredential('a', 'b') >>> e2 = EnvironCredential('a', 'b') >>> e3 = EnvironCredential('a', 'c') >>> e1 == e2 True >>> e2 == e3 False """ def __init__(self, user_env_var, pwd_env_var): self.user_env_var = user_env_var self.pwd_env_var = pwd_env_var def __eq__(self, other: object) -> bool: return vars(self) == vars(other) def _get_env(self, env_var): """Helper to read an environment variable""" value = os.environ.get(env_var) if not value: raise ValueError('Missing environment variable:%s' % env_var) return value @property def username(self): return self._get_env(self.user_env_var) @property def password(self): return self._get_env(self.pwd_env_var) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/keyring/devpi_client.py0000644000175100001710000000030700000000000017300 0ustar00runnerdockerfrom pluggy import HookimplMarker import keyring hookimpl = HookimplMarker("devpiclient") @hookimpl() def devpiclient_get_password(url, username): return keyring.get_password(url, username) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/keyring/errors.py0000644000175100001710000000262600000000000016155 0ustar00runnerdockerimport sys class KeyringError(Exception): """Base class for exceptions in keyring""" class PasswordSetError(KeyringError): """Raised when the password can't be set.""" class PasswordDeleteError(KeyringError): """Raised when the password can't be deleted.""" class InitError(KeyringError): """Raised when the keyring could not be initialised""" class KeyringLocked(KeyringError): """Raised when the keyring failed unlocking""" class NoKeyringError(KeyringError, RuntimeError): """Raised when there is no keyring backend""" class ExceptionRaisedContext: """ An exception-trapping context that indicates whether an exception was raised. """ def __init__(self, ExpectedException=Exception): self.ExpectedException = ExpectedException self.exc_info = None def __enter__(self): self.exc_info = object.__new__(ExceptionInfo) return self.exc_info def __exit__(self, *exc_info): self.exc_info.__init__(*exc_info) return self.exc_info.type and issubclass( self.exc_info.type, self.ExpectedException ) class ExceptionInfo: def __init__(self, *info): if not info: info = sys.exc_info() self.type, self.value, _ = info def __bool__(self): """ Return True if an exception occurred """ return bool(self.type) __nonzero__ = __bool__ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/keyring/http.py0000644000175100001710000000231700000000000015615 0ustar00runnerdocker""" urllib2.HTTPPasswordMgr object using the keyring, for use with the urllib2.HTTPBasicAuthHandler. usage: import urllib2 handlers = [urllib2.HTTPBasicAuthHandler(PasswordMgr())] urllib2.install_opener(handlers) urllib2.urlopen(...) This will prompt for a password if one is required and isn't already in the keyring. Then, it adds it to the keyring for subsequent use. """ import getpass from . import get_password, delete_password, set_password class PasswordMgr: def get_username(self, realm, authuri): return getpass.getuser() def add_password(self, realm, authuri, password): user = self.get_username(realm, authuri) set_password(realm, user, password) def find_user_password(self, realm, authuri): user = self.get_username(realm, authuri) password = get_password(realm, user) if password is None: prompt = 'password for %(user)s@%(realm)s for ' '%(authuri)s: ' % vars() password = getpass.getpass(prompt) set_password(realm, user, password) return user, password def clear_password(self, realm, authuri): user = self.get_username(realm, authuri) delete_password(realm, user) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/keyring/py.typed0000644000175100001710000000000000000000000015746 0ustar00runnerdocker././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1641083381.8178484 keyring-23.5.0/keyring/testing/0000755000175100001710000000000000000000000015736 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/keyring/testing/__init__.py0000644000175100001710000000000000000000000020035 0ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/keyring/testing/backend.py0000644000175100001710000001375200000000000017707 0ustar00runnerdocker# coding: utf-8 """ Common test functionality for backends. """ import os import string import pytest from .util import random_string from keyring import errors # unicode only characters # Sourced from The Quick Brown Fox... Pangrams # http://www.columbia.edu/~fdc/utf8/ UNICODE_CHARS = ( "זהכיףסתםלשמועאיךתנצחקרפדעץטובבגן" "ξεσκεπάζωτηνψυχοφθόραβδελυγμία" "Съешьжеещёэтихмягкихфранцузскихбулокдавыпейчаю" "Жълтатадюлябешещастливачепухъткойтоцъфназамръзнакатогьон" ) # ensure no-ascii chars slip by - watch your editor! assert min(ord(char) for char in UNICODE_CHARS) > 127 def is_ascii_printable(s): return all(32 <= ord(c) < 127 for c in s) class BackendBasicTests: """Test for the keyring's basic functions. password_set and password_get""" DIFFICULT_CHARS = string.whitespace + string.punctuation @pytest.fixture(autouse=True) def _init_properties(self, request): self.keyring = self.init_keyring() self.credentials_created = set() request.addfinalizer(self.cleanup) def cleanup(self): for item in self.credentials_created: self.keyring.delete_password(*item) def set_password(self, service, username, password): # set the password and save the result so the test runner can clean # up after if necessary. self.keyring.set_password(service, username, password) self.credentials_created.add((service, username)) def check_set_get(self, service, username, password): keyring = self.keyring # for the non-existent password assert keyring.get_password(service, username) is None # common usage self.set_password(service, username, password) assert keyring.get_password(service, username) == password # for the empty password self.set_password(service, username, "") assert keyring.get_password(service, username) == "" def test_password_set_get(self): password = random_string(20) username = random_string(20) service = random_string(20) self.check_set_get(service, username, password) def test_difficult_chars(self): password = random_string(20, self.DIFFICULT_CHARS) username = random_string(20, self.DIFFICULT_CHARS) service = random_string(20, self.DIFFICULT_CHARS) self.check_set_get(service, username, password) def test_delete_present(self): password = random_string(20, self.DIFFICULT_CHARS) username = random_string(20, self.DIFFICULT_CHARS) service = random_string(20, self.DIFFICULT_CHARS) self.keyring.set_password(service, username, password) self.keyring.delete_password(service, username) assert self.keyring.get_password(service, username) is None def test_delete_not_present(self): username = random_string(20, self.DIFFICULT_CHARS) service = random_string(20, self.DIFFICULT_CHARS) with pytest.raises(errors.PasswordDeleteError): self.keyring.delete_password(service, username) def test_delete_one_in_group(self): username1 = random_string(20, self.DIFFICULT_CHARS) username2 = random_string(20, self.DIFFICULT_CHARS) password = random_string(20, self.DIFFICULT_CHARS) service = random_string(20, self.DIFFICULT_CHARS) self.keyring.set_password(service, username1, password) self.set_password(service, username2, password) self.keyring.delete_password(service, username1) assert self.keyring.get_password(service, username2) == password def test_name_property(self): assert is_ascii_printable(self.keyring.name) def test_unicode_chars(self): password = random_string(20, UNICODE_CHARS) username = random_string(20, UNICODE_CHARS) service = random_string(20, UNICODE_CHARS) self.check_set_get(service, username, password) def test_unicode_and_ascii_chars(self): source = ( random_string(10, UNICODE_CHARS) + random_string(10) + random_string(10, self.DIFFICULT_CHARS) ) password = random_string(20, source) username = random_string(20, source) service = random_string(20, source) self.check_set_get(service, username, password) def test_different_user(self): """ Issue #47 reports that WinVault isn't storing passwords for multiple users. This test exercises that test for each of the backends. """ keyring = self.keyring self.set_password('service1', 'user1', 'password1') self.set_password('service1', 'user2', 'password2') assert keyring.get_password('service1', 'user1') == 'password1' assert keyring.get_password('service1', 'user2') == 'password2' self.set_password('service2', 'user3', 'password3') assert keyring.get_password('service1', 'user1') == 'password1' def test_credential(self): keyring = self.keyring cred = keyring.get_credential('service', None) assert cred is None self.set_password('service1', 'user1', 'password1') self.set_password('service1', 'user2', 'password2') cred = keyring.get_credential('service1', None) assert cred is None or (cred.username, cred.password) in ( ('user1', 'password1'), ('user2', 'password2'), ) cred = keyring.get_credential('service1', 'user2') assert cred is not None assert (cred.username, cred.password) in ( ('user1', 'password1'), ('user2', 'password2'), ) def test_set_properties(self, monkeypatch): env = dict(KEYRING_PROPERTY_FOO_BAR='fizz buzz', OTHER_SETTING='ignore me') monkeypatch.setattr(os, 'environ', env) self.keyring.set_properties_from_env() assert self.keyring.foo_bar == 'fizz buzz' ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/keyring/testing/util.py0000644000175100001710000000357600000000000017300 0ustar00runnerdockerimport contextlib import os import sys import random import string class ImportKiller: "Context manager to make an import of a given name or names fail." def __init__(self, *names): self.names = names def find_module(self, fullname, path=None): if fullname in self.names: return self def load_module(self, fullname): assert fullname in self.names raise ImportError(fullname) def __enter__(self): self.original = {} for name in self.names: self.original[name] = sys.modules.pop(name, None) sys.meta_path.insert(0, self) def __exit__(self, *args): sys.meta_path.remove(self) for key, value in self.original.items(): if value is not None: sys.modules[key] = value @contextlib.contextmanager def NoNoneDictMutator(destination, **changes): """Helper context manager to make and unmake changes to a dict. A None is not a valid value for the destination, and so means that the associated name should be removed.""" original = {} for key, value in changes.items(): original[key] = destination.get(key) if value is None: if key in destination: del destination[key] else: destination[key] = value yield for key, value in original.items(): if value is None: if key in destination: del destination[key] else: destination[key] = value def Environ(**changes): """A context manager to temporarily change the os.environ""" return NoNoneDictMutator(os.environ, **changes) ALPHABET = string.ascii_letters + string.digits def random_string(k, source=ALPHABET): """Generate a random string with length k""" result = '' for i in range(0, k): result += random.choice(source) return result ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1641083381.8178484 keyring-23.5.0/keyring/util/0000755000175100001710000000000000000000000015236 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/keyring/util/__init__.py0000644000175100001710000000154400000000000017353 0ustar00runnerdockerimport functools def once(func): """ Decorate func so it's only ever called the first time. This decorator can ensure that an expensive or non-idempotent function will not be expensive on subsequent calls and is idempotent. >>> func = once(lambda a: a+3) >>> func(3) 6 >>> func(9) 6 >>> func('12') 6 """ def wrapper(*args, **kwargs): if not hasattr(func, 'always_returns'): func.always_returns = func(*args, **kwargs) return func.always_returns return functools.wraps(func)(wrapper) def suppress_exceptions(callables, exceptions=Exception): """ yield the results of calling each element of callables, suppressing any indicated exceptions. """ for callable in callables: try: yield callable() except exceptions: pass ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/keyring/util/platform_.py0000644000175100001710000000424600000000000017601 0ustar00runnerdockerimport os import platform import pathlib def _settings_root_XP(): return os.path.join(os.environ['USERPROFILE'], 'Local Settings') def _settings_root_Vista(): return os.environ.get('LOCALAPPDATA', os.environ.get('ProgramData', '.')) def _data_root_Windows(): release, version, csd, ptype = platform.win32_ver() root = _settings_root_XP() if release == 'XP' else _settings_root_Vista() return os.path.join(root, 'Python Keyring') def _data_root_Linux(): """ Use freedesktop.org Base Dir Specification to determine storage location. """ fallback = pathlib.Path.home() / '.local/share' root = os.environ.get('XDG_DATA_HOME', None) or fallback return os.path.join(root, 'python_keyring') _config_root_Windows = _data_root_Windows def _check_old_config_root(): """ Prior versions of keyring would search for the config in XDG_DATA_HOME, but should probably have been searching for config in XDG_CONFIG_HOME. If the config exists in the former but not in the latter, raise a RuntimeError to force the change. """ # disable the check - once is enough and avoids infinite loop globals()['_check_old_config_root'] = lambda: None config_file_new = os.path.join(_config_root_Linux(), 'keyringrc.cfg') config_file_old = os.path.join(_data_root_Linux(), 'keyringrc.cfg') if os.path.isfile(config_file_old) and not os.path.isfile(config_file_new): msg = ( "Keyring config exists only in the old location " f"{config_file_old} and should be moved to {config_file_new} " "to work with this version of keyring." ) raise RuntimeError(msg) def _config_root_Linux(): """ Use freedesktop.org Base Dir Specification to determine config location. """ _check_old_config_root() fallback = pathlib.Path.home() / '.config' key = 'XDG_CONFIG_HOME' root = os.environ.get(key, None) or fallback return os.path.join(root, 'python_keyring') # by default, use Unix convention data_root = globals().get('_data_root_' + platform.system(), _data_root_Linux) config_root = globals().get('_config_root' + platform.system(), _config_root_Linux) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/keyring/util/properties.py0000644000175100001710000000245500000000000020012 0ustar00runnerdockerfrom collections import abc class ClassProperty(property): """ An implementation of a property callable on a class. Used to decorate a classmethod but to then treat it like a property. Example: >>> class MyClass: ... @ClassProperty ... @classmethod ... def skillz(cls): ... return cls.__name__.startswith('My') >>> MyClass.skillz True >>> class YourClass(MyClass): pass >>> YourClass.skillz False """ def __get__(self, cls, owner): return self.fget.__get__(None, owner)() # borrowed from jaraco.util.dictlib class NonDataProperty: """Much like the property builtin, but only implements __get__, making it a non-data property, and can be subsequently reset. See http://users.rcn.com/python/download/Descriptor.htm for more information. >>> class X: ... @NonDataProperty ... def foo(self): ... return 3 >>> x = X() >>> x.foo 3 >>> x.foo = 4 >>> x.foo 4 """ def __init__(self, fget): assert fget is not None, "fget cannot be none" assert isinstance(fget, abc.Callable), "fget must be callable" self.fget = fget def __get__(self, obj, objtype=None): if obj is None: return self return self.fget(obj) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1641083381.8138483 keyring-23.5.0/keyring.egg-info/0000755000175100001710000000000000000000000015753 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083381.0 keyring-23.5.0/keyring.egg-info/PKG-INFO0000644000175100001710000004344000000000000017055 0ustar00runnerdockerMetadata-Version: 2.1 Name: keyring Version: 23.5.0 Summary: Store and access your passwords safely. Home-page: https://github.com/jaraco/keyring Author: Kang Zhang Author-email: jobo.zh@gmail.com Maintainer: Jason R. Coombs Maintainer-email: jaraco@jaraco.com License: UNKNOWN Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Python Software Foundation License Classifier: License :: OSI Approved :: MIT License Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3 :: Only Requires-Python: >=3.7 Provides-Extra: testing Provides-Extra: docs License-File: LICENSE .. image:: https://img.shields.io/pypi/v/keyring.svg :target: `PyPI link`_ .. image:: https://img.shields.io/pypi/pyversions/keyring.svg :target: `PyPI link`_ .. _PyPI link: https://pypi.org/project/keyring .. image:: https://github.com/jaraco/keyring/workflows/tests/badge.svg :target: https://github.com/jaraco/keyring/actions?query=workflow%3A%22tests%22 :alt: tests .. image:: https://img.shields.io/badge/code%20style-black-000000.svg :target: https://github.com/psf/black :alt: Code style: Black .. image:: https://readthedocs.org/projects/keyring/badge/?version=latest :target: https://keyring.readthedocs.io/en/latest/?badge=latest .. image:: https://img.shields.io/badge/skeleton-2021-informational :target: https://blog.jaraco.com/skeleton .. image:: https://tidelift.com/badges/package/pypi/keyring :target: https://tidelift.com/subscription/pkg/pypi-keyring?utm_source=pypi-keyring&utm_medium=readme .. image:: https://badges.gitter.im/jaraco/keyring.svg :alt: Join the chat at https://gitter.im/jaraco/keyring :target: https://gitter.im/jaraco/keyring?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge The Python keyring library provides an easy way to access the system keyring service from python. It can be used in any application that needs safe password storage. These recommended keyring backends are supported: * macOS `Keychain `_ * Freedesktop `Secret Service `_ supports many DE including GNOME (requires `secretstorage `_) * KDE4 & KDE5 `KWallet `_ (requires `dbus `_) * `Windows Credential Locker `_ Other keyring implementations are available through `Third-Party Backends`_. Installation - Linux ==================== On Linux, the KWallet backend relies on dbus-python_, which does not always install correctly when using pip (compilation is needed). For best results, install dbus-python as a system package. .. _dbus-python: https://gitlab.freedesktop.org/dbus/dbus-python Compatibility - macOS ===================== macOS keychain support macOS 11 (Big Sur) and later requires Python 3.8.7 or later with the "universal2" binary. See `#525 `_ for details. Using Keyring ============= The basic usage of keyring is pretty simple: just call ``keyring.set_password`` and ``keyring.get_password``:: >>> import keyring >>> keyring.set_password("system", "username", "password") >>> keyring.get_password("system", "username") 'password' Command-line Utility -------------------- Keyring supplies a ``keyring`` command which is installed with the package. After installing keyring in most environments, the command should be available for setting, getting, and deleting passwords. For more information on usage, invoke with no arguments or with ``--help`` as so:: $ keyring --help $ keyring set system username Password for 'username' in 'system': $ keyring get system username password The command-line functionality is also exposed as an executable package, suitable for invoking from Python like so:: $ python -m keyring --help $ python -m keyring set system username Password for 'username' in 'system': $ python -m keyring get system username password Configuring =========== The python keyring lib contains implementations for several backends. The library will attempt to automatically choose the most suitable backend for the current environment. Users may also specify the preferred keyring in a config file or by calling the ``set_keyring()`` function. Config file path ---------------- The configuration is stored in a file named "keyringrc.cfg" found in a platform-specific location. To determine where the config file is stored, run the following:: python -c "import keyring.util.platform_; print(keyring.util.platform_.config_root())" Some keyrings also store the keyring data in the file system. To determine where the data files are stored, run:: python -c "import keyring.util.platform_; print(keyring.util.platform_.data_root())" Config file content ------------------- To specify a keyring backend, set the **default-keyring** option to the full path of the class for that backend, such as ``keyring.backends.OS_X.Keyring``. If **keyring-path** is indicated, keyring will add that path to the Python module search path before loading the backend. For example, this config might be used to load the ``SimpleKeyring`` from the ``simplekeyring`` module in the ``./demo`` directory (not implemented):: [backend] default-keyring=simplekeyring.SimpleKeyring keyring-path=demo Third-Party Backends ==================== In addition to the backends provided by the core keyring package for the most common and secure use cases, there are additional keyring backend implementations available for other use-cases. Simply install them to make them available: - `keyrings.cryptfile `_ - Encrypted text file storage. - `keyring_jeepney `__ - a pure Python backend using the secret service DBus API for desktop Linux. - `keyrings.alt `_ - "alternate", possibly-insecure backends, originally part of the core package, but available for opt-in. - `gsheet-keyring `_ - a backend that stores secrets in a Google Sheet. For use with `ipython-secrets `_. - `bitwarden-keyring `_ - a backend that stores secrets in the `BitWarden `_ password manager. - `sagecipher `_ - an encryption backend which uses the ssh agent protocol's signature operation to derive the cipher key. - `keyrings.osx_keychain_keys `_ - OSX keychain key-management, for private, public and symmetric keys. Write your own keyring backend ============================== The interface for the backend is defined by ``keyring.backend.KeyringBackend``. Every backend should derive from that base class and define a ``priority`` attribute and three functions: ``get_password()``, ``set_password()``, and ``delete_password()``. The ``get_credential()`` function may be defined if desired. See the ``backend`` module for more detail on the interface of this class. Keyring employs entry points to allow any third-party package to implement backends without any modification to the keyring itself. Those interested in creating new backends are encouraged to create new, third-party packages in the ``keyrings`` namespace, in a manner modeled by the `keyrings.alt package `_. See the ``setup.cfg`` file in that project for a hints on how to create the requisite entry points. Backends that prove essential may be considered for inclusion in the core library, although the ease of installing these third-party packages should mean that extensions may be readily available. To create an extension for Keyring, please submit a pull request to have your extension mentioned as an available extension. Runtime Configuration ===================== Keyring additionally allows programmatic configuration of the backend calling the api ``set_keyring()``. The indicated backend will subsequently be used to store and retrieve passwords. To invoke ``set_keyring``:: # define a new keyring class which extends the KeyringBackend import keyring.backend class TestKeyring(keyring.backend.KeyringBackend): """A test keyring which always outputs same password """ priority = 1 def set_password(self, servicename, username, password): pass def get_password(self, servicename, username): return "password from TestKeyring" def delete_password(self, servicename, username): pass # set the keyring for keyring lib keyring.set_keyring(TestKeyring()) # invoke the keyring lib try: keyring.set_password("demo-service", "tarek", "passexample") print("password stored successfully") except keyring.errors.PasswordSetError: print("failed to store password") print("password", keyring.get_password("demo-service", "tarek")) Disabling Keyring ================= In many cases, uninstalling keyring will never be necessary. Especially on Windows and macOS, the behavior of keyring is usually degenerate, meaning it will return empty values to the caller, allowing the caller to fall back to some other behavior. In some cases, the default behavior of keyring is undesirable and it would be preferable to disable the keyring behavior altogether. There are several mechanisms to disable keyring: - Uninstall keyring. Most applications are tolerant to keyring not being installed. Uninstalling keyring should cause those applications to fall back to the behavior without keyring. This approach affects that Python environment where keyring would otherwise have been installed. - Configure the Null keyring in the environment. Set ``PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring`` in the environment, and the ``Null`` (degenerate) backend will be used. This approach affects all uses of Keyring where that variable is set. - Permanently configure the Null keyring for the user by running ``keyring --disable`` or ``python -m keyring --disable``. This approach affects all uses of keyring for that user. Altering Keyring Behavior ========================= Keyring provides a mechanism to alter the keyring's behavior through environment variables. Each backend implements a ``KeyringBackend.set_properties_from_env``, which when invoked will find all environment variables beginning with ``KEYRING_PROPERTY_{NAME}`` and will set a property for each ``{NAME.lower()}`` on the keyring. This method is invoked during initialization for the default/configured keyring. This mechanism may be used to set some useful values on various keyrings, including: - keychain; macOS, path to an alternate keychain file - appid; Linux/SecretService, alternate ID for the application Using Keyring on Ubuntu 16.04 ============================= The following is a complete transcript for installing keyring in a virtual environment on Ubuntu 16.04. No config file was used:: $ sudo apt install python3-venv libdbus-glib-1-dev $ cd /tmp $ pyvenv py3 $ source py3/bin/activate $ pip install -U pip $ pip install secretstorage dbus-python $ pip install keyring $ python >>> import keyring >>> keyring.get_keyring() >>> keyring.set_password("system", "username", "password") >>> keyring.get_password("system", "username") 'password' Using Keyring on headless Linux systems ======================================= It is possible to use the SecretService backend on Linux systems without X11 server available (only D-Bus is required). In this case: * Install the `GNOME Keyring`_ daemon. * Start a D-Bus session, e.g. run ``dbus-run-session -- sh`` and run the following commands inside that shell. * Run ``gnome-keyring-daemon`` with ``--unlock`` option. The description of that option says: Read a password from stdin, and use it to unlock the login keyring or create it if the login keyring does not exist. When that command is started, enter a password into stdin and press Ctrl+D (end of data). After that, the daemon will fork into background (use ``--foreground`` option to block). * Now you can use the SecretService backend of Keyring. Remember to run your application in the same D-Bus session as the daemon. .. _GNOME Keyring: https://wiki.gnome.org/Projects/GnomeKeyring Using Keyring on headless Linux systems in a Docker container ============================================================= It is possible to use keyring with the SecretService backend in Docker containers as well. All you need to do is install the necessary dependencies and add the `--privileged` flag to avoid any `Operation not permitted` errors when attempting to unlock the system's keyring. The following is a complete transcript for installing keyring on a Ubuntu 18:04 container:: docker run -it -d --privileged ubuntu:18.04 $ apt-get update $ apt install -y gnome-keyring python3-venv python3-dev $ python3 -m venv venv $ source venv/bin/activate # source a virtual environment to avoid polluting your system $ pip3 install --upgrade pip $ pip3 install keyring $ dbus-run-session -- sh # this will drop you into a new D-bus shell $ echo 'somecredstorepass' | gnome-keyring-daemon --unlock # unlock the system's keyring $ python >>> import keyring >>> keyring.get_keyring() >>> keyring.set_password("system", "username", "password") >>> keyring.get_password("system", "username") 'password' Integration =========== API --- The keyring lib has a few functions: * ``get_keyring()``: Return the currently-loaded keyring implementation. * ``get_password(service, username)``: Returns the password stored in the active keyring. If the password does not exist, it will return None. * ``get_credential(service, username)``: Return a credential object stored in the active keyring. This object contains at least ``username`` and ``password`` attributes for the specified service, where the returned ``username`` may be different from the argument. * ``set_password(service, username, password)``: Store the password in the keyring. * ``delete_password(service, username)``: Delete the password stored in keyring. If the password does not exist, it will raise an exception. In all cases, the parameters (``service``, ``username``, ``password``) should be Unicode text. Exceptions ---------- The keyring lib raises following exceptions: * ``keyring.errors.KeyringError``: Base Error class for all exceptions in keyring lib. * ``keyring.errors.InitError``: Raised when the keyring cannot be initialized. * ``keyring.errors.PasswordSetError``: Raised when password cannot be set in the keyring. * ``keyring.errors.PasswordDeleteError``: Raised when the password cannot be deleted in the keyring. Get Involved ============ Python keyring lib is an open community project and eagerly welcomes contributors. * Repository: https://github.com/jaraco/keyring/ * Bug Tracker: https://github.com/jaraco/keyring/issues/ * Mailing list: http://groups.google.com/group/python-keyring For Enterprise ============== Available as part of the Tidelift Subscription. This project and the maintainers of thousands of other packages are working with Tidelift to deliver one enterprise subscription that covers all of the open source you use. `Learn more `_. Security Contact ================ To report a security vulnerability, please use the `Tidelift security contact `_. Tidelift will coordinate the fix and disclosure. Security Considerations ======================= Each builtin backend may have security considerations to understand before using this library. Authors of tools or libraries utilizing ``keyring`` are encouraged to consider these concerns. As with any list of known security concerns, this list is not exhaustive. Additional issues can be added as needed. - macOS Keychain - Any Python script or application can access secrets created by ``keyring`` from that same Python executable without the operating system prompting the user for a password. To cause any specific secret to prompt for a password every time it is accessed, locate the credential using the ``Keychain Access`` application, and in the ``Access Control`` settings, remove ``Python`` from the list of allowed applications. - Freedesktop Secret Service - No analysis has been performed - KDE4 & KDE5 KWallet - No analysis has been performed - Windows Credential Locker - No analysis has been performed Making Releases =============== This project makes use of automated releases continuous integration. The simple workflow is to tag a commit and push it to Github. If it passes tests in CI, it will be automatically deployed to PyPI. Other things to consider when making a release: - Check that the changelog is current for the intended release. Running Tests ============= Tests are continuously run in Github Actions. To run the tests locally, install and invoke `tox `_. Background ========== The project was based on Tarek Ziade's idea in `this post`_. Kang Zhang initially carried it out as a `Google Summer of Code`_ project, and Tarek mentored Kang on this project. .. _this post: http://tarekziade.wordpress.com/2009/03/27/pycon-hallway-session-1-a-keyring-library-for-python/ .. _Google Summer of Code: http://socghop.appspot.com/ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083381.0 keyring-23.5.0/keyring.egg-info/SOURCES.txt0000644000175100001710000000305500000000000017642 0ustar00runnerdocker.coveragerc .editorconfig .flake8 .gitignore .pre-commit-config.yaml .readthedocs.yml .travis-macos CHANGES.rst Dockerfile LICENSE README.rst conftest.py hook-keyring.backend.py mypy.ini pyproject.toml pytest.ini setup.cfg setup.py tox.ini .github/FUNDING.yml .github/dependabot.yml .github/ISSUE_TEMPLATE/bug_report.md .github/workflows/main.yml docs/conf.py docs/history.rst docs/index.rst docs/troubleshooting config.md keyring/__init__.py keyring/__main__.py keyring/backend.py keyring/cli.py keyring/core.py keyring/credentials.py keyring/devpi_client.py keyring/errors.py keyring/http.py keyring/py.typed keyring.egg-info/PKG-INFO keyring.egg-info/SOURCES.txt keyring.egg-info/dependency_links.txt keyring.egg-info/entry_points.txt keyring.egg-info/requires.txt keyring.egg-info/top_level.txt keyring/backends/OS_X.py keyring/backends/SecretService.py keyring/backends/Windows.py keyring/backends/__init__.py keyring/backends/chainer.py keyring/backends/fail.py keyring/backends/kwallet.py keyring/backends/libsecret.py keyring/backends/null.py keyring/backends/macOS/__init__.py keyring/backends/macOS/api.py keyring/testing/__init__.py keyring/testing/backend.py keyring/testing/util.py keyring/util/__init__.py keyring/util/platform_.py keyring/util/properties.py tests/test_core.py tests/test_errors.py tests/test_integration.py tests/test_multiprocess.py tests/test_packaging.py tests/backends/test_SecretService.py tests/backends/test_Windows.py tests/backends/test_chainer.py tests/backends/test_kwallet.py tests/backends/test_libsecret.py tests/backends/test_macOS.py././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083381.0 keyring-23.5.0/keyring.egg-info/dependency_links.txt0000644000175100001710000000000100000000000022021 0ustar00runnerdocker ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083381.0 keyring-23.5.0/keyring.egg-info/entry_points.txt0000644000175100001710000000051700000000000021254 0ustar00runnerdocker[console_scripts] keyring = keyring.cli:main [devpi_client] keyring = keyring.devpi_client [keyring.backends] KWallet = keyring.backends.kwallet SecretService = keyring.backends.SecretService Windows = keyring.backends.Windows chainer = keyring.backends.chainer libsecret = keyring.backends.libsecret macOS = keyring.backends.macOS ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083381.0 keyring-23.5.0/keyring.egg-info/requires.txt0000644000175100001710000000061000000000000020350 0ustar00runnerdockerimportlib_metadata>=3.6 [:sys_platform == "linux"] SecretStorage>=3.2 jeepney>=0.4.2 [:sys_platform == "win32"] pywin32-ctypes!=0.1.0,!=0.1.1 [docs] sphinx jaraco.packaging>=8.2 rst.linker>=1.9 jaraco.tidelift>=1.4 [testing] pytest>=6 pytest-checkdocs>=2.4 pytest-flake8 pytest-cov pytest-enabler>=1.0.1 [testing:platform_python_implementation != "PyPy"] pytest-black>=0.3.7 pytest-mypy ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083381.0 keyring-23.5.0/keyring.egg-info/top_level.txt0000644000175100001710000000001000000000000020474 0ustar00runnerdockerkeyring ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/mypy.ini0000644000175100001710000000004500000000000014307 0ustar00runnerdocker[mypy] ignore_missing_imports = True ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/pyproject.toml0000644000175100001710000000054600000000000015532 0ustar00runnerdocker[build-system] requires = ["setuptools>=56", "setuptools_scm[toml]>=3.4.1"] build-backend = "setuptools.build_meta" [tool.black] skip-string-normalization = true [tool.setuptools_scm] [pytest.enabler.black] addopts = "--black" [pytest.enabler.mypy] addopts = "--mypy" [pytest.enabler.flake8] addopts = "--flake8" [pytest.enabler.cov] addopts = "--cov" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/pytest.ini0000644000175100001710000000041500000000000014642 0ustar00runnerdocker[pytest] norecursedirs=dist build .tox .eggs addopts=--doctest-modules doctest_optionflags=ALLOW_UNICODE ELLIPSIS filterwarnings= # Suppress deprecation warning in flake8 ignore:SelectableGroups dict interface is deprecated::flake8 ignore:OS_X module is deprecated ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1641083381.8178484 keyring-23.5.0/setup.cfg0000644000175100001710000000311600000000000014433 0ustar00runnerdocker[metadata] name = keyring author = Kang Zhang author_email = jobo.zh@gmail.com maintainer = Jason R. Coombs maintainer_email = jaraco@jaraco.com description = Store and access your passwords safely. long_description = file:README.rst url = https://github.com/jaraco/keyring classifiers = Development Status :: 5 - Production/Stable Intended Audience :: Developers License :: OSI Approved :: Python Software Foundation License License :: OSI Approved :: MIT License Programming Language :: Python :: 3 Programming Language :: Python :: 3 :: Only [options] packages = find_namespace: include_package_data = true python_requires = >=3.7 install_requires = pywin32-ctypes!=0.1.0,!=0.1.1; sys_platform=="win32" SecretStorage>=3.2; sys_platform=="linux" jeepney>=0.4.2; sys_platform=="linux" importlib_metadata >= 3.6 [options.packages.find] exclude = build* dist* docs* tests* [options.extras_require] testing = pytest >= 6 pytest-checkdocs >= 2.4 pytest-flake8 pytest-black >= 0.3.7; \ python_implementation != "PyPy" pytest-cov pytest-mypy; \ python_implementation != "PyPy" pytest-enabler >= 1.0.1 docs = sphinx jaraco.packaging >= 8.2 rst.linker >= 1.9 jaraco.tidelift >= 1.4 [options.entry_points] console_scripts = keyring=keyring.cli:main devpi_client = keyring = keyring.devpi_client keyring.backends = Windows = keyring.backends.Windows macOS = keyring.backends.macOS libsecret = keyring.backends.libsecret SecretService = keyring.backends.SecretService KWallet = keyring.backends.kwallet chainer = keyring.backends.chainer [egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/setup.py0000644000175100001710000000013400000000000014321 0ustar00runnerdocker#!/usr/bin/env python import setuptools if __name__ == "__main__": setuptools.setup() ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1641083381.8178484 keyring-23.5.0/tests/0000755000175100001710000000000000000000000013753 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1641083381.8178484 keyring-23.5.0/tests/backends/0000755000175100001710000000000000000000000015525 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/tests/backends/test_SecretService.py0000644000175100001710000000172500000000000021711 0ustar00runnerdockerimport pytest from keyring.testing.backend import BackendBasicTests from keyring.testing.util import NoNoneDictMutator from keyring.backends import SecretService @pytest.mark.skipif( not SecretService.Keyring.viable, reason="SecretStorage package is needed for SecretServiceKeyring", ) class TestSecretServiceKeyring(BackendBasicTests): __test__ = True def init_keyring(self): print( "Testing SecretServiceKeyring; the following " "password prompts are for this keyring" ) keyring = SecretService.Keyring() keyring.preferred_collection = '/org/freedesktop/secrets/collection/session' return keyring class TestUnits: def test_supported_no_secretstorage(self): """ SecretService Keyring is not viable if secretstorage can't be imported. """ with NoNoneDictMutator(SecretService.__dict__, secretstorage=None): assert not SecretService.Keyring.viable ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/tests/backends/test_Windows.py0000644000175100001710000000444000000000000020572 0ustar00runnerdockerimport sys import pytest import keyring.backends.Windows from keyring.testing.backend import BackendBasicTests, UNICODE_CHARS @pytest.mark.skipif( not keyring.backends.Windows.WinVaultKeyring.viable, reason="Needs Windows" ) class TestWinVaultKeyring(BackendBasicTests): def tearDown(self): # clean up any credentials created for cred in self.credentials_created: try: self.keyring.delete_password(*cred) except Exception as e: print(e, file=sys.stderr) def init_keyring(self): return keyring.backends.Windows.WinVaultKeyring() def set_utf8_password(self, service, username, password): """ Write a UTF-8 encoded password using win32ctypes primitives """ from win32ctypes.core import _authentication as auth from win32ctypes.core.ctypes._common import LPBYTE from ctypes import cast, c_char, create_string_buffer, sizeof credential = dict( Type=1, TargetName=service, UserName=username, CredentialBlob=password, Comment="Stored using python-keyring", Persist=3, ) c_cred = auth.CREDENTIAL.fromdict(credential, 0) blob_data = create_string_buffer(password.encode("utf-8")) c_cred.CredentialBlobSize = sizeof(blob_data) - sizeof(c_char) c_cred.CredentialBlob = cast(blob_data, LPBYTE) c_cred_pointer = auth.PCREDENTIAL(c_cred) auth._CredWrite(c_cred_pointer, 0) self.credentials_created.add((service, username)) def test_long_password_nice_error(self): self.keyring.set_password('system', 'user', 'x' * 512 * 2) def test_read_utf8_password(self): """ Write a UTF-8 encoded credential and make sure it can be read back correctly. """ service = "keyring-utf8-test" username = "keyring" password = "utf8-test" + UNICODE_CHARS self.set_utf8_password(service, username, password) assert self.keyring.get_password(service, username) == password @pytest.mark.skipif('sys.platform != "win32"') def test_winvault_always_viable(): """ The WinVault backend should always be viable on Windows. """ assert keyring.backends.Windows.WinVaultKeyring.viable ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/tests/backends/test_chainer.py0000644000175100001710000000265700000000000020561 0ustar00runnerdockerimport pytest import keyring.backends.chainer from keyring import backend @pytest.fixture def two_keyrings(monkeypatch): def get_two(): class Keyring1(backend.KeyringBackend): priority = 1 def get_password(self, system, user): return f'ring1-{system}-{user}' def set_password(self, system, user, password): pass class Keyring2(backend.KeyringBackend): priority = 2 def get_password(self, system, user): return f'ring2-{system}-{user}' def set_password(self, system, user, password): raise NotImplementedError() return Keyring1(), Keyring2() monkeypatch.setattr('keyring.backend.get_all_keyring', get_two) class TestChainer: def test_chainer_gets_from_highest_priority(self, two_keyrings): chainer = keyring.backends.chainer.ChainerBackend() pw = chainer.get_password('alpha', 'bravo') assert pw == 'ring2-alpha-bravo' def test_chainer_defers_to_fail(self, monkeypatch): """ The Chainer backend should defer to the Fail backend when there are no backends to be chained. """ monkeypatch.setattr('keyring.backend.get_all_keyring', tuple) assert keyring.backend.by_priority( keyring.backends.chainer.ChainerBackend ) < keyring.backend.by_priority(keyring.backends.fail.Keyring) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/tests/backends/test_kwallet.py0000644000175100001710000000557400000000000020614 0ustar00runnerdockerimport pytest from keyring.backends import kwallet from keyring.testing.backend import BackendBasicTests @pytest.mark.skipif(not kwallet.DBusKeyring.viable, reason="KWallet5 unavailable") class TestDBusKWallet(BackendBasicTests): # Remove '@' from service name as this is not supported in service names # '@' will cause troubles during migration of kwallet entries DIFFICULT_CHARS = BackendBasicTests.DIFFICULT_CHARS.replace('@', '') def init_keyring(self): return kwallet.DBusKeyring() def cleanup(self): for item in self.credentials_created: # Suppress errors, as only one pre/post migration item will be # present try: self.keyring.delete_password(*item) except BaseException: pass # TODO Remove empty folders created during tests def set_password(self, service, username, password, old_format=False): # set the password and save the result so the test runner can clean # up after if necessary. self.credentials_created.add((service, username)) if old_format: username = username + '@' + service service = 'Python' super().set_password(service, username, password) def check_set_get(self, service, username, password): keyring = self.keyring # for the non-existent password assert keyring.get_password(service, username) is None # common usage self.set_password(service, username, password, True) # re-init keyring to force migration self.keyring = keyring = self.init_keyring() ret_password = keyring.get_password(service, username) assert ret_password == password, ( "Incorrect password for username: '%s' " "on service: '%s'. '%s' != '%s'" % (service, username, ret_password, password), ) # for the empty password self.set_password(service, username, "", True) # re-init keyring to force migration self.keyring = keyring = self.init_keyring() ret_password = keyring.get_password(service, username) assert ret_password == "", ( "Incorrect password for username: '%s' " "on service: '%s'. '%s' != '%s'" % (service, username, ret_password, ""), ) ret_password = keyring.get_password('Python', username + '@' + service) assert ret_password is None, ( "Not 'None' password returned for username: '%s' " "on service: '%s'. '%s' != '%s'. Passwords from old " "folder should be deleted during migration." % (service, username, ret_password, None), ) @pytest.mark.skipif( not kwallet.DBusKeyringKWallet4.viable, reason="KWallet4 unavailable" ) class TestDBusKWallet4(TestDBusKWallet): def init_keyring(self): return kwallet.DBusKeyringKWallet4() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/tests/backends/test_libsecret.py0000644000175100001710000000155400000000000021117 0ustar00runnerdockerimport pytest from keyring.testing.backend import BackendBasicTests from keyring.testing.util import NoNoneDictMutator from keyring.backends import libsecret @pytest.mark.skipif( not libsecret.Keyring.viable, reason="libsecret package is needed for LibSecretKeyring", ) class TestLibSecretKeyring(BackendBasicTests): __test__ = True def init_keyring(self): print( "Testing LibSecretKeyring; the following " "password prompts are for this keyring" ) keyring = libsecret.Keyring() keyring.collection = 'session' return keyring class TestUnits: def test_supported_no_libsecret(self): """ LibSecretKeyring is not viable if Secret can't be imported. """ with NoNoneDictMutator(libsecret.__dict__, Secret=None): assert not libsecret.Keyring.viable ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/tests/backends/test_macOS.py0000644000175100001710000000051600000000000020142 0ustar00runnerdockerimport pytest import keyring from keyring.testing.backend import BackendBasicTests from keyring.backends import macOS @pytest.mark.skipif( not keyring.backends.macOS.Keyring.viable, reason="macOS backend not viable", ) class Test_macOSKeychain(BackendBasicTests): def init_keyring(self): return macOS.Keyring() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/tests/test_core.py0000644000175100001710000000041500000000000016314 0ustar00runnerdockerimport keyring.core def test_init_recommended(monkeypatch): """ Test filtering of backends to recommended ones (#117, #423). """ monkeypatch.setattr(keyring.core, 'set_keyring', lambda kr: None) keyring.core.init_backend(keyring.core.recommended) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/tests/test_errors.py0000644000175100001710000000142700000000000016704 0ustar00runnerdockerimport sys from unittest import mock import pytest from keyring import errors class DeletionDetector: def __init__(self, state): self.state = state self.state.deleted = False def __del__(self): self.state.deleted = True class TestExceptionInfo: @pytest.mark.skipif( '__pypy__' in sys.builtin_module_names, reason="pypy garbage collection defers deletion", ) def test_traceback_not_referenced(self): """ Ensure that an ExceptionInfo does not keep a reference to the traceback, as doing so can create unintended side effects. See #386 for more info. """ state = mock.MagicMock() _ = errors.ExceptionInfo(None, None, DeletionDetector(state)) assert state.deleted ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/tests/test_integration.py0000644000175100001710000000073500000000000017714 0ustar00runnerdockerimport sys import subprocess import pytest argv_manipulations = [ 'del sys.argv', 'sys.argv = []', 'sys.argv = None', ] @pytest.mark.parametrize('argv', argv_manipulations) def test_argv(argv): """ Keyrings should initialize without error even when sys.argv is malformed. Ref #445. """ code = f'import sys; {argv}; import keyring' cmd = [sys.executable, '-c', code] assert not subprocess.check_output(cmd, stderr=subprocess.STDOUT) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/tests/test_multiprocess.py0000644000175100001710000000145500000000000020122 0ustar00runnerdockerimport sys import platform import multiprocessing import pytest import keyring def subprocess_get(): keyring.get_password('test_app', 'test_user') pytestmark = [ pytest.mark.xfail( platform.system() == 'Linux', reason="#410: keyring discovery fails intermittently", ), pytest.mark.skipif( # always skip as it crashes the interpreter sys.version_info < (3, 8) and platform.system() == 'Darwin', reason="#281, #494: Prior to 3.8, multiprocess invocation fails", ), ] def test_multiprocess_get(): proc1 = multiprocessing.Process(target=subprocess_get) proc1.start() proc1.join() assert proc1.exitcode == 0 def test_multiprocess_get_after_native_get(): keyring.get_password('test_app', 'test_user') test_multiprocess_get() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/tests/test_packaging.py0000644000175100001710000000105500000000000017311 0ustar00runnerdockerimport importlib_metadata as metadata from keyring import backend def test_entry_point(): """ Keyring provides exactly one 'keyring' console script that's a callable. """ matches = metadata.entry_points(group='console_scripts', name='keyring') (script,) = matches assert callable(script.load()) def test_missing_metadata(monkeypatch): """ _load_plugins should pass when keyring metadata is missing. """ monkeypatch.setattr(metadata, 'entry_points', metadata.EntryPoints().select) backend._load_plugins() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1641083358.0 keyring-23.5.0/tox.ini0000644000175100001710000000133400000000000014125 0ustar00runnerdocker[tox] envlist = python minversion = 3.2 # https://github.com/jaraco/skeleton/issues/6 tox_pip_extensions_ext_venv_update = true toxworkdir={env:TOX_WORK_DIR:.tox} [testenv] deps = commands = pytest {posargs} usedevelop = True extras = testing [testenv:docs] extras = docs testing changedir = docs commands = python -m sphinx -W --keep-going . {toxinidir}/build/html [testenv:release] skip_install = True deps = build twine>=3 jaraco.develop>=7.1 passenv = TWINE_PASSWORD GITHUB_TOKEN setenv = TWINE_USERNAME = {env:TWINE_USERNAME:__token__} commands = python -c "import shutil; shutil.rmtree('dist', ignore_errors=True)" python -m build python -m twine upload dist/* python -m jaraco.develop.create-github-release