Source code for arxiv.users.auth
"""Provides tools for working with authenticated user/client sessions."""
from typing import Optional, Union
from flask import Flask, request
from . import decorators, middleware, scopes, tokens
from .. import domain, legacy
from arxiv.base import logging
logger = logging.getLogger(__name__)
[docs]class Auth(object):
    """
    Attaches session and authn/z information to the request.
    Intended for use in a Flask application factory, for example:
    .. code-block:: python
       from flask import Flask
       from arxiv.users.auth import Auth
       from someapp import routes
       def create_web_app() -> Flask:
          app = Flask('someapp')
          app.config.from_pyfile('config.py')
          Auth(app)   # Registers the base/UI blueprint.
          app.register_blueprint(routes.blueprint)    # Your blueprint.
       return app
    """
    def __init__(self, app: Optional[Flask] = None) -> None:
        """
        Initialize ``app`` with base blueprint.
        Parameters
        ----------
        app : :class:`Flask`
        """
        if app is not None:
            self.init_app(app)
    def _get_legacy_session(self) -> Optional[domain.Session]:
        """
        Attempt to load a legacy auth session.
        Returns
        -------
        :class:`domain.Session` or None
        """
        classic_cookie_key = self.app.config['CLASSIC_COOKIE_NAME']
        classic_cookie = request.cookies.get(classic_cookie_key, None)
        if classic_cookie is None:
            return None
        try:
            return legacy.sessions.load(classic_cookie)
        except legacy.exceptions.UnknownSession as e:
            logger.debug('No legacy session available')
        except legacy.exceptions.InvalidCookie as e:
            logger.debug('Invalid legacy cookie')
        except legacy.exceptions.SessionExpired as e:
            logger.debug('Legacy session is expired')
        return None
[docs]    def init_app(self, app: Flask) -> None:
        """
        Attach :meth:`.load_session` to the Flask app.
        Parameters
        ----------
        app : :class:`Flask`
        """
        self.app = app
        self.app.before_request(self.load_session) 
[docs]    def load_session(self) -> None:
        """
        Look for an active session, and attach it to the request.
        The typical scenario will involve the
        :class:`.middleware.AuthMiddleware` unpacking a session token and
        adding it to the WSGI request environ. As a fallback, if the legacy
        database is available, this method will also attempt to load an
        active legacy session.
        """
        # Check the WSGI request environ for the ``session`` key, which
        # is where the auth middleware puts any unpacked auth information from
        # the request OR any exceptions that need to be raised withing the
        # request context.
        session: Optional[Union[domain.Session, Exception]] = \
            
request.environ.get('session')
        # Middlware may have passed an exception, which needs to be raised
        # within the app/execution context to be handled correctly.
        if isinstance(session, Exception):
            logger.debug('Middleware passed an exception: %s', session)
            raise session
        # If we don't see a session, we may not be deployed behind a
        # gateway with an authorization service. If the legacy database
        # is available, we can try to use that as a fall-back.
        if not session and legacy.is_configured():
            logger.debug('No session; attempting to get legacy session')
            session = self._get_legacy_session()
        # Attach the session to the request so that other
        # components can access it easily.
        request.session = session