flask-auth / flaskext / auth / auth.py

"""
Base module of the extension. Contains basic functions, the Auth object and the
AuthUser base class.
"""

import time, hashlib, datetime
from functools import partial
from flask import session, abort, current_app, redirect, url_for

DEFAULT_HASH_ALGORITHM = hashlib.sha1

DEFAULT_USER_TIMEOUT = 3600

SESSION_USER_KEY = 'auth_user'
SESSION_LOGIN_KEY = 'auth_login'

def _default_not_authorized(*args, **kwargs):
    return abort(401)

def _redirect_to_login(login_url_name):
    return redirect(url_for(login_url_name))

class Auth(object):
    """
    Extension initialization object containing settings for the extension.
    
    Supported settings:

    - login_url_name: Name of the URL that is used for login. It's used in
      the not_logged_in_callback if provided in the constructor.
    - not_logged_in_callback: Function to call when a user accesses a page
      without being logged in. Normally used to redirect to the login page.
      If a login_url_name is provided, it will by default redirect to that
      url. Otherwise, the default is abort(401).
    - not_permitted_callback: Function to call when a user tries to access
      a page for which he doesn't have the permission. Default: abort(401).
    - hash_algorithm: Algorithm from the hashlib library used for password
      encryption. Default: sha1.
    - user_timeout: Timeout (in seconds) after which the sesion of the user
      expires. Default: 3600. A timeout of 0 means it will never expire.
    - load_role: Function to load a role. Is called with user.role as only
      parameter.
    """

    def __init__(self, app=None, login_url_name=None):
        if login_url_name is None:
            self.not_logged_in_callback = _default_not_authorized
        else:
            self.not_logged_in_callback = partial(_redirect_to_login,
                                                  login_url_name)
        self.not_permitted_callback = _default_not_authorized
        self.hash_algorithm = DEFAULT_HASH_ALGORITHM
        self.user_timeout = DEFAULT_USER_TIMEOUT
        self.load_role = lambda _: None
        if app is not None:
            self.init_app(app)

    def init_app(self, app):
        app.auth = self
        
class AuthUser(object):
    """
    Baseclass for a user model. Contains a few convenience methods.

    Attributes:

    - username: Username of the user.
    - password: Password of the user. By default not encrypted. The 
      set_and_encrypt_password() method sets and encrypts the password.
    - salt: Salt used for the encrytion of the password.
    - role: Role of this user.  """

    role = None

    def __init__(self, username=None, password=None, salt=None, role=None):
        self.username = username
        # Storing password unmodified. Encryption of the password should 
        # happen explicitly.
        self.password = password
        self.salt = salt
        self.role = role

    def set_and_encrypt_password(self, password, salt=str(int(time.time()))):
        """
        Encrypts and sets the password. If no salt is provided, a new
        one is generated.
        """
        self.salt = salt
        self.password = encrypt(password, self.salt)

    def authenticate(self, password):
        """
        Attempts to verify the password and log the user in. Returns true if 
        succesful.
        """
        if self.password == encrypt(password, self.salt):
            login(self)
            return True
        return False

    def __eq__(self, other):
        return self.username == getattr(other, 'username', None)

    def __ne__(self, other):
        return not self.__eq__(other)

    def __getstate__(self):
        return self.__dict__

    @classmethod
    def load_current_user(cls, apply_timeout=True):
        """
        Load current user based on the result of get_current_user_data().
        """
        data = get_current_user_data(apply_timeout)
        if not data:
            return None
        user = cls()
        user.__dict__ = data
        return user

    def is_logged_in(self):
        user_data = get_current_user_data()
        return user_data is not None and user_data.get('username') == self.username

def encrypt(password, salt=None, hash_algorithm=None):
    """Encrypts a password based on the hashing algorithm."""
    to_encrypt = password
    if salt is not None:
        to_encrypt += salt
    if hash_algorithm is not None:
        return hash_algorithm(to_encrypt).hexdigest()
    return current_app.auth.hash_algorithm(to_encrypt).hexdigest()

def login(user):
    """
    Logs the user in. Note that NO AUTHENTICATION is done by this function. If
    you want to authenticate a user, use the AuthUser.authenticate() method.
    """
    session[SESSION_USER_KEY] = user.__getstate__()
    session[SESSION_LOGIN_KEY] = datetime.datetime.utcnow()

def logout():
    """Logs the currently logged in user out and returns the user data."""
    session.pop(SESSION_LOGIN_KEY, None)
    return session.pop(SESSION_USER_KEY, None)
    
def get_current_user_data(apply_timeout=True):
    """ 
    Returns the data of the current user (user.__dict__) if there is a
    current user and he didn't time out yet. If timeout should be ignored,
    provide apply_timeout=False.  
    """
    user_data = session.get(SESSION_USER_KEY, None)
    if user_data is None:
        return None 
    if not apply_timeout:
        return user_data
    login_datetime = session[SESSION_LOGIN_KEY]
    now = datetime.datetime.utcnow()
    user_timeout = current_app.auth.user_timeout
    if user_timeout > 0 and now - login_datetime > \
       datetime.timedelta(seconds=user_timeout):
        logout()
        return None
    return user_data

def _not_logged_in(callback, *args, **kwargs):
    if callback is None:
        return current_app.auth.not_logged_in_callback(*args, **kwargs)
    else:
        return callback(*args, **kwargs)

def login_required(callback=None):
    """
    Decorator for views that require login. Callback can be specified to
    override the default callback on the auth object.
    """
    def wrap(func):
        def decorator(*args, **kwargs):
            if get_current_user_data() is None:
                return _not_logged_in(callback, *args, **kwargs)
            return func(*args, **kwargs)
        return decorator
    return wrap
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.