diff --git a/Makefile b/Makefile index 64222c2..65d4c33 100644 --- a/Makefile +++ b/Makefile @@ -3,6 +3,7 @@ relgnupghome ::= test/.gnupghome export GNUPGHOME ::= $(projectdir)/$(relgnupghome) gpg_key_id ::= "8c2a59a7" relpassstore ::= test/.test-password-store +pass ::= pypass export PASSWORD_STORE_DIR ::= $(projectdir)/$(relpassstore) .PHONY: all test coverage style clean clean-pycache clean-build @@ -10,7 +11,7 @@ export PASSWORD_STORE_DIR ::= $(projectdir)/$(relpassstore) all: style test test: | $(relpassstore) - dbus-run-session -- pytest-3 -v test + dbus-run-session -- pytest-3 -v test --asyncio-mode=auto coverage: | $(relpassstore) dbus-run-session -- python3 -m coverage run -m pytest -v test @@ -28,7 +29,7 @@ $(relgnupghome): test/test_key.asc test/test_ownertrust.txt $(relpassstore): | $(relgnupghome) @echo "===== Preparing password store in $(relpassstore) =====" - pypass init -p $(relpassstore) $(gpg_key_id) + $(pass) init -p $(relpassstore) $(gpg_key_id) clean: clean-test-environment clean-pycache clean-build diff --git a/pass_secret_service/common/native_pass.py b/pass_secret_service/common/native_pass.py new file mode 100644 index 0000000..b1a06aa --- /dev/null +++ b/pass_secret_service/common/native_pass.py @@ -0,0 +1,30 @@ +import subprocess +import os + +DEFAULT_PASS = "pass" + +class NativePasswordStore: + def __init__(self, use_pass=None, path=None): + self.pass_cmd = use_pass or DEFAULT_PASS + self.path = path + + def _pass(self, *args, **kwargs): + env = os.environ + if self.path is not None: + env.update({'PASSWORD_STORE_DIR': self.path}) + + proc = subprocess.run([self.pass_cmd, *args], + check=True, + text=True, + capture_output=True, + env=env, + **kwargs + ) + + return proc + + def get_decrypted_password(self, passname): + return self._pass("show", passname).stdout.removesuffix("\n") + + def insert_password(self, passname, password): + self._pass("insert", "--echo", passname, input=password) diff --git a/pass_secret_service/common/pass_store.py b/pass_secret_service/common/pass_store.py index c1aa175..4f13438 100644 --- a/pass_secret_service/common/pass_store.py +++ b/pass_secret_service/common/pass_store.py @@ -2,19 +2,30 @@ import shutil import uuid import json -from pypass import PasswordStore +try: + from pypass import PasswordStore -# Work around a typo in pypass -if not hasattr(PasswordStore, "get_decrypted_password"): - PasswordStore.get_decrypted_password = PasswordStore.get_decypted_password + # Work around a typo in pypass + if not hasattr(PasswordStore, "get_decrypted_password"): + PasswordStore.get_decrypted_password = PasswordStore.get_decypted_password + +except ImportError: + from .native_pass import NativePasswordStore + PasswordStore = NativePasswordStore class PassStore: PREFIX = "secret_service" - def __init__(self, *args, **kwargs): - self._store = PasswordStore(*args, **kwargs) + def __init__(self, *args, use_pass=None, **kwargs): + if not use_pass: + self._store = PasswordStore(*args, **kwargs) + + else: + from .native_pass import NativePasswordStore + self._store = NativePasswordStore(use_pass=use_pass, **kwargs) + self.base_path = os.path.join(self._store.path, self.PREFIX) if not os.path.exists(self.base_path): os.makedirs(self.base_path) diff --git a/pass_secret_service/pass_secret_service.py b/pass_secret_service/pass_secret_service.py index 8c35db9..7dbcdf0 100755 --- a/pass_secret_service/pass_secret_service.py +++ b/pass_secret_service/pass_secret_service.py @@ -30,10 +30,10 @@ async def register_service(pass_store): return service -def _main(path, verbose): +def _main(path, pass_, verbose): if verbose: logging.basicConfig(level=20) - pass_store = PassStore(**({"path": path} if path else {})) + pass_store = PassStore(use_pass=pass_, **({"path": path} if path else {})) mainloop = asyncio.get_event_loop() mainloop.add_signal_handler(signal.SIGTERM, functools.partial(term_loop, mainloop)) mainloop.add_signal_handler(signal.SIGINT, functools.partial(term_loop, mainloop)) @@ -51,9 +51,10 @@ def _main(path, verbose): @click.command() @click.option("--path", help="path to the password store (optional)") +@click.option("-e", "pass_", help="use given pass executable") @click.option("-v", "--verbose", help="be verbose", is_flag=True, default=False) -def main(path, verbose): - _main(path, verbose) +def main(path, pass_, verbose): + _main(path, pass_, verbose) if __name__ == "__main__": # pragma: no cover