Source code for pyramid_redis_sessions

# -*- coding: utf-8 -*-

import functools

from pyramid.session import (
    signed_deserialize,
    signed_serialize,
    )

from .compat import cPickle
from .connection import get_default_connection
from .session import RedisSession
from .util import (
    _generate_session_id,
    _parse_settings,
    get_unique_session_id,
    )


[docs]def includeme(config): """ This function is detected by Pyramid so that you can easily include `pyramid_redis_sessions` in your `main` method like so:: config.include('pyramid_redis_sessions') Parameters: ``config`` A Pyramid ``config.Configurator`` """ settings = config.registry.settings # special rule for converting dotted python paths to callables for option in ('client_callable', 'serialize', 'deserialize', 'id_generator'): key = 'redis.sessions.%s' % option if key in settings: settings[key] = config.maybe_dotted(settings[key]) session_factory = session_factory_from_settings(settings) config.set_session_factory(session_factory)
[docs]def session_factory_from_settings(settings): """ Convenience method to construct a ``RedisSessionFactory`` from Paste config settings. Only settings prefixed with "redis.sessions" will be inspected and, if needed, coerced to their appropriate types (for example, casting the ``timeout`` value as an `int`). Parameters: ``settings`` A dict of Pyramid application settings """ options = _parse_settings(settings) return RedisSessionFactory(**options)
[docs]def RedisSessionFactory( secret, timeout=1200, cookie_name='session', cookie_max_age=None, cookie_path='/', cookie_domain=None, cookie_secure=False, cookie_httponly=True, cookie_on_exception=True, url=None, host='localhost', port=6379, db=0, password=None, socket_timeout=None, connection_pool=None, charset='utf-8', errors='strict', unix_socket_path=None, client_callable=None, serialize=cPickle.dumps, deserialize=cPickle.loads, id_generator=_generate_session_id, ): """ Constructs and returns a session factory that will provide session data from a Redis server. The returned factory can be supplied as the ``session_factory`` argument of a :class:`pyramid.config.Configurator` constructor, or used as the ``session_factory`` argument of the :meth:`pyramid.config.Configurator.set_session_factory` method. Parameters: ``secret`` A string which is used to sign the cookie. ``timeout`` A number of seconds of inactivity before a session times out. ``cookie_name`` The name of the cookie used for sessioning. Default: ``session``. ``cookie_max_age`` The maximum age of the cookie used for sessioning (in seconds). Default: ``None`` (browser scope). ``cookie_path`` The path used for the session cookie. Default: ``/``. ``cookie_domain`` The domain used for the session cookie. Default: ``None`` (no domain). ``cookie_secure`` The 'secure' flag of the session cookie. Default: ``False``. ``cookie_httponly`` The 'httpOnly' flag of the session cookie. Default: ``True``. ``cookie_on_exception`` If ``True``, set a session cookie even if an exception occurs while rendering a view. Default: ``True``. ``url`` A connection string for a Redis server, in the format: redis://username:password@localhost:6379/0 Default: ``None``. ``host`` A string representing the IP of your Redis server. Default: ``localhost``. ``port`` An integer representing the port of your Redis server. Default: ``6379``. ``db`` An integer to select a specific database on your Redis server. Default: ``0`` ``password`` A string password to connect to your Redis server/database if required. Default: ``None``. ``client_callable`` A python callable that accepts a Pyramid `request` and Redis config options and returns a Redis client such as redis-py's `StrictRedis`. Default: ``None``. ``serialize`` A function to serialize the session dict for storage in Redis. Default: ``cPickle.dumps``. ``deserialize`` A function to deserialize the stored session data in Redis. Default: ``cPickle.loads``. ``id_generator`` A function to create a unique ID to be used as the session key when a session is first created. Default: private function that uses sha1 with the time and random elements to create a 40 character unique ID. The following arguments are also passed straight to the ``StrictRedis`` constructor and allow you to further configure the Redis client:: socket_timeout connection_pool charset errors unix_socket_path """ def factory(request, new_session_id=get_unique_session_id): redis_options = dict( host=host, port=port, db=db, password=password, socket_timeout=socket_timeout, connection_pool=connection_pool, charset=charset, errors=errors, unix_socket_path=unix_socket_path, ) # an explicit client callable gets priority over the default redis = client_callable(request, **redis_options) \ if client_callable is not None \ else get_default_connection(request, url=url, **redis_options) # attempt to retrieve a session_id from the cookie session_id_from_cookie = _get_session_id_from_cookie( request=request, cookie_name=cookie_name, secret=secret, ) new_session = functools.partial( new_session_id, redis=redis, timeout=timeout, serialize=serialize, generator=id_generator, ) if session_id_from_cookie and redis.exists(session_id_from_cookie): session_id = session_id_from_cookie session_cookie_was_valid = True else: session_id = new_session() session_cookie_was_valid = False session = RedisSession( redis=redis, session_id=session_id, new=not session_cookie_was_valid, new_session=new_session, serialize=serialize, deserialize=deserialize, ) set_cookie = functools.partial( _set_cookie, cookie_name=cookie_name, cookie_max_age=cookie_max_age, cookie_path=cookie_path, cookie_domain=cookie_domain, cookie_secure=cookie_secure, cookie_httponly=cookie_httponly, secret=secret, ) delete_cookie = functools.partial( _delete_cookie, cookie_name=cookie_name, cookie_path=cookie_path, cookie_domain=cookie_domain, ) cookie_callback = functools.partial( _cookie_callback, session_cookie_was_valid=session_cookie_was_valid, cookie_on_exception=cookie_on_exception, set_cookie=set_cookie, delete_cookie=delete_cookie, ) request.add_response_callback(cookie_callback) return session return factory
def _get_session_id_from_cookie(request, cookie_name, secret): """ Attempts to retrieve and return a session ID from a session cookie in the current request. Returns None if the cookie isn't found or the value cannot be deserialized for any reason. """ cookieval = request.cookies.get(cookie_name) if cookieval is not None: try: session_id = signed_deserialize(cookieval, secret) return session_id except ValueError: pass return None def _set_cookie( request, response, cookie_name, cookie_max_age, cookie_path, cookie_domain, cookie_secure, cookie_httponly, secret, ): cookieval = signed_serialize(request.session.session_id, secret) response.set_cookie( cookie_name, value=cookieval, max_age=cookie_max_age, path=cookie_path, domain=cookie_domain, secure=cookie_secure, httponly=cookie_httponly, ) def _delete_cookie(response, cookie_name, cookie_path, cookie_domain): response.delete_cookie(cookie_name, path=cookie_path, domain=cookie_domain) def _cookie_callback( request, response, session_cookie_was_valid, cookie_on_exception, set_cookie, delete_cookie, ): """Response callback to set the appropriate Set-Cookie header.""" session = request.session if session._invalidated: if session_cookie_was_valid: delete_cookie(response=response) return if session.new: if cookie_on_exception is True or request.exception is None: set_cookie(request=request, response=response) elif session_cookie_was_valid: # We don't set a cookie for the new session here (as # cookie_on_exception is False and an exception was raised), but we # still need to delete the existing cookie for the session that the # request started with (as the session has now been invalidated). delete_cookie(response=response)