root/galaxy-central/eggs/Beaker-1.4-py2.6.egg/beaker/session.py @ 3

リビジョン 3, 20.9 KB (コミッタ: kohda, 14 年 前)

Install Unix tools  http://hannonlab.cshl.edu/galaxy_unix_tools/galaxy.html

行番号 
1import cPickle
2import Cookie
3import hmac
4import os
5import random
6import time
7from datetime import datetime, timedelta
8try:
9    from hashlib import md5
10except ImportError:
11    from md5 import md5
12try:
13    # Use PyCrypto (if available)
14    from Crypto.Hash import HMAC, SHA as SHA1
15
16except ImportError:
17    # PyCrypto not available.  Use the Python standard library.
18    import hmac as HMAC
19    import sys
20    # When using the stdlib, we have to make sure the hmac version and sha
21    # version are compatible
22    if sys.version_info[0:2] <= (2,4):
23        # hmac in python2.4 or less require the sha module
24        import sha as SHA1
25    else:
26        # NOTE: We have to use the callable with hashlib (hashlib.sha1),
27        # otherwise hmac only accepts the sha module object itself
28        from hashlib import sha1 as SHA1
29
30# Check for pycryptopp encryption for AES
31try:
32    from beaker.crypto import generateCryptoKeys, aesEncrypt
33    crypto_ok = True
34except:
35    crypto_ok = False
36
37from beaker.cache import clsmap
38from beaker.exceptions import BeakerException
39from beaker.util import b64decode, b64encode, Set
40
41__all__ = ['SignedCookie', 'Session']
42
43getpid = hasattr(os, 'getpid') and os.getpid or (lambda : '')
44
45class SignedCookie(Cookie.BaseCookie):
46    """Extends python cookie to give digital signature support"""
47    def __init__(self, secret, input=None):
48        self.secret = secret
49        Cookie.BaseCookie.__init__(self, input)
50   
51    def value_decode(self, val):
52        val = val.strip('"')
53        sig = HMAC.new(self.secret, val[40:], SHA1).hexdigest()
54        if sig != val[:40]:
55            return None, val
56        else:
57            return val[40:], val
58   
59    def value_encode(self, val):
60        sig = HMAC.new(self.secret, val, SHA1).hexdigest()
61        return str(val), ("%s%s" % (sig, val))
62
63
64class Session(dict):
65    """Session object that uses container package for storage"""
66    def __init__(self, request, id=None, invalidate_corrupt=False,
67                 use_cookies=True, type=None, data_dir=None,
68                 key='beaker.session.id', timeout=None, cookie_expires=True,
69                 cookie_domain=None, secret=None, secure=False,
70                 namespace_class=None, **namespace_args):
71        if not type:
72            if data_dir:
73                self.type = 'file'
74            else:
75                self.type = 'memory'
76        else:
77            self.type = type
78
79        self.namespace_class = namespace_class or clsmap[self.type]
80
81        self.namespace_args = namespace_args
82       
83        self.request = request
84        self.data_dir = data_dir
85        self.key = key
86       
87        self.timeout = timeout
88        self.use_cookies = use_cookies
89        self.cookie_expires = cookie_expires
90       
91        # Default cookie domain/path
92        self._domain = cookie_domain
93        self._path = '/'
94        self.was_invalidated = False
95        self.secret = secret
96        self.secure = secure
97        self.id = id
98        self.accessed_dict = {}
99       
100        if self.use_cookies:
101            cookieheader = request.get('cookie', '')
102            if secret:
103                try:
104                    self.cookie = SignedCookie(secret, input=cookieheader)
105                except Cookie.CookieError:
106                    self.cookie = SignedCookie(secret, input=None)
107            else:
108                self.cookie = Cookie.SimpleCookie(input=cookieheader)
109           
110            if not self.id and self.key in self.cookie:
111                self.id = self.cookie[self.key].value
112       
113        self.is_new = self.id is None
114        if self.is_new:
115            self._create_id()
116            self['_accessed_time'] = self['_creation_time'] = time.time()
117        else:
118            try:
119                self.load()
120            except:
121                if invalidate_corrupt:
122                    self.invalidate()
123                else:
124                    raise
125       
126    def _create_id(self):
127        self.id = md5(
128            md5("%f%s%f%s" % (time.time(), id({}), random.random(),
129                              getpid())).hexdigest(),
130        ).hexdigest()
131        self.is_new = True
132        self.last_accessed = None
133        if self.use_cookies:
134            self.cookie[self.key] = self.id
135            if self._domain:
136                self.cookie[self.key]['domain'] = self._domain
137            if self.secure:
138                self.cookie[self.key]['secure'] = True
139            self.cookie[self.key]['path'] = self._path
140            if self.cookie_expires is not True:
141                if self.cookie_expires is False:
142                    expires = datetime.fromtimestamp( 0x7FFFFFFF )
143                elif isinstance(self.cookie_expires, timedelta):
144                    expires = datetime.today() + self.cookie_expires
145                elif isinstance(self.cookie_expires, datetime):
146                    expires = self.cookie_expires
147                else:
148                    raise ValueError("Invalid argument for cookie_expires: %s"
149                                     % repr(self.cookie_expires))
150                self.cookie[self.key]['expires'] = \
151                    expires.strftime("%a, %d-%b-%Y %H:%M:%S GMT" )
152            self.request['cookie_out'] = self.cookie[self.key].output(header='')
153            self.request['set_cookie'] = False
154   
155    def created(self):
156        return self['_creation_time']
157    created = property(created)
158   
159    def _set_domain(self, domain):
160        self['_domain'] = domain
161        self.cookie[self.key]['domain'] = domain
162        self.request['cookie_out'] = self.cookie[self.key].output(header='')
163        self.request['set_cookie'] = True
164   
165    def _get_domain(self, domain):
166        return self._domain
167   
168    domain = property(_get_domain, _set_domain)
169   
170    def _set_path(self, path):
171        self['_path'] = path
172        self.cookie[self.key]['path'] = path
173        self.request['cookie_out'] = self.cookie[self.key].output(header='')
174        self.request['set_cookie'] = True
175   
176    def _get_domain(self, domain):
177        return self._domain
178   
179    domain = property(_get_domain, _set_domain)
180
181    def _delete_cookie(self):
182        self.request['set_cookie'] = True
183        self.cookie[self.key] = self.id
184        if self._domain:
185            self.cookie[self.key]['domain'] = self._domain
186        if self.secure:
187            self.cookie[self.key]['secure'] = True
188        self.cookie[self.key]['path'] = '/'
189        expires = datetime.today().replace(year=2003)
190        self.cookie[self.key]['expires'] = \
191            expires.strftime("%a, %d-%b-%Y %H:%M:%S GMT" )
192        self.request['cookie_out'] = self.cookie[self.key].output(header='')
193        self.request['set_cookie'] = True
194
195    def delete(self):
196        """Deletes the session from the persistent storage, and sends
197        an expired cookie out"""
198        if self.use_cookies:
199            self._delete_cookie()
200        self.clear()
201
202    def invalidate(self):
203        """Invalidates this session, creates a new session id, returns
204        to the is_new state"""
205        self.clear()
206        self.was_invalidated = True
207        self._create_id()
208        self.load()
209   
210    def load(self):
211        "Loads the data from this session from persistent storage"
212        self.namespace = self.namespace_class(self.id,
213            data_dir=self.data_dir, digest_filenames=False,
214            **self.namespace_args)
215        now = time.time()
216        self.request['set_cookie'] = True
217       
218        self.namespace.acquire_read_lock()
219        timed_out = False
220        try:
221            self.clear()
222            try:
223                session_data = self.namespace['session']
224
225                # Memcached always returns a key, its None when its not
226                # present
227                if session_data is None:
228                    session_data = {
229                        '_creation_time':now,
230                        '_accessed_time':now
231                    }
232                    self.is_new = True
233            except (KeyError, TypeError):
234                session_data = {
235                    '_creation_time':now,
236                    '_accessed_time':now
237                }
238                self.is_new = True
239           
240            if self.timeout is not None and \
241               now - session_data['_accessed_time'] > self.timeout:
242                timed_out= True
243            else:
244                # Properly set the last_accessed time, which is different
245                # than the *currently* _accessed_time
246                if self.is_new or '_accessed_time' not in session_data:
247                    self.last_accessed = None
248                else:
249                    self.last_accessed = session_data['_accessed_time']
250               
251                # Update the current _accessed_time
252                session_data['_accessed_time'] = now
253                self.update(session_data)
254                self.accessed_dict = session_data.copy()               
255        finally:
256            self.namespace.release_read_lock()
257        if timed_out:
258            self.invalidate()
259   
260    def save(self, accessed_only=False):
261        """Saves the data for this session to persistent storage
262       
263        If accessed_only is True, then only the original data loaded
264        at the beginning of the request will be saved, with the updated
265        last accessed time.
266       
267        """
268        # Look to see if its a new session that was only accessed
269        # Don't save it under that case
270        if accessed_only and self.is_new:
271            return None
272       
273        if not hasattr(self, 'namespace'):
274            self.namespace = self.namespace_class(
275                                    self.id,
276                                    data_dir=self.data_dir,
277                                    digest_filenames=False,
278                                    **self.namespace_args)
279       
280        self.namespace.acquire_write_lock()
281        try:
282            if accessed_only:
283                data = dict(self.accessed_dict.items())
284            else:
285                data = dict(self.items())
286           
287            # Save the data
288            if not data and 'session' in self.namespace:
289                del self.namespace['session']
290            else:
291                self.namespace['session'] = data
292        finally:
293            self.namespace.release_write_lock()
294        if self.is_new:
295            self.request['set_cookie'] = True
296   
297    def revert(self):
298        """Revert the session to its original state from its first
299        access in the request"""
300        self.clear()
301        self.update(self.accessed_dict)
302   
303    # TODO: I think both these methods should be removed.  They're from
304    # the original mod_python code i was ripping off but they really
305    # have no use here.
306    def lock(self):
307        """Locks this session against other processes/threads.  This is
308        automatic when load/save is called.
309       
310        ***use with caution*** and always with a corresponding 'unlock'
311        inside a "finally:" block, as a stray lock typically cannot be
312        unlocked without shutting down the whole application.
313
314        """
315        self.namespace.acquire_write_lock()
316
317    def unlock(self):
318        """Unlocks this session against other processes/threads.  This
319        is automatic when load/save is called.
320
321        ***use with caution*** and always within a "finally:" block, as
322        a stray lock typically cannot be unlocked without shutting down
323        the whole application.
324
325        """
326        self.namespace.release_write_lock()
327
328class CookieSession(Session):
329    """Pure cookie-based session
330   
331    Options recognized when using cookie-based sessions are slightly
332    more restricted than general sessions.
333   
334    ``key``
335        The name the cookie should be set to.
336    ``timeout``
337        How long session data is considered valid. This is used
338        regardless of the cookie being present or not to determine
339        whether session data is still valid.
340    ``encrypt_key``
341        The key to use for the session encryption, if not provided the
342        session will not be encrypted.
343    ``validate_key``
344        The key used to sign the encrypted session
345    ``cookie_domain``
346        Domain to use for the cookie.
347    ``secure``
348        Whether or not the cookie should only be sent over SSL.
349   
350    """
351    def __init__(self, request, key='beaker.session.id', timeout=None,
352                 cookie_expires=True, cookie_domain=None, encrypt_key=None,
353                 validate_key=None, secure=False, **kwargs):
354        if not crypto_ok and encrypt_key:
355            raise BeakerException("pycryptopp is not installed, can't use "
356                                  "encrypted cookie-only Session.")
357       
358        self.request = request
359        self.key = key
360        self.timeout = timeout
361        self.cookie_expires = cookie_expires
362        self.encrypt_key = encrypt_key
363        self.validate_key = validate_key
364        self.request['set_cookie'] = False
365        self.secure = secure
366        self._domain = cookie_domain
367        self._path = '/'
368       
369        try:
370            cookieheader = request['cookie']
371        except KeyError:
372            cookieheader = ''
373       
374        if validate_key is None:
375            raise BeakerException("No validate_key specified for Cookie only "
376                                  "Session.")
377       
378        try:
379            self.cookie = SignedCookie(validate_key, input=cookieheader)
380        except Cookie.CookieError:
381            self.cookie = SignedCookie(validate_key, input=None)
382       
383        self['_id'] = self._make_id()
384        self.is_new = True
385       
386        # If we have a cookie, load it
387        if self.key in self.cookie and self.cookie[self.key].value is not None:
388            self.is_new = False
389            try:
390                self.update(self._decrypt_data())
391            except:
392                pass
393            if self.timeout is not None and time.time() - \
394               self['_accessed_time'] > self.timeout:
395                self.clear()
396            self.accessed_dict = self.copy()
397            self._create_cookie()
398   
399    def created(self):
400        return self['_creation_time']
401    created = property(created)
402   
403    def id(self):
404        return self['_id']
405    id = property(id)
406
407    def _set_domain(self, domain):
408        self['_domain'] = domain
409        self._domain = domain
410       
411    def _set_path(self, path):
412        self['_path'] = path
413        self._path = path
414   
415    def _encrypt_data(self):
416        """Serialize, encipher, and base64 the session dict"""
417        if self.encrypt_key:
418            nonce = b64encode(os.urandom(40))[:8]
419            encrypt_key = generateCryptoKeys(self.encrypt_key,
420                                             self.validate_key + nonce, 1)
421            data = cPickle.dumps(self.copy(), 2)
422            return nonce + b64encode(aesEncrypt(data, encrypt_key))
423        else:
424            data = cPickle.dumps(self.copy(), 2)
425            return b64encode(data)
426   
427    def _decrypt_data(self):
428        """Bas64, decipher, then un-serialize the data for the session
429        dict"""
430        if self.encrypt_key:
431            nonce = self.cookie[self.key].value[:8]
432            encrypt_key = generateCryptoKeys(self.encrypt_key,
433                                             self.validate_key + nonce, 1)
434            payload = b64decode(self.cookie[self.key].value[8:])
435            data = aesEncrypt(payload, encrypt_key)
436            return cPickle.loads(data)
437        else:
438            data = b64decode(self.cookie[self.key].value)
439            return cPickle.loads(data)
440   
441    def _make_id(self):
442        return md5(md5(
443            "%f%s%f%d" % (time.time(), id({}), random.random(), getpid())
444            ).hexdigest()
445        ).hexdigest()
446   
447    def save(self, accessed_only=False):
448        """Saves the data for this session to persistent storage"""
449        if accessed_only and self.is_new:
450            return
451        if accessed_only:
452            self.clear()
453            self.update(self.accessed_dict)
454        self._create_cookie()
455   
456    def expire(self):
457        """Delete the 'expires' attribute on this Session, if any."""
458       
459        self.pop('_expires', None)
460       
461    def _create_cookie(self):
462        if '_creation_time' not in self:
463            self['_creation_time'] = time.time()
464        if '_id' not in self:
465            self['_id'] = self._make_id()
466        self['_accessed_time'] = time.time()
467       
468        if self.cookie_expires is not True:
469            if self.cookie_expires is False:
470                expires = datetime.fromtimestamp( 0x7FFFFFFF )
471            elif isinstance(self.cookie_expires, timedelta):
472                expires = datetime.today() + self.cookie_expires
473            elif isinstance(self.cookie_expires, datetime):
474                expires = self.cookie_expires
475            else:
476                raise ValueError("Invalid argument for cookie_expires: %s"
477                                 % repr(self.cookie_expires))
478            self['_expires'] = expires
479        elif '_expires' in self:
480            expires = self['_expires']
481        else:
482            expires = None
483
484        val = self._encrypt_data()
485        if len(val) > 4064:
486            raise BeakerException("Cookie value is too long to store")
487       
488        self.cookie[self.key] = val
489        if '_domain' in self:
490            self.cookie[self.key]['domain'] = self['_domain']
491        elif self._domain:
492            self.cookie[self.key]['domain'] = self._domain
493        if self.secure:
494            self.cookie[self.key]['secure'] = True
495       
496        self.cookie[self.key]['path'] = self.get('_path', '/')
497       
498        if expires:
499            self.cookie[self.key]['expires'] = \
500                expires.strftime("%a, %d-%b-%Y %H:%M:%S GMT" )
501        self.request['cookie_out'] = self.cookie[self.key].output(header='')
502        self.request['set_cookie'] = True
503   
504    def delete(self):
505        """Delete the cookie, and clear the session"""
506        # Send a delete cookie request
507        self._delete_cookie()
508        self.clear()
509   
510    def invalidate(self):
511        """Clear the contents and start a new session"""
512        self.delete()
513        self['_id'] = self._make_id()
514
515
516class SessionObject(object):
517    """Session proxy/lazy creator
518   
519    This object proxies access to the actual session object, so that in
520    the case that the session hasn't been used before, it will be
521    setup. This avoid creating and loading the session from persistent
522    storage unless its actually used during the request.
523   
524    """
525    def __init__(self, environ, **params):
526        self.__dict__['_params'] = params
527        self.__dict__['_environ'] = environ
528        self.__dict__['_sess'] = None
529        self.__dict__['_headers'] = []
530   
531    def _session(self):
532        """Lazy initial creation of session object"""
533        if self.__dict__['_sess'] is None:
534            params = self.__dict__['_params']
535            environ = self.__dict__['_environ']
536            self.__dict__['_headers'] = req = {'cookie_out':None}
537            req['cookie'] = environ.get('HTTP_COOKIE')
538            if params.get('type') == 'cookie':
539                self.__dict__['_sess'] = CookieSession(req, **params)
540            else:
541                self.__dict__['_sess'] = Session(req, use_cookies=True,
542                                                 **params)
543        return self.__dict__['_sess']
544   
545    def __getattr__(self, attr):
546        return getattr(self._session(), attr)
547   
548    def __setattr__(self, attr, value):
549        setattr(self._session(), attr, value)
550   
551    def __delattr__(self, name):
552        self._session().__delattr__(name)
553   
554    def __getitem__(self, key):
555        return self._session()[key]
556   
557    def __setitem__(self, key, value):
558        self._session()[key] = value
559   
560    def __delitem__(self, key):
561        self._session().__delitem__(key)
562   
563    def __repr__(self):
564        return self._session().__repr__()
565   
566    def __iter__(self):
567        """Only works for proxying to a dict"""
568        return iter(self._session().keys())
569   
570    def __contains__(self, key):
571        return self._session().has_key(key)
572   
573    def get_by_id(self, id):
574        params = self.__dict__['_params']
575        session = Session({}, use_cookies=False, id=id, **params)
576        if session.is_new:
577            return None
578        return session
579   
580    def save(self):
581        self.__dict__['_dirty'] = True
582   
583    def delete(self):
584        self.__dict__['_dirty'] = True
585        self._session().delete()
586   
587    def persist(self):
588        """Persist the session to the storage
589       
590        If its set to autosave, then the entire session will be saved
591        regardless of if save() has been called. Otherwise, just the
592        accessed time will be updated if save() was not called, or
593        the session will be saved if save() was called.
594       
595        """
596        if self.__dict__['_params'].get('auto'):
597            self._session().save()
598        else:
599            if self.__dict__.get('_dirty'):
600                self._session().save()
601            else:
602                self._session().save(accessed_only=True)
603   
604    def dirty(self):
605        return self.__dict__.get('_dirty', False)
606   
607    def accessed(self):
608        return self.__dict__['_sess'] is not None
Note: リポジトリブラウザについてのヘルプは TracBrowser を参照してください。