git.schokokeks.org
Repositories
Help
Report an Issue
derivepassphrase.git
Code
Commits
Branches
Tags
Suche
Strukturansicht:
e981744
Branches
Tags
documentation-tree
master
unstable/modularize-and-refactor-test-machinery
unstable/ssh-agent-socket-providers
wishlist
0.1.0
0.1.1
0.1.2
0.1.3
0.2.0
0.3.0
0.3.1
0.3.2
0.3.3
0.4.0
0.5.1
0.5.2
derivepassphrase.git
src
derivepassphrase
exporter
__init__.py
Change the code style to use double quotes for strings
Marco Ricci
commited
e981744
at 2025-08-05 21:11:03
__init__.py
Blame
History
Raw
# SPDX-FileCopyrightText: 2025 Marco Ricci <software@the13thletter.info> # # SPDX-License-Identifier: Zlib """Foreign configuration exporter for derivepassphrase.""" from __future__ import annotations import importlib import os import pathlib from typing import TYPE_CHECKING, Protocol if TYPE_CHECKING: from collections.abc import Callable from typing import Any from typing_extensions import Buffer __all__ = () INVALID_VAULT_NATIVE_CONFIGURATION_FORMAT = ( "Invalid vault native configuration format: {fmt!r}" ) class NotAVaultConfigError(ValueError): """The `path` does not hold a `format`-type vault configuration.""" def __init__( self, path: str | bytes | os.PathLike, format: str | None = None, # noqa: A002 ) -> None: self.path = os.fspath(path) self.format = format def __str__(self) -> str: # pragma: no cover [failsafe] """""" # noqa: D419 formatted_format = ( f"vault {self.format} configuration" if self.format else "vault configuration" ) return f"Not a {formatted_format}: {self.path!r}" def get_vault_key() -> bytes: """Automatically determine the vault(1) master key/password. Query the `VAULT_KEY`, `LOGNAME`, `USER` and `USERNAME` environment variables, in that order. This is the same algorithm that vault uses. Returns: The master key/password. This is generally used as input to a key-derivation function to determine the *actual* encryption and signing keys for the vault configuration. Raises: KeyError: We cannot find any of the named environment variables. Please set `VAULT_KEY` manually to the desired value. """ def getenv_environb(env_var: str) -> bytes: # pragma: no cover [external] return os.environb.get(env_var.encode("UTF-8"), b"") # type: ignore[attr-defined] def getenv_environ(env_var: str) -> bytes: # pragma: no cover [external] return os.environ.get(env_var, "").encode("UTF-8") getenv: Callable[[str], bytes] = ( getenv_environb if os.supports_bytes_environ else getenv_environ ) username = ( getenv("VAULT_KEY") or getenv("LOGNAME") or getenv("USER") or getenv("USERNAME") ) if not username: env_var = "VAULT_KEY" raise KeyError(env_var) return username def get_vault_path() -> pathlib.Path: """Automatically determine the vault(1) configuration path. Query the `VAULT_PATH` environment variable, or default to `~/.vault`. This is the same algorithm that vault uses. If not absolute, then `VAULT_PATH` is relative to the home directory. Returns: The vault configuration path. Depending on the vault version, this may be a file or a directory. Raises: RuntimeError: We cannot determine the home directory. Please set `HOME` manually to the correct value. """ return pathlib.Path( "~", os.environ.get("VAULT_PATH", ".vault") ).expanduser() class ExportVaultConfigDataFunction(Protocol): """Typing protocol for vault config data export handlers.""" def __call__( self, path: str | bytes | os.PathLike | None = None, key: str | Buffer | None = None, *, format: str, # noqa: A002 ) -> Any: # noqa: ANN401 """Export the full vault-native configuration stored in `path`. Args: path: The path to the vault configuration file or directory. If not given, then query [`get_vault_path`][] for the correct value. key: Encryption key/password for the configuration file or directory, usually the username, or passed via the `VAULT_KEY` environment variable. If not given, then query [`get_vault_key`][] for the value. format: The format to attempt parsing as. Must be `v0.2`, `v0.3` or `storeroom`. Returns: The vault configuration, as recorded in the configuration file. This may or may not be a valid configuration according to `vault` or `derivepassphrase`. Raises: IsADirectoryError: The requested format requires a configuration file, but `path` points to a directory instead. NotADirectoryError: The requested format requires a configuration directory, but `path` points to something else instead. OSError: There was an OS error while accessing the configuration file/directory. RuntimeError: Something went wrong during data collection, e.g. we encountered unsupported or corrupted data in the configuration file/directory. json.JSONDecodeError: An internal JSON data structure failed to parse from disk. The configuration file/directory is probably corrupted. exporter.NotAVaultConfigError: The file/directory contents are not in the claimed configuration format. ValueError: The requested format is invalid. ModuleNotFoundError: The requested format requires support code, which failed to load because of missing Python libraries. """ _export_vault_config_data_registry: dict[ str, ExportVaultConfigDataFunction, ] = {} def register_export_vault_config_data_handler( *names: str, ) -> Callable[[ExportVaultConfigDataFunction], ExportVaultConfigDataFunction]: if not names: msg = "No names given to export_data handler registry" raise ValueError(msg) if "" in names: msg = "Cannot register export_data handler under an empty name" raise ValueError(msg) def wrapper( f: ExportVaultConfigDataFunction, ) -> ExportVaultConfigDataFunction: for name in names: if name in _export_vault_config_data_registry: msg = f"export_data handler already registered: {name!r}" raise ValueError(msg) _export_vault_config_data_registry[name] = f return f return wrapper def find_vault_config_data_handlers() -> None: """Find all export handlers for vault config data. (This function is idempotent.) Raises: ModuleNotFoundError: A required module was not found. """ # Defer imports (and handler registrations) to avoid circular # imports. The modules themselves contain function definitions that # register themselves automatically with # `_export_vault_config_data_registry`. importlib.import_module("derivepassphrase.exporter.storeroom") importlib.import_module("derivepassphrase.exporter.vault_native") def export_vault_config_data( path: str | bytes | os.PathLike | None = None, key: str | Buffer | None = None, *, format: str, # noqa: A002 ) -> Any: # noqa: ANN401 """Export the full vault-native configuration stored in `path`. See [`ExportVaultConfigDataFunction`][] for an explanation of the call signature, and the exceptions to expect. """ # noqa: DOC201,DOC501 find_vault_config_data_handlers() handler = _export_vault_config_data_registry.get(format) if handler is None: msg = INVALID_VAULT_NATIVE_CONFIGURATION_FORMAT.format(fmt=format) raise ValueError(msg) return handler(path, key, format=format)