# Copyright (c) 2010 Agendaless Consulting and Contributors. # (http://www.agendaless.com), All Rights Reserved # License: BSD-derived (http://www.repoze.org/LICENSE.txt) # With Patches from RhodeCode GmBH import os from beaker import cache from beaker.session import SessionObject from beaker.util import coerce_cache_params from beaker.util import coerce_session_params from pyramid.interfaces import ISession from pyramid.settings import asbool from zope.interface import implementer from binascii import hexlify def BeakerSessionFactoryConfig(**options): """ Return a Pyramid session factory using Beaker session settings supplied directly as ``**options``""" class PyramidBeakerSessionObject(SessionObject): _options = options _cookie_on_exception = _options.pop('cookie_on_exception', True) _constant_csrf_token = _options.pop('constant_csrf_token', False) def __init__(self, request): SessionObject.__init__(self, request.environ, **self._options) def session_callback(request, response): exception = getattr(request, 'exception', None) if (exception is None or self._cookie_on_exception) and self.accessed(): self.persist() headers = self.__dict__['_headers'] if headers['set_cookie'] and headers['cookie_out']: response.headerlist.append(('Set-Cookie', headers['cookie_out'])) request.add_response_callback(session_callback) # ISession API @property def id(self): # this is as inspected in SessionObject.__init__ if self.__dict__['_params'].get('type') != 'cookie': return self._session().id return None @property def new(self): return self.last_accessed is None changed = SessionObject.save # modifying dictionary methods @call_save def clear(self): return self._session().clear() @call_save def update(self, d, **kw): return self._session().update(d, **kw) @call_save def setdefault(self, k, d=None): return self._session().setdefault(k, d) @call_save def pop(self, k, d=None): return self._session().pop(k, d) @call_save def popitem(self): return self._session().popitem() __setitem__ = call_save(SessionObject.__setitem__) __delitem__ = call_save(SessionObject.__delitem__) # Flash API methods def flash(self, msg, queue='', allow_duplicate=True): storage = self.setdefault('_f_' + queue, []) if allow_duplicate or (msg not in storage): storage.append(msg) def pop_flash(self, queue=''): storage = self.pop('_f_' + queue, []) return storage def peek_flash(self, queue=''): storage = self.get('_f_' + queue, []) return storage # CSRF API methods def new_csrf_token(self): token = (self._constant_csrf_token or hexlify(os.urandom(20)).decode('ascii')) self['_csrft_'] = token return token def get_csrf_token(self): token = self.get('_csrft_', None) if token is None: token = self.new_csrf_token() return token return implementer(ISession)(PyramidBeakerSessionObject) def call_save(wrapped): """ By default, in non-auto-mode beaker badly wants people to call save even though it should know something has changed when a mutating method is called. This hack should be removed if Beaker ever starts to do this by default. """ def save(session, *arg, **kw): value = wrapped(session, *arg, **kw) session.save() return value save.__doc__ = wrapped.__doc__ return save def session_factory_from_settings(settings): """ Return a Pyramid session factory using Beaker session settings supplied from a Paste configuration file""" prefixes = ('session.', 'beaker.session.') options = {} # Pull out any config args meant for beaker session. if there are any for k, v in settings.items(): for prefix in prefixes: if k.startswith(prefix): option_name = k[len(prefix):] if option_name == 'cookie_on_exception': v = asbool(v) options[option_name] = v options = coerce_session_params(options) return BeakerSessionFactoryConfig(**options) def set_cache_regions_from_settings(settings): """ Add cache support to the Pylons application. The ``settings`` passed to the configurator are used to setup the cache options. Cache options in the settings should start with either 'beaker.cache.' or 'cache.'. """ cache_settings = {'regions': []} for key in settings.keys(): for prefix in ['beaker.cache.', 'cache.']: if key.startswith(prefix): name = key.split(prefix)[1].strip() cache_settings[name] = settings[key].strip() if ('expire' in cache_settings and isinstance(cache_settings['expire'], basestring) and cache_settings['expire'].lower() in ['none', 'no']): cache_settings['expire'] = None coerce_cache_params(cache_settings) if 'enabled' not in cache_settings: cache_settings['enabled'] = True regions = cache_settings['regions'] if regions: for region in regions: if not region: continue region_settings = { 'data_dir': cache_settings.get('data_dir'), 'lock_dir': cache_settings.get('lock_dir'), 'expire': cache_settings.get('expire', 60), 'enabled': cache_settings['enabled'], 'key_length': cache_settings.get('key_length', 250), 'type': cache_settings.get('type'), 'url': cache_settings.get('url'), } region_prefix = '%s.' % region region_len = len(region_prefix) for key in list(cache_settings.keys()): if key.startswith(region_prefix): region_settings[key[region_len:]] = cache_settings.pop(key) if (isinstance(region_settings['expire'], basestring) and region_settings['expire'].lower() in ['none', 'no']): region_settings['expire'] = None coerce_cache_params(region_settings) cache.cache_regions[region] = region_settings def includeme(config): session_factory = session_factory_from_settings(config.registry.settings) config.set_session_factory(session_factory) set_cache_regions_from_settings(config.registry.settings)