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