192 lines
6.4 KiB
Python
192 lines
6.4 KiB
Python
|
#!/usr/bin/env python
|
||
|
#
|
||
|
# Bottle session manager. See README for full documentation.
|
||
|
#
|
||
|
# Written by: Sean Reifschneider <jafo@tummy.com>
|
||
|
#
|
||
|
# 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)
|