Source code for archelond.auth

"""
Decorators for authentication via basic auth or tokens
"""
from functools import wraps
import hashlib
import logging

from flask import request, Response, current_app
from itsdangerous import JSONWebSignatureSerializer as Serializer
from itsdangerous import BadSignature

log = logging.getLogger(__name__)


[docs]def check_basic_auth(username, password): """ This function is called to check if a username / password combination is valid via the htpasswd file. """ valid = current_app.config['users'].check_password(username, password) if not valid: log.warn('Invalid login from %s', username) return ( valid, username )
[docs]def get_signature(): """ Setup crypto sig. """ return Serializer(current_app.config['FLASK_SECRET'])
[docs]def get_hashhash(username): """ Generate a digest of the htpasswd hash """ return hashlib.sha256( current_app.config['users'].find(username) ).hexdigest()
[docs]def generate_token(username): """ assumes user exists in htpasswd file. Return the token for the given user by signing a token of the username and a hash of the htpasswd string. """ serializer = get_signature() return serializer.dumps( { 'username': username, 'hashhash': get_hashhash(username) } )
[docs]def check_token_auth(token): """ Check to see who this is and if their token gets them into the system. """ users = current_app.config['users'] serializer = get_signature() try: data = serializer.loads(token) except BadSignature: log.warn('Received bad token signature') return False, None if data['username'] not in users.users(): log.warn( 'Token auth signed message, but invalid user %s', data['username'] ) return False, None if data['hashhash'] != get_hashhash(data['username']): log.warn( 'Token and password do not match, %s needs to regenerate token', data['username'] ) return False, None return True, data['username']
[docs]def auth_failed(): """ Sends a 401 response that enables basic auth """ 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"'} )
[docs]def requires_auth(func): """ Decorator function with basic and token authentication handler """ @wraps(func) def decorated(*args, **kwargs): """ Actual wrapper to run the auth checks. """ basic_auth = request.authorization is_valid = False if basic_auth: is_valid, user = check_basic_auth( basic_auth.username, basic_auth.password ) else: token = request.headers.get('Authorization', None) param_token = request.args.get('access_token') if token or param_token: if token: # slice the 'token ' piece of the header (following # github style): token = token[6:] else: # Grab it from query dict instead token = param_token log.debug('Received token: %s', token) is_valid, user = check_token_auth(token) if not is_valid: return auth_failed() kwargs['user'] = user return func(*args, **kwargs) return decorated