web.web.session

Package web :: Package web :: Module session |
[hide private]
[frames] | no frames]

# Source Code for Module web.web.session

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