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
|
import getpass
|
||||||
from typing import Optional, Tuple
|
from typing import Optional, Tuple
|
||||||
|
|
||||||
|
# optional import for KeyringAuthenticator
|
||||||
|
try:
|
||||||
|
# pylint: disable=import-error
|
||||||
|
import keyring
|
||||||
|
except ModuleNotFoundError:
|
||||||
|
pass
|
||||||
|
|
||||||
class TfaAuthenticator:
|
class TfaAuthenticator:
|
||||||
# pylint: disable=too-few-public-methods
|
# 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:
|
if self._given_username is not None and self._given_password is not None:
|
||||||
self._given_username = None
|
self._given_username = None
|
||||||
self._given_password = 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/).
|
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,
|
from .crawler import (IliasCrawler, IliasCrawlerEntry, IliasDirectoryFilter,
|
||||||
IliasElementType)
|
IliasElementType)
|
||||||
from .downloader import (IliasDownloader, IliasDownloadInfo,
|
from .downloader import (IliasDownloader, IliasDownloadInfo,
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ from typing import Optional
|
||||||
import bs4
|
import bs4
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from ..authenticators import TfaAuthenticator, UserPassAuthenticator
|
from ..authenticators import TfaAuthenticator, UserPassAuthenticator, KeyringAuthenticator
|
||||||
from ..utils import soupify
|
from ..utils import soupify
|
||||||
|
|
||||||
LOGGER = logging.getLogger(__name__)
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
|
@ -129,3 +129,13 @@ class KitShibbolethAuthenticator(IliasAuthenticator):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _tfa_required(soup: bs4.BeautifulSoup) -> bool:
|
def _tfa_required(soup: bs4.BeautifulSoup) -> bool:
|
||||||
return soup.find(id="j_tokenNumber") is not None
|
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 import Pferd
|
||||||
from PFERD.cookie_jar import CookieJar
|
from PFERD.cookie_jar import CookieJar
|
||||||
from PFERD.ilias import (IliasCrawler, IliasElementType,
|
from PFERD.ilias import (IliasCrawler, IliasElementType,
|
||||||
KitShibbolethAuthenticator)
|
KitShibbolethAuthenticator,
|
||||||
|
KeyringKitShibbolethAuthenticator)
|
||||||
from PFERD.utils import to_path
|
from PFERD.utils import to_path
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -20,6 +21,7 @@ def main() -> None:
|
||||||
parser.add_argument("--test-run", action="store_true")
|
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('-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('--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('url', help="URL to the course page")
|
||||||
parser.add_argument('folder', nargs='?', default=None, help="Folder to put stuff into")
|
parser.add_argument('folder', nargs='?', default=None, help="Folder to put stuff into")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
@ -28,7 +30,8 @@ def main() -> None:
|
||||||
|
|
||||||
cookie_jar = CookieJar(to_path(args.cookies) if args.cookies else None)
|
cookie_jar = CookieJar(to_path(args.cookies) if args.cookies else None)
|
||||||
session = cookie_jar.create_session()
|
session = cookie_jar.create_session()
|
||||||
authenticator = KitShibbolethAuthenticator()
|
authenticator = (KeyringKitShibbolethAuthenticator() if args.keyring
|
||||||
|
else KitShibbolethAuthenticator())
|
||||||
crawler = IliasCrawler(url.scheme + '://' + url.netloc, session,
|
crawler = IliasCrawler(url.scheme + '://' + url.netloc, session,
|
||||||
authenticator, lambda x, y: True)
|
authenticator, lambda x, y: True)
|
||||||
|
|
||||||
|
|
@ -55,11 +58,14 @@ def main() -> None:
|
||||||
|
|
||||||
pferd.enable_logging()
|
pferd.enable_logging()
|
||||||
# fetch
|
# fetch
|
||||||
|
(username, password) = authenticator._auth.get_credentials()
|
||||||
pferd.ilias_kit_folder(
|
pferd.ilias_kit_folder(
|
||||||
target=folder,
|
target=folder,
|
||||||
full_url=args.url,
|
full_url=args.url,
|
||||||
cookies=args.cookies,
|
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