Source code for bowtie.auth

"""Authentication out of the box.

References
----------
https://stackoverflow.com/questions/13428708/best-way-to-make-flask-logins-login-required-the-default
https://stackoverflow.com/questions/14367991/flask-before-request-add-exception-for-specific-route

"""

from typing import Dict, Optional
from abc import ABC, abstractmethod

from flask import Response, request, session

from bowtie._app import App


[docs]class Auth(ABC): """Abstract Authentication class.""" def __init__(self, app: App) -> None: """Create Auth class to protect flask routes and socketio connect.""" self.app = app self.app.app.before_request(self.before_request) # only need to check credentials on "connect" event self.app._socketio.on('connect')(self.socketio_auth) # pylint: disable=protected-access
[docs] @abstractmethod def before_request(self): """Determine if a user is allowed to view this route. Name is subject to change. Returns ------- None, if no protection is needed. """
[docs] @abstractmethod def socketio_auth(self) -> bool: """Determine if a user is allowed to establish socketio connection. Name is subject to change. """
[docs]class BasicAuth(Auth): """Basic Authentication.""" def __init__(self, app: App, credentials: Dict[str, str]) -> None: """ Create basic auth with credentials. Parameters ---------- credentials : dict Usernames and passwords should be passed in as a dictionary. Examples -------- >>> from bowtie import App >>> from bowtie.auth import BasicAuth >>> app = App(__name__) >>> auth = BasicAuth(app, {'alice': 'secret1', 'bob': 'secret2'}) """ self.credentials = credentials super().__init__(app) def _check_auth(self, username: str, password: str) -> bool: """Check if a username/password combination is valid.""" try: return self.credentials[username] == password except KeyError: return False
[docs] def socketio_auth(self) -> bool: """Determine if a user is allowed to establish socketio connection.""" try: return session['logged_in'] in self.credentials except KeyError: return False
[docs] def before_request(self) -> Optional[Response]: """Determine if a user is allowed to view this route.""" auth = request.authorization if not auth or not self._check_auth(auth.username, auth.password): return Response( 'Could not verify your access level for that URL.\n' 'You have to login with proper credentials', 401, {'WWW-Authenticate': 'Basic realm="Login Required"'} ) session['logged_in'] = auth.username # pylint wants this return statement return None