From 6e9f8fd39107ce2ca0a11b5dd9f08b9d7dfa7cf2 Mon Sep 17 00:00:00 2001 From: I-Al-Istannen Date: Sun, 23 May 2021 19:44:12 +0200 Subject: [PATCH] Add a keyring authenticator --- CONFIG.md | 9 +++++ PFERD/auth/__init__.py | 3 ++ PFERD/auth/keyring_authenticator.py | 56 +++++++++++++++++++++++++++++ setup.cfg | 1 + 4 files changed, 69 insertions(+) create mode 100644 PFERD/auth/keyring_authenticator.py diff --git a/CONFIG.md b/CONFIG.md index e92858f..bd3baca 100644 --- a/CONFIG.md +++ b/CONFIG.md @@ -154,6 +154,15 @@ This authenticator prompts the user on the console for a two-factor authentication token. The token is provided as password and it is not cached. This authenticator does not support usernames. +### The `keyring` authenticator + +This authenticator uses the system keyring to store passwords. It expects a +username in the config and will prompt *once* for the password. After that it +receives the password from the system keyring. + +- `username`: The username. (Required) +- `keyring_name`: The service name PFERD uses for storing credentials. (Optional) + ## Transformation rules Transformation rules are rules for renaming and excluding files and directories. diff --git a/PFERD/auth/__init__.py b/PFERD/auth/__init__.py index 6247e2b..81ec31d 100644 --- a/PFERD/auth/__init__.py +++ b/PFERD/auth/__init__.py @@ -3,6 +3,7 @@ from typing import Callable, Dict from ..config import Config from .authenticator import Authenticator, AuthSection +from .keyring_authenticator import KeyringAuthenticator, KeyringAuthSection from .simple import SimpleAuthenticator, SimpleAuthSection from .tfa import TfaAuthenticator @@ -17,4 +18,6 @@ AUTHENTICATORS: Dict[str, AuthConstructor] = { SimpleAuthenticator(n, SimpleAuthSection(s), c), "tfa": lambda n, s, c: TfaAuthenticator(n, AuthSection(s), c), + "keyring": lambda n, s, c: + KeyringAuthenticator(n, KeyringAuthSection(s), c) } diff --git a/PFERD/auth/keyring_authenticator.py b/PFERD/auth/keyring_authenticator.py new file mode 100644 index 0000000..413c7ad --- /dev/null +++ b/PFERD/auth/keyring_authenticator.py @@ -0,0 +1,56 @@ +from typing import Optional, Tuple + +import keyring + +from ..config import Config +from ..logging import log +from ..utils import agetpass +from ..version import NAME +from .authenticator import Authenticator, AuthException, AuthSection + + +class KeyringAuthSection(AuthSection): + def username(self) -> str: + name = self.s.get("username") + if name is None: + self.missing_value("username") + return name + + def keyring_name(self) -> str: + return self.s.get("keyring_name", fallback=NAME) + + +class KeyringAuthenticator(Authenticator): + + def __init__( + self, + name: str, + section: KeyringAuthSection, + config: Config, + ) -> None: + super().__init__(name, section, config) + + self._username = section.username() + self._password: Optional[str] = None + self._keyring_name = section.keyring_name() + + async def credentials(self) -> Tuple[str, str]: + if self._password is not None: + return self._username, self._password + + password = keyring.get_password(self._keyring_name, self._username) + + if not password: + async with log.exclusive_output(): + password = await agetpass("Password: ") + keyring.set_password(self._keyring_name, self._username, password) + + self._password = password + + return self._username, password + + def invalidate_credentials(self) -> None: + self.invalidate_password() + + def invalidate_password(self) -> None: + raise AuthException("Invalid password") diff --git a/setup.cfg b/setup.cfg index 431c3b9..1cbfc6a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -9,6 +9,7 @@ install_requires = aiohttp>=3.7.4.post0 beautifulsoup4>=4.9.3 rich>=10.1.0 + keyring>=23.0.1 [options.entry_points] console_scripts =