mirror of
https://github.com/Garmelon/PFERD.git
synced 2026-04-13 07:55:05 +02:00
Use system keyring service for password auth
This commit is contained in:
parent
f4abe3197c
commit
b174b5020a
4 changed files with 107 additions and 5 deletions
|
|
@ -5,6 +5,12 @@ General authenticators useful in many situations
|
|||
import getpass
|
||||
from typing import Optional, Tuple
|
||||
|
||||
# optional import for KeyringAuthenticator
|
||||
try:
|
||||
# pylint: disable=import-error
|
||||
import keyring
|
||||
except ModuleNotFoundError:
|
||||
pass
|
||||
|
||||
class TfaAuthenticator:
|
||||
# pylint: disable=too-few-public-methods
|
||||
|
|
@ -123,3 +129,82 @@ 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,
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ from typing import Optional
|
|||
import bs4
|
||||
import requests
|
||||
|
||||
from ..authenticators import TfaAuthenticator, UserPassAuthenticator
|
||||
from ..authenticators import TfaAuthenticator, UserPassAuthenticator, KeyringAuthenticator
|
||||
from ..utils import soupify
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
|
@ -129,3 +129,13 @@ class KitShibbolethAuthenticator(IliasAuthenticator):
|
|||
@staticmethod
|
||||
def _tfa_required(soup: bs4.BeautifulSoup) -> bool:
|
||||
return soup.find(id="j_tokenNumber") is not None
|
||||
|
||||
|
||||
class KeyringKitShibbolethAuthenticator(KitShibbolethAuthenticator):
|
||||
"""
|
||||
Authenticate via KIT's shibboleth system using the system keyring service.
|
||||
"""
|
||||
|
||||
def __init__(self, username: Optional[str] = None, password: Optional[str] = None) -> None:
|
||||
super().__init__()
|
||||
self._auth = KeyringAuthenticator("KIT ILIAS Shibboleth", username, password)
|
||||
|
|
|
|||
12
sync_url.py
12
sync_url.py
|
|
@ -11,7 +11,8 @@ from urllib.parse import urlparse
|
|||
from PFERD import Pferd
|
||||
from PFERD.cookie_jar import CookieJar
|
||||
from PFERD.ilias import (IliasCrawler, IliasElementType,
|
||||
KitShibbolethAuthenticator)
|
||||
KitShibbolethAuthenticator,
|
||||
KeyringKitShibbolethAuthenticator)
|
||||
from PFERD.utils import to_path
|
||||
|
||||
|
||||
|
|
@ -20,6 +21,7 @@ def main() -> None:
|
|||
parser.add_argument("--test-run", action="store_true")
|
||||
parser.add_argument('-c', '--cookies', nargs='?', default=None, help="File to store cookies in")
|
||||
parser.add_argument('--no-videos', nargs='?', default=None, help="Don't download videos")
|
||||
parser.add_argument("-k", "--keyring", action="store_true", help="Use the system keyring service for authentication")
|
||||
parser.add_argument('url', help="URL to the course page")
|
||||
parser.add_argument('folder', nargs='?', default=None, help="Folder to put stuff into")
|
||||
args = parser.parse_args()
|
||||
|
|
@ -28,7 +30,8 @@ def main() -> None:
|
|||
|
||||
cookie_jar = CookieJar(to_path(args.cookies) if args.cookies else None)
|
||||
session = cookie_jar.create_session()
|
||||
authenticator = KitShibbolethAuthenticator()
|
||||
authenticator = (KeyringKitShibbolethAuthenticator() if args.keyring
|
||||
else KitShibbolethAuthenticator())
|
||||
crawler = IliasCrawler(url.scheme + '://' + url.netloc, session,
|
||||
authenticator, lambda x, y: True)
|
||||
|
||||
|
|
@ -55,11 +58,14 @@ def main() -> None:
|
|||
|
||||
pferd.enable_logging()
|
||||
# fetch
|
||||
(username, password) = authenticator._auth.get_credentials()
|
||||
pferd.ilias_kit_folder(
|
||||
target=folder,
|
||||
full_url=args.url,
|
||||
cookies=args.cookies,
|
||||
dir_filter=dir_filter
|
||||
dir_filter=dir_filter,
|
||||
username=username,
|
||||
password=password
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue