mirror of
https://github.com/Garmelon/PFERD.git
synced 2023-12-21 10:23:01 +01:00
Use system keyring service for password auth
This commit is contained in:
parent
75471c46d1
commit
83ea15ee83
4
LICENSE
4
LICENSE
@ -1,4 +1,8 @@
|
||||
<<<<<<< HEAD
|
||||
Copyright 2019-2020 Garmelon, I-Al-Istannen, danstooamerican, pavelzw, TheChristophe
|
||||
=======
|
||||
Copyright 2019-2020 Garmelon, I-Al-Istannen, danstooamerican, pavelzw, Scriptim
|
||||
>>>>>>> f89226c (Use system keyring service for password auth)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
|
@ -3,8 +3,19 @@ General authenticators useful in many situations
|
||||
"""
|
||||
|
||||
import getpass
|
||||
import logging
|
||||
from typing import Optional, Tuple
|
||||
|
||||
from .logging import PrettyLogger
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
PRETTY = PrettyLogger(LOGGER)
|
||||
|
||||
try:
|
||||
import keyring
|
||||
except ImportError:
|
||||
PRETTY.warning("Keyring module not found, KeyringAuthenticator won't work!")
|
||||
|
||||
|
||||
class TfaAuthenticator:
|
||||
# pylint: disable=too-few-public-methods
|
||||
@ -123,3 +134,81 @@ class UserPassAuthenticator:
|
||||
if self._given_username is not None and self._given_password is not None:
|
||||
self._given_username = None
|
||||
self._given_password = None
|
||||
|
||||
|
||||
class KeyringAuthenticator(UserPassAuthenticator):
|
||||
"""
|
||||
An authenticator for username-password combinations that stores the
|
||||
password using the system keyring service and prompts the user for missing
|
||||
information.
|
||||
"""
|
||||
|
||||
def get_credentials(self) -> Tuple[str, str]:
|
||||
"""
|
||||
Returns a tuple (username, password). Prompts user for username or
|
||||
password when necessary.
|
||||
"""
|
||||
|
||||
if self._username is None and self._given_username is not None:
|
||||
self._username = self._given_username
|
||||
|
||||
if self._password is None and self._given_password is not None:
|
||||
self._password = self._given_password
|
||||
|
||||
if self._username is not None and self._password is None:
|
||||
self._load_password()
|
||||
|
||||
if self._username is None or self._password is None:
|
||||
print(f"Enter credentials ({self._reason})")
|
||||
|
||||
username: str
|
||||
if self._username is None:
|
||||
username = input("Username: ")
|
||||
self._username = username
|
||||
else:
|
||||
username = self._username
|
||||
|
||||
if self._password is None:
|
||||
self._load_password()
|
||||
|
||||
password: str
|
||||
if self._password is None:
|
||||
password = getpass.getpass(prompt="Password: ")
|
||||
self._password = password
|
||||
self._save_password()
|
||||
else:
|
||||
password = self._password
|
||||
|
||||
return (username, password)
|
||||
|
||||
def _load_password(self) -> None:
|
||||
"""
|
||||
Loads the saved password associated with self._username from the system
|
||||
keyring service (or None if not password has been saved yet) and stores
|
||||
it in self._password.
|
||||
"""
|
||||
self._password = keyring.get_password("pferd-ilias", self._username)
|
||||
|
||||
def _save_password(self) -> None:
|
||||
"""
|
||||
Saves self._password to the system keyring service and associates it
|
||||
with self._username.
|
||||
"""
|
||||
keyring.set_password("pferd-ilias", self._username, self._password)
|
||||
|
||||
def invalidate_credentials(self) -> None:
|
||||
"""
|
||||
Marks the credentials as invalid. If only a username was supplied in
|
||||
the constructor, assumes that the username is valid and only the
|
||||
password is invalid. If only a password was supplied in the
|
||||
constructor, assumes that the password is valid and only the username
|
||||
is invalid. Otherwise, assumes that username and password are both
|
||||
invalid.
|
||||
"""
|
||||
|
||||
try:
|
||||
keyring.delete_password("pferd-ilias", self._username)
|
||||
except keyring.errors.PasswordDeleteError:
|
||||
pass
|
||||
|
||||
super().invalidate_credentials()
|
||||
|
@ -2,7 +2,8 @@
|
||||
Synchronizing files from ILIAS instances (https://www.ilias.de/).
|
||||
"""
|
||||
|
||||
from .authenticators import IliasAuthenticator, KitShibbolethAuthenticator
|
||||
from .authenticators import (IliasAuthenticator, KitShibbolethAuthenticator,
|
||||
KeyringKitShibbolethAuthenticator)
|
||||
from .crawler import (IliasCrawler, IliasCrawlerEntry, IliasDirectoryFilter,
|
||||
IliasElementType)
|
||||
from .downloader import (IliasDownloader, IliasDownloadInfo,
|
||||
|
@ -37,8 +37,12 @@ class KitShibbolethAuthenticator(IliasAuthenticator):
|
||||
Authenticate via KIT's shibboleth system.
|
||||
"""
|
||||
|
||||
def __init__(self, username: Optional[str] = None, password: Optional[str] = None) -> None:
|
||||
self._auth = UserPassAuthenticator("KIT ILIAS Shibboleth", username, password)
|
||||
def __init__(self, authenticator: Optional[UserPassAuthenticator] = None) -> None:
|
||||
if authenticator:
|
||||
self._auth = authenticator
|
||||
else:
|
||||
self._auth = UserPassAuthenticator("KIT ILIAS Shibboleth")
|
||||
|
||||
self._tfa_auth = TfaAuthenticator("KIT ILIAS Shibboleth")
|
||||
|
||||
def authenticate(self, sess: requests.Session) -> None:
|
||||
|
@ -1,3 +1,4 @@
|
||||
requests>=2.21.0
|
||||
beautifulsoup4>=4.7.1
|
||||
rich>=2.1.0
|
||||
keyring>=21.5.0
|
3
setup.py
3
setup.py
@ -7,7 +7,8 @@ setup(
|
||||
install_requires=[
|
||||
"requests>=2.21.0",
|
||||
"beautifulsoup4>=4.7.1",
|
||||
"rich>=2.1.0"
|
||||
"rich>=2.1.0",
|
||||
"keyring>=21.5.0"
|
||||
],
|
||||
)
|
||||
|
||||
|
27
sync_url.py
27
sync_url.py
@ -8,10 +8,11 @@ import argparse
|
||||
import logging
|
||||
import sys
|
||||
from pathlib import Path, PurePath
|
||||
from typing import Optional, Tuple
|
||||
from typing import Optional
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from PFERD import Pferd
|
||||
from PFERD.authenticators import KeyringAuthenticator, UserPassAuthenticator
|
||||
from PFERD.cookie_jar import CookieJar
|
||||
from PFERD.ilias import (IliasCrawler, IliasElementType,
|
||||
KitShibbolethAuthenticator)
|
||||
@ -25,9 +26,9 @@ _LOGGER = logging.getLogger("sync_url")
|
||||
_PRETTY = PrettyLogger(_LOGGER)
|
||||
|
||||
|
||||
def _extract_credentials(file_path: Optional[str]) -> Tuple[Optional[str], Optional[str]]:
|
||||
def _extract_credentials(file_path: Optional[str]) -> UserPassAuthenticator:
|
||||
if not file_path:
|
||||
return (None, None)
|
||||
return UserPassAuthenticator("KIT ILIAS Shibboleth", None, None)
|
||||
|
||||
if not Path(file_path).exists():
|
||||
_PRETTY.error("Credential file does not exist")
|
||||
@ -39,7 +40,7 @@ def _extract_credentials(file_path: Optional[str]) -> Tuple[Optional[str], Optio
|
||||
|
||||
name = read_name if read_name else None
|
||||
password = read_password[0] if read_password else None
|
||||
return (name, password)
|
||||
return UserPassAuthenticator("KIT ILIAS Shibboleth", username=name, password=password)
|
||||
|
||||
|
||||
def _resolve_remote_first(_path: PurePath, _conflict: ConflictType) -> FileConflictResolution:
|
||||
@ -71,6 +72,8 @@ def main() -> None:
|
||||
parser.add_argument('--credential-file', nargs='?', default=None,
|
||||
help="Path to a file containing credentials for Ilias. The file must have "
|
||||
"one line in the following format: '<user>:<password>'")
|
||||
parser.add_argument("-k", "--keyring", action="store_true",
|
||||
help="Use the system keyring service for authentication")
|
||||
parser.add_argument('--no-videos', nargs='?', default=None, help="Don't download videos")
|
||||
parser.add_argument('--local-first', action="store_true",
|
||||
help="Don't prompt for confirmation, keep existing files")
|
||||
@ -85,10 +88,21 @@ def main() -> None:
|
||||
cookie_jar = CookieJar(to_path(args.cookies) if args.cookies else None)
|
||||
session = cookie_jar.create_session()
|
||||
|
||||
username, password = _extract_credentials(args.credential_file)
|
||||
authenticator = KitShibbolethAuthenticator(username=username, password=password)
|
||||
if args.keyring:
|
||||
if not args.username:
|
||||
_PRETTY.error("Keyring auth selected but no --username passed!")
|
||||
return
|
||||
inner_auth: UserPassAuthenticator = KeyringAuthenticator(
|
||||
"KIT ILIAS Shibboleth", username=args.username, password=args.password
|
||||
)
|
||||
else:
|
||||
inner_auth = _extract_credentials(args.credential_file)
|
||||
|
||||
username, password = inner_auth.get_credentials()
|
||||
authenticator = KitShibbolethAuthenticator(inner_auth)
|
||||
|
||||
url = urlparse(args.url)
|
||||
|
||||
crawler = IliasCrawler(url.scheme + '://' + url.netloc, session,
|
||||
authenticator, lambda x, y: True)
|
||||
|
||||
@ -125,6 +139,7 @@ def main() -> None:
|
||||
file_confilict_resolver = resolve_prompt_user
|
||||
|
||||
pferd.enable_logging()
|
||||
|
||||
# fetch
|
||||
pferd.ilias_kit_folder(
|
||||
target=target,
|
||||
|
Loading…
Reference in New Issue
Block a user