#!/usr/bin/env python # # Bottle session manager. See README for full documentation. # # Written by: Sean Reifschneider # # License: 3-clause BSD from __future__ import with_statement import bottle import time def authenticator(session_manager, login_url='/auth/login'): '''Create an authenticator decorator. :param session_manager: A session manager class to be used for storing and retrieving session data. Probably based on :class:`BaseSession`. :param login_url: The URL to redirect to if a login is required. (default: ``'/auth/login'``). ''' def valid_user(login_url=login_url): def decorator(handler): import functools @functools.wraps(handler) def check_auth(*args, **kwargs): try: data = session_manager.get_session() if not data['valid']: raise KeyError('Invalid login') except (KeyError, TypeError): bottle.response.set_cookie( 'validuserloginredirect', bottle.request.fullpath, path='/', expires=(int(time.time()) + 3600)) bottle.redirect(login_url) # set environment if data.get('name'): bottle.request.environ['REMOTE_USER'] = data['name'] return handler(*args, **kwargs) return check_auth return decorator return(valid_user) import pickle import os import uuid class BaseSession(object): '''Base class which implements some of the basic functionality required for session managers. Cannot be used directly. :param cookie_expires: Expiration time of session ID cookie, either `None` if the cookie is not to expire, a number of seconds in the future, or a datetime object. (default: 30 days) ''' def __init__(self, cookie_expires=86400 * 30): self.cookie_expires = cookie_expires def load(self, sessionid): raise NotImplementedError def save(self, sessionid, data): raise NotImplementedError def make_session_id(self): return str(uuid.uuid4()) def allocate_new_session_id(self): # retry allocating a unique sessionid for i in range(100): sessionid = self.make_session_id() if not self.load(sessionid): return sessionid raise ValueError('Unable to allocate unique session') def get_session(self): # get existing or create new session identifier sessionid = bottle.request.cookies.get('sessionid') if not sessionid: sessionid = self.allocate_new_session_id() bottle.response.set_cookie( 'sessionid', sessionid, path='/', expires=(int(time.time()) + self.cookie_expires)) # load existing or create new session data = self.load(sessionid) if not data: data = {'sessionid': sessionid, 'valid': False} self.save(data) return data class PickleSession(BaseSession): '''Class which stores session information in the file-system. :param session_dir: Directory that session information is stored in. (default: ``'/tmp'``). ''' def __init__(self, session_dir='/tmp', *args, **kwargs): super(PickleSession, self).__init__(*args, **kwargs) self.session_dir = session_dir def load(self, sessionid): filename = os.path.join(self.session_dir, 'session-%s' % sessionid) if not os.path.exists(filename): return None with open(filename, 'rb') as fp: session = pickle.load(fp) return session def save(self, data): sessionid = data['sessionid'] fileName = os.path.join(self.session_dir, 'session-%s' % sessionid) tmpName = fileName + '.' + str(uuid.uuid4()) with open(tmpName, 'wb') as fp: self.session = pickle.dump(data, fp) os.rename(tmpName, fileName) class CookieSession(BaseSession): '''Session manager class which stores session in a signed browser cookie. :param cookie_name: Name of the cookie to store the session in. (default: ``session_data``) :param secret: Secret to be used for "secure cookie". If ``None``, a random secret will be generated and written to a temporary file for future use. This may not be suitable for systems which have untrusted users on it. (default: ``None``) :param secret_file: File to read the secret from. If ``secret`` is ``None`` and ``secret_file`` is set, the first line of this file is read, and stripped, to produce the secret. ''' def __init__( self, secret=None, secret_file=None, cookie_name='session_data', *args, **kwargs): super(CookieSession, self).__init__(*args, **kwargs) self.cookie_name = cookie_name if not secret and secret_file is not None: with open(secret_file, 'r') as fp: secret = fp.readline().strip() if not secret: import string import random import tempfile import sys tmpfilename = os.path.join( tempfile.gettempdir(), '%s.secret' % os.path.basename(sys.argv[0])) if os.path.exists(tmpfilename): with open(tmpfilename, 'r') as fp: secret = fp.readline().strip() else: # save off a secret to a tmp file secret = ''.join([ random.choice(string.ascii_letters) for x in range(32)]) old_umask = os.umask(0o77) with open(tmpfilename, 'w') as fp: fp.write(secret) os.umask(old_umask) self.secret = secret def load(self, sessionid): cookie = bottle.request.get_cookie( self.cookie_name, secret=self.secret) if cookie is None: return {} return pickle.loads(cookie) def save(self, data): bottle.response.set_cookie( self.cookie_name, pickle.dumps(data), secret=self.secret, path='/', expires=int(time.time()) + self.cookie_expires, secure=True, httponly=True)