From ff2cddbf65cb7d77bef185345679ab3c562a6324 Mon Sep 17 00:00:00 2001 From: Daniel Dizdarevic Date: Fri, 28 Apr 2023 14:37:24 +0200 Subject: [PATCH] KeePass support --- README.md | 8 ++++++++ pyproject.toml | 6 ++++++ src/ldaptool/search/config.py | 34 +++++++++++++++++++++++++++++++++- 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9b7639f..63600f8 100644 --- a/README.md +++ b/README.md @@ -88,3 +88,11 @@ This configures [`keyringer`](https://0xacab.org/rhatto/keyringer) (based on GPG `keyringer` need a "keyring" to search in, and you can (optionally) specify a folder to be prefixed to the password names created from the realm. + +#### keepass + +```yaml +keepass: /home/me/mypasswords.kdbx +``` + +This configures KeePass as password manager; it will prompt for your master password every time. diff --git a/pyproject.toml b/pyproject.toml index 0b796a7..3e9746e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,6 +22,11 @@ dependencies = [ "PyYAML", ] +[project.optional-dependencies] +keepass = [ + "pykeepass", +] + [project.scripts] ldaptool = "ldaptool._main:main" @@ -46,5 +51,6 @@ module = [ "ldap", "ldap.dn", "ldap.controls.libldap", + "pykeepass", ] ignore_missing_imports = true diff --git a/src/ldaptool/search/config.py b/src/ldaptool/search/config.py index de32d8f..438d980 100644 --- a/src/ldaptool/search/config.py +++ b/src/ldaptool/search/config.py @@ -2,6 +2,7 @@ from __future__ import annotations import abc import dataclasses +import getpass import os import os.path import shlex @@ -122,6 +123,34 @@ class Keyringer(PasswordManager): return result.stdout.strip() +@dataclasses.dataclass +class Keepass(PasswordManager): + database: str + + @staticmethod + def load(data: typing.Any) -> Keepass: + try: + import pykeepass # make sure dependency is present during config loading + + assert pykeepass # so it isn't unused... + except ImportError: + raise Exception( + "Missing (optional) dependency for keepass support - please install (python package for) pykeepass" + ) + assert isinstance( + data, str + ), "keepass integration expects a single string as configuration with the database location" + return Keepass(database=data) + + def get_password(self, password_name: str) -> str: + import pykeepass # already made sure it is avaiable above + + password = getpass.getpass(f"KeePass password for database {self.database}: ") + kp = pykeepass.PyKeePass(self.database, password=password) + entry = kp.find_entries(username=password_name, first=True) + return entry.password # type: ignore + + @dataclasses.dataclass class PasswordScript(PasswordManager): command: list[str] @@ -173,6 +202,10 @@ class Config: if password_manager: raise ValueError("Can only set a single password manager") password_manager = Keyringer.load(data.pop("keyringer")) + if "keepass" in data: + if password_manager: + raise ValueError("Can only set a single password manager") + password_manager = Keepass.load(data.pop("keepass")) if "password-script" in data: if password_manager: raise ValueError("Can only set a single password manager") @@ -189,6 +222,5 @@ class Config: raise RuntimeError("Can't get password without acccount - should use kerberos instead") if self.password_manager: return self.password_manager.get_password(realm.password_name) - import getpass return getpass.getpass(f"Enter password for {realm.password_name}: ")