Source code for sanic_auth

# -*- coding: utf-8 -*-
from collections import namedtuple
from functools import partial, wraps
from inspect import isawaitable

from sanic import response

__version__ = '0.4.0.dev0'

__all__ = ['Auth', 'User']


#: A User proxy type, used by default implementation of :meth:`Auth.load_user`
User = namedtuple('User', 'id name'.split())


[docs] class Auth: """Authentication Manager.""" def __init__(self, app=None): self.app = None if app is not None: self.setup(app)
[docs] def setup(self, app): """Setup with application's configuration. This method be called automatically if the application is provided upon initialization """ if self.app is not None: raise RuntimeError('already initialized with an application') self.app = app get = app.config.get self.login_endpoint = get('AUTH_LOGIN_ENDPOINT', 'auth.login') self.login_url = get('AUTH_LOGIN_URL', None) session = get('AUTH_SESSION_NAME', get('SESSION_NAME', 'session')) self.session_name = session self.auth_session_key = get('AUTH_TOKEN_NAME', '_auth')
[docs] def login_user(self, request, user): """Log in a user. The user object will be serialized with :meth:`Auth.serialize` and the result, usually a token representing the logged in user, will be placed into the request session. """ self.get_session(request)[self.auth_session_key] = self.serialize(user)
[docs] def logout_user(self, request): """Log out any logged in user in this session. Return the user token or :code:`None` if no user logged in. """ return self.get_session(request).pop(self.auth_session_key, None)
[docs] def current_user(self, request): """Get the current logged in user. Return :code:`None` if no user logged in. """ token = self.get_session(request).get(self.auth_session_key, None) if token is not None: return self.load_user(token)
[docs] def login_required(self, route=None, *, user_keyword=None, handle_no_auth=None): """Decorator to make routes only accessible with authenticated user. Redirect visitors to login view if no user logged in. :param route: the route handler to be protected :param user_keyword: keyword only arugment, if it is not :code:`None`, and set to a string representing a valid python identifier, a user object loaded by :meth:`load_user` will be injected into the route handler's arguments. This is to save from loading the user twice if the current user object is going to be used inside the route handler. :param handle_no_auth: keyword only arugment, if it is not :code:`None`, and set to a function this will be used to handle an unauthorized request. """ if route is None: return partial(self.login_required, user_keyword=user_keyword, handle_no_auth=handle_no_auth) if handle_no_auth is not None: assert callable(handle_no_auth), 'handle_no_auth must be callable' @wraps(route) async def privileged(request, *args, **kwargs): user = self.current_user(request) if isawaitable(user): user = await user if user is None: if handle_no_auth: resp = handle_no_auth(request) else: resp = self.handle_no_auth(request) else: if user_keyword is not None: if user_keyword in kwargs: raise RuntimeError( 'override user keyword %r in route' % user_keyword) kwargs[user_keyword] = user resp = route(request, *args, **kwargs) if isawaitable(resp): resp = await resp return resp return privileged
[docs] def serialize(self, user): """Serialize the user, returns a token to be placed into session""" return {'uid': user.id, 'name': user.name}
[docs] def serializer(self, user_serializer): """Decorator to set a custom user serializer""" self.serialize = user_serializer return user_serializer
[docs] def load_user(self, token): """Load user with token. Return a User object, the default implementation use a proxy object of :class:`User`, Sanic-Auth can be remain backend agnostic this way. Override this with routine that loads user from database if needed. """ if token is not None: return User(id=token['uid'], name=token['name'])
[docs] def user_loader(self, load_user): """Decorator to set a custom user loader that loads user with token""" self.load_user = load_user return load_user
[docs] def get_session(self, request): """Get the session object associated with current request""" return request.ctx.session
[docs] def handle_no_auth(self, request): """Handle unauthorized user""" u = self.login_url or request.app.url_for(self.login_endpoint) return response.redirect(u)
[docs] def no_auth_handler(self, handle_no_auth): """Decorator to handle an unauthorized request""" self.handle_no_auth = handle_no_auth return handle_no_auth