OpenSecurity/install/web.py-0.37/build/lib/web/session.py
author om
Mon, 02 Dec 2013 14:02:05 +0100
changeset 3 65432e6c6042
permissions -rwxr-xr-x
initial deployment and project layout commit
     1 """
     2 Session Management
     3 (from web.py)
     4 """
     5 
     6 import os, time, datetime, random, base64
     7 import os.path
     8 from copy import deepcopy
     9 try:
    10     import cPickle as pickle
    11 except ImportError:
    12     import pickle
    13 try:
    14     import hashlib
    15     sha1 = hashlib.sha1
    16 except ImportError:
    17     import sha
    18     sha1 = sha.new
    19 
    20 import utils
    21 import webapi as web
    22 
    23 __all__ = [
    24     'Session', 'SessionExpired',
    25     'Store', 'DiskStore', 'DBStore',
    26 ]
    27 
    28 web.config.session_parameters = utils.storage({
    29     'cookie_name': 'webpy_session_id',
    30     'cookie_domain': None,
    31     'cookie_path' : None,
    32     'timeout': 86400, #24 * 60 * 60, # 24 hours in seconds
    33     'ignore_expiry': True,
    34     'ignore_change_ip': True,
    35     'secret_key': 'fLjUfxqXtfNoIldA0A0J',
    36     'expired_message': 'Session expired',
    37     'httponly': True,
    38     'secure': False
    39 })
    40 
    41 class SessionExpired(web.HTTPError): 
    42     def __init__(self, message):
    43         web.HTTPError.__init__(self, '200 OK', {}, data=message)
    44 
    45 class Session(object):
    46     """Session management for web.py
    47     """
    48     __slots__ = [
    49         "store", "_initializer", "_last_cleanup_time", "_config", "_data", 
    50         "__getitem__", "__setitem__", "__delitem__"
    51     ]
    52 
    53     def __init__(self, app, store, initializer=None):
    54         self.store = store
    55         self._initializer = initializer
    56         self._last_cleanup_time = 0
    57         self._config = utils.storage(web.config.session_parameters)
    58         self._data = utils.threadeddict()
    59         
    60         self.__getitem__ = self._data.__getitem__
    61         self.__setitem__ = self._data.__setitem__
    62         self.__delitem__ = self._data.__delitem__
    63 
    64         if app:
    65             app.add_processor(self._processor)
    66 
    67     def __contains__(self, name):
    68         return name in self._data
    69 
    70     def __getattr__(self, name):
    71         return getattr(self._data, name)
    72     
    73     def __setattr__(self, name, value):
    74         if name in self.__slots__:
    75             object.__setattr__(self, name, value)
    76         else:
    77             setattr(self._data, name, value)
    78         
    79     def __delattr__(self, name):
    80         delattr(self._data, name)
    81 
    82     def _processor(self, handler):
    83         """Application processor to setup session for every request"""
    84         self._cleanup()
    85         self._load()
    86 
    87         try:
    88             return handler()
    89         finally:
    90             self._save()
    91 
    92     def _load(self):
    93         """Load the session from the store, by the id from cookie"""
    94         cookie_name = self._config.cookie_name
    95         cookie_domain = self._config.cookie_domain
    96         cookie_path = self._config.cookie_path
    97         httponly = self._config.httponly
    98         self.session_id = web.cookies().get(cookie_name)
    99 
   100         # protection against session_id tampering
   101         if self.session_id and not self._valid_session_id(self.session_id):
   102             self.session_id = None
   103 
   104         self._check_expiry()
   105         if self.session_id:
   106             d = self.store[self.session_id]
   107             self.update(d)
   108             self._validate_ip()
   109         
   110         if not self.session_id:
   111             self.session_id = self._generate_session_id()
   112 
   113             if self._initializer:
   114                 if isinstance(self._initializer, dict):
   115                     self.update(deepcopy(self._initializer))
   116                 elif hasattr(self._initializer, '__call__'):
   117                     self._initializer()
   118  
   119         self.ip = web.ctx.ip
   120 
   121     def _check_expiry(self):
   122         # check for expiry
   123         if self.session_id and self.session_id not in self.store:
   124             if self._config.ignore_expiry:
   125                 self.session_id = None
   126             else:
   127                 return self.expired()
   128 
   129     def _validate_ip(self):
   130         # check for change of IP
   131         if self.session_id and self.get('ip', None) != web.ctx.ip:
   132             if not self._config.ignore_change_ip:
   133                return self.expired() 
   134     
   135     def _save(self):
   136         if not self.get('_killed'):
   137             self._setcookie(self.session_id)
   138             self.store[self.session_id] = dict(self._data)
   139         else:
   140             self._setcookie(self.session_id, expires=-1)
   141             
   142     def _setcookie(self, session_id, expires='', **kw):
   143         cookie_name = self._config.cookie_name
   144         cookie_domain = self._config.cookie_domain
   145         cookie_path = self._config.cookie_path
   146         httponly = self._config.httponly
   147         secure = self._config.secure
   148         web.setcookie(cookie_name, session_id, expires=expires, domain=cookie_domain, httponly=httponly, secure=secure, path=cookie_path)
   149     
   150     def _generate_session_id(self):
   151         """Generate a random id for session"""
   152 
   153         while True:
   154             rand = os.urandom(16)
   155             now = time.time()
   156             secret_key = self._config.secret_key
   157             session_id = sha1("%s%s%s%s" %(rand, now, utils.safestr(web.ctx.ip), secret_key))
   158             session_id = session_id.hexdigest()
   159             if session_id not in self.store:
   160                 break
   161         return session_id
   162 
   163     def _valid_session_id(self, session_id):
   164         rx = utils.re_compile('^[0-9a-fA-F]+$')
   165         return rx.match(session_id)
   166         
   167     def _cleanup(self):
   168         """Cleanup the stored sessions"""
   169         current_time = time.time()
   170         timeout = self._config.timeout
   171         if current_time - self._last_cleanup_time > timeout:
   172             self.store.cleanup(timeout)
   173             self._last_cleanup_time = current_time
   174 
   175     def expired(self):
   176         """Called when an expired session is atime"""
   177         self._killed = True
   178         self._save()
   179         raise SessionExpired(self._config.expired_message)
   180  
   181     def kill(self):
   182         """Kill the session, make it no longer available"""
   183         del self.store[self.session_id]
   184         self._killed = True
   185 
   186 class Store:
   187     """Base class for session stores"""
   188 
   189     def __contains__(self, key):
   190         raise NotImplementedError
   191 
   192     def __getitem__(self, key):
   193         raise NotImplementedError
   194 
   195     def __setitem__(self, key, value):
   196         raise NotImplementedError
   197 
   198     def cleanup(self, timeout):
   199         """removes all the expired sessions"""
   200         raise NotImplementedError
   201 
   202     def encode(self, session_dict):
   203         """encodes session dict as a string"""
   204         pickled = pickle.dumps(session_dict)
   205         return base64.encodestring(pickled)
   206 
   207     def decode(self, session_data):
   208         """decodes the data to get back the session dict """
   209         pickled = base64.decodestring(session_data)
   210         return pickle.loads(pickled)
   211 
   212 class DiskStore(Store):
   213     """
   214     Store for saving a session on disk.
   215 
   216         >>> import tempfile
   217         >>> root = tempfile.mkdtemp()
   218         >>> s = DiskStore(root)
   219         >>> s['a'] = 'foo'
   220         >>> s['a']
   221         'foo'
   222         >>> time.sleep(0.01)
   223         >>> s.cleanup(0.01)
   224         >>> s['a']
   225         Traceback (most recent call last):
   226             ...
   227         KeyError: 'a'
   228     """
   229     def __init__(self, root):
   230         # if the storage root doesn't exists, create it.
   231         if not os.path.exists(root):
   232             os.makedirs(
   233                     os.path.abspath(root)
   234                     )
   235         self.root = root
   236 
   237     def _get_path(self, key):
   238         if os.path.sep in key: 
   239             raise ValueError, "Bad key: %s" % repr(key)
   240         return os.path.join(self.root, key)
   241     
   242     def __contains__(self, key):
   243         path = self._get_path(key)
   244         return os.path.exists(path)
   245 
   246     def __getitem__(self, key):
   247         path = self._get_path(key)
   248         if os.path.exists(path): 
   249             pickled = open(path).read()
   250             return self.decode(pickled)
   251         else:
   252             raise KeyError, key
   253 
   254     def __setitem__(self, key, value):
   255         path = self._get_path(key)
   256         pickled = self.encode(value)    
   257         try:
   258             f = open(path, 'w')
   259             try:
   260                 f.write(pickled)
   261             finally: 
   262                 f.close()
   263         except IOError:
   264             pass
   265 
   266     def __delitem__(self, key):
   267         path = self._get_path(key)
   268         if os.path.exists(path):
   269             os.remove(path)
   270     
   271     def cleanup(self, timeout):
   272         now = time.time()
   273         for f in os.listdir(self.root):
   274             path = self._get_path(f)
   275             atime = os.stat(path).st_atime
   276             if now - atime > timeout :
   277                 os.remove(path)
   278 
   279 class DBStore(Store):
   280     """Store for saving a session in database
   281     Needs a table with the following columns:
   282 
   283         session_id CHAR(128) UNIQUE NOT NULL,
   284         atime DATETIME NOT NULL default current_timestamp,
   285         data TEXT
   286     """
   287     def __init__(self, db, table_name):
   288         self.db = db
   289         self.table = table_name
   290     
   291     def __contains__(self, key):
   292         data = self.db.select(self.table, where="session_id=$key", vars=locals())
   293         return bool(list(data)) 
   294 
   295     def __getitem__(self, key):
   296         now = datetime.datetime.now()
   297         try:
   298             s = self.db.select(self.table, where="session_id=$key", vars=locals())[0]
   299             self.db.update(self.table, where="session_id=$key", atime=now, vars=locals())
   300         except IndexError:
   301             raise KeyError
   302         else:
   303             return self.decode(s.data)
   304 
   305     def __setitem__(self, key, value):
   306         pickled = self.encode(value)
   307         now = datetime.datetime.now()
   308         if key in self:
   309             self.db.update(self.table, where="session_id=$key", data=pickled, vars=locals())
   310         else:
   311             self.db.insert(self.table, False, session_id=key, data=pickled )
   312                 
   313     def __delitem__(self, key):
   314         self.db.delete(self.table, where="session_id=$key", vars=locals())
   315 
   316     def cleanup(self, timeout):
   317         timeout = datetime.timedelta(timeout/(24.0*60*60)) #timedelta takes numdays as arg
   318         last_allowed_time = datetime.datetime.now() - timeout
   319         self.db.delete(self.table, where="$last_allowed_time > atime", vars=locals())
   320 
   321 class ShelfStore:
   322     """Store for saving session using `shelve` module.
   323 
   324         import shelve
   325         store = ShelfStore(shelve.open('session.shelf'))
   326 
   327     XXX: is shelve thread-safe?
   328     """
   329     def __init__(self, shelf):
   330         self.shelf = shelf
   331 
   332     def __contains__(self, key):
   333         return key in self.shelf
   334 
   335     def __getitem__(self, key):
   336         atime, v = self.shelf[key]
   337         self[key] = v # update atime
   338         return v
   339 
   340     def __setitem__(self, key, value):
   341         self.shelf[key] = time.time(), value
   342         
   343     def __delitem__(self, key):
   344         try:
   345             del self.shelf[key]
   346         except KeyError:
   347             pass
   348 
   349     def cleanup(self, timeout):
   350         now = time.time()
   351         for k in self.shelf.keys():
   352             atime, v = self.shelf[k]
   353             if now - atime > timeout :
   354                 del self[k]
   355 
   356 if __name__ == '__main__' :
   357     import doctest
   358     doctest.testmod()