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