karl/app/services/passwords.py

73 lines
2.1 KiB
Python

import os.path
from contextlib import contextmanager
from typing import Any, Generator
import keyring
from injectable import injectable
from pykeepass import PyKeePass, create_database
class KeyRequest:
def __init__(self, prompt: str):
self.field_name = None
self.entry_name = None
self.path = None
self._parse_prompt(prompt)
def _parse_prompt(self, prompt: str):
prompt_parts = prompt.split("/")
key = None
match len(prompt_parts):
case 1:
self.field_name = 'password'
key = prompt_parts[0]
case 2:
self.field_name = prompt_parts[1]
key = prompt_parts[0]
case _:
key = None
if key is None:
return
key_parts = key.split(".")
self.path = key_parts[:] if len(key_parts) > 1 else None
self.entry_name = key_parts[-1]
@injectable(singleton=True)
class Passwords:
def __init__(self):
from app.config import get_settings
settings = get_settings()
with open(settings.kp.secret, "r") as fh:
keyring.set_password("karl", "kp", fh.read().splitlines()[0])
self._path = settings.kp.file
@contextmanager
def open(self, mode: str = "r") -> Generator[PyKeePass | Any, Any, None]:
kp = PyKeePass(self._path, password=keyring.get_password("karl", "kp")) \
if os.path.exists(self._path) else create_database(self._path, password=keyring.get_password("karl", "kp"))
yield kp
if mode == "rw":
kp.save()
def get_values(self, keys: list[str]) -> dict[str, str]:
output = {}
for k in keys:
request = KeyRequest(k)
with self.open() as kp:
kp_entry = kp.find_entries(path=request.path, first=True, title=request.entry_name)
output[k] = self._get_field_value(kp_entry, request.field_name)
return output
@staticmethod
def _get_field_value(kp_entry, field_name):
if kp_entry is None:
return None
match field_name:
case "username":
return kp_entry.username
case "password":
return kp_entry.password
case "url":
return kp_entry.url
case _:
return kp_entry.get_custom_property(field_name)