Source code for zensols.config.keychain
"""Get passwords from the macOS Keychain.app, and optionally add as a
configuraiton.
"""
__author__ = 'Paul Landes'
from dataclasses import dataclass, field
import logging
import os
from frozendict import frozendict
from . import DictionaryConfig
logger = logging.getLogger(__name__)
[docs]
@dataclass
class Keychain(object):
"""A wrapper to macOS's Keychain service using binary ``/usr/bin/security``.
This provides a cleartext password for the given service and account.
"""
account: str = field()
"""The account, which is usually an email address."""
service: str = field(default='python-passwords')
"""the service (grouping in Keychain.app)"""
[docs]
@staticmethod
def getpassword(account: str, service: str) -> str:
"""Get the password for the account and service (see class docs).
"""
cmd = ('/usr/bin/security find-generic-password ' +
f'-w -s {service} -a {account}')
with os.popen(cmd) as p:
s = p.read().strip()
return s
@property
def password(self):
"""Get the password for the account and service provided as member
variables (see class docs).
"""
logger.debug(f'getting password for service={self.service}, ' +
f'account={self.account}')
return self.getpassword(self.account, self.service)
[docs]
class KeychainConfig(DictionaryConfig):
"""A configuration that adds a user and password based on a macOS
Keychain.app entry. The account (user name) and service (a grouping in
Keychain.app) is provided and the password is fetched.
Example::
[import]
sections = list: keychain_imp
[keychain_imp]
type = keychain
account = my-user-name
default_section = login
"""
[docs]
def __init__(self, account: str, user: str = None,
service: str = 'python-passwords',
default_section: str = 'keychain'):
"""Initialize.
:param account: the account (usually an email address) used to fetch in
Keychain.app
:param user: the name of the user to use in the generated entry, which
defaults to ``acount``
:param service: the service (grouping in Keychain.app)
:param default_section: used as the default section when non given on
the get methds such as :meth:`get_option`
"""
super().__init__(default_section=default_section)
keychain = Keychain(account, service)
conf = {self.default_section:
{'user': account if user is None else user,
'password': keychain.password}}
self._dict_config = frozendict(conf)