Package: zc.wsgisessions
This is an implementation of persistent sessions as a WSGI middleware
usingzope.sessionas an underlying mechanism.To use it:Addzc.wsgisessionstoinstall_requireslist insetup.pyof
your application (e.g.,myapp)Add the following tomyapp.ini:[filter:sessions]
use = egg:zc.wsgisessionsYou can add to configuration:secure = trueor:http-only = offValid words are:true,false,on,off,yes,no, 1, and 0.Other options include:domain = .example.com

max-age = 10000

path = /fooYou can also specify a database name for session storage:db-name = appdbAddsessionsto the pipelineafterdatabase middleware, butbeforethe application.Add to a function that is listed asinitializerfor the database
middleware:zc.wsgisessions.sessions.initialize_database(database)You can also pass keyword arguments for:db_name,namespace,secret,timeout, andresolution.Add to a function that is listed asbobo.configure(initializer of
your WSGI application):zope.component.provideAdapter(zc.wsgisessions.sessions.get_session)You can use some helpers in your authentication code:PKG_KEY = __name__  # e.g., myapp.auth

def get_user(request):
    return zc.wsgisessions.sessions.get(request, PKG_KEY, 'user')

def save_user(request, user):
    zc.wsgisession.sessions.store(request, PKG_KEY, 'user', user)

def forget_user(request):
    return zc.wsgisessions.sessions.remove(request, PKG_KEY, 'user')When runningSeleniumtests,HttpOnlycookies cannot be used.
Set the option'http-only':Falsein theglobal_confdictionary
of your testing application.Detailed DocumentationSessionsThere are two aspects to the sessions support: browser identification,
and session storage.  Browsers are identified using cookies; if the
cookie isn’t set on an incoming request, the response sets it for future
requests.Session data are stored using a persistent session data container, as
defined by thezope.sessionpackage.  An instance is added to the
database at startup if not present.  We can control certain parameters
by passing keyword arguments to the database initializer.  One run of
this test uses the default settings, while a second run sets custom
parameters.>>> import re
>>> import zc.wsgisessions.testing
>>> import zc.wsgisessions.sessions
>>> db_name = 'sessions'
>>> if zc.wsgisessions.testing.TEST_DB_INIT:
...     db_name = 'test'
...     db = conn.get_connection(db_name).db()
...     zc.wsgisessions.sessions.initialize_database(
...         db,
...         db_name=db_name,
...         namespace='browserid_c0defeed',
...         secret='0.10612221415937506119',
...         timeout=(15 * 60),  # 15 minutes
...         resolution=60,      #  1 minute
...         )>>> dbroot = conn.get_connection(db_name).root()
>>> dbroot['sessions']
<zope.session.session.PersistentSessionDataContainer object at 0xc0defeed>>>> if zc.wsgisessions.testing.TEST_DB_INIT:
...     expected_id = re.compile('browserid_c0defeed')
...     expected_secret = re.compile('0.10612221415937506119')
...     expected_timeout = 15 * 60
...     expected_resolution = 60
... else:
...     expected_id = re.compile('browserid_[0-9a-f]{8}')
...     expected_secret = re.compile('[0-9a-f]{20}')
...     expected_timeout = 24 * 60 * 60
...     expected_resolution = 60 * 60
>>> re.match(expected_id, dbroot['browserid_info'][0]) is not None
True
>>> re.match(expected_secret, dbroot['browserid_info'][1]) is not None
True
>>> dbroot['sessions'].timeout == expected_timeout
True
>>> dbroot['sessions'].resolution == expected_resolution
TrueIf the configuration containssecureset to true or if the request is
https,secureis added to the Set-Cookie response.  AlsoHttpOnlyis
added to the Set-Cookie response, unless the configuration setshttp-onlyto false.>>> global_conf = {}
>>> filter_conf = {'db-name': db_name}
>>> filter = zc.wsgisessions.sessions.BrowserIdFilter(
...     global_conf, **filter_conf)(object())
>>> environ = {
...     'zodb.connection': conn.get_connection('test'),
...     'wsgi.url_scheme': 'https'
... }
>>> h = dict(filter.prepare(environ, lambda *args: args)(200, [], None)[1])
>>> cookie_parts = h['Set-Cookie'].split('; ')
>>> 'secure' in cookie_parts
True
>>> 'HttpOnly' in cookie_parts
TrueWhen the settings are changed in the filter configuration (in.inifile), the defaults are replaced.>>> filter_conf.update({'http-only': 'false', 'secure': 'true',
...                     'domain': '.example.com', 'max-age': '5000',
...                     'path': '/foo'})
>>> filter = zc.wsgisessions.sessions.BrowserIdFilter(
...     global_conf, **filter_conf)(object())
>>> environ['wsgi.url_scheme'] = 'http'
>>> h = dict(filter.prepare(environ, lambda *args: args)(200, [], None)[1])
>>> cookie_parts = h['Set-Cookie'].split('; ')
>>> 'secure' in cookie_parts
True
>>> 'HttpOnly' in cookie_parts
False
>>> 'Domain=.example.com' in cookie_parts
True
>>> 'Max-Age=5000' in cookie_parts
True
>>> 'Path=/foo' in cookie_parts
TrueNotice that the URL scheme above wasnothttps, but the secure was set
because it was requested in the filter configuration.For Selenium testing we need to resetHttpOnlyand since we are using
http URL scheme in development, the default forsecure(off) is
acceptable.  Notice that we are settinghttp-onlyin global
configuration this time to override the value from the settings in.inifile.>>> global_conf = {'http-only': 'off'}
>>> filter_conf = {'db-name': db_name, 'http-only': 'on'}
>>> filter = zc.wsgisessions.sessions.BrowserIdFilter(
...     global_conf, **filter_conf)(object())
>>> h = dict(filter.prepare(environ, lambda *args: args)(200, [], None)[1])
>>> cookie_parts = h['Set-Cookie'].split('; ')
>>> 'secure' in cookie_parts
False
>>> 'HttpOnly' in cookie_parts
FalseThe database name for session storage is set ininitialize_databasetosessionsby default or to a supplieddb_name(testfor the second
run of these tests).  If we try to pass a wrong database name to the
filter from its configuration (in.inifile) we’ll get an error.>>> if zc.wsgisessions.testing.TEST_DB_INIT:
...     filter_conf['db-name'] = 'sessions'
...     filter = zc.wsgisessions.sessions.BrowserIdFilter(
...         global_conf, **filter_conf)(object())
... else:
...     filter_conf['db-name'] = 'test'
...     filter = zc.wsgisessions.sessions.BrowserIdFilter(
...         global_conf, **filter_conf)(object())
>>> h = dict(filter.prepare(environ, lambda *args: args)(200, [], None)[1])
Traceback (most recent call last):
  ...
KeyError: 'browserid_info'Browser identificationInformation needed to support the cookies is also stored in the
database:>>> dbroot['browserid_info']
('browserid_...', '...')>>> cookie_name = dbroot['browserid_info'][0]>>> import webtest
>>> app = webtest.TestApp(app)
>>> response = app.get('http://localhost/')>>> cookie_value = app.cookies[cookie_name]
>>> len(cookie_value)
54If we change the secret in the database, we can cause the session
identifier to be re-set:>>> import random
>>> import transaction>>> secret = '%.20f' % random.random()
>>> dbroot['browserid_info'] = cookie_name, secret
>>> transaction.commit()>>> response = app.get('http://localhost/')>>> cookie_value == app.cookies[cookie_name]
False>>> cookie_value = app.cookies[cookie_name]
>>> app.cookies[cookie_name] = 'bad'
>>> response = app.get('http://localhost/')>>> cookie_value == app.cookies[cookie_name]
FalseSession storageOnce the cookie has been loaded from the request, or arranged to be sent
with the response, anISessionobject is stored on the request.
Let’s create one directly so we can see how that works:>>> sdc = dbroot['sessions']
>>> session = zc.wsgisessions.sessions.Session(cookie_value, sdc)>>> pkgdata = session['myapp.auth']
>>> pkgdata['mydata'] = 42>>> sdc[cookie_value]['myapp.auth']['mydata']
42>>> list(session)
Traceback (most recent call last):
...
NotImplementedErrorHelpers>>> import webob
>>> import zc.dbconnection
>>> import zope.session.interfaces
>>> zc.dbconnection.set_local(conn)
>>> environ = {'zc.wsgisessions.session': session}
>>> request = webob.Request(environ=environ)get(request, pkg_id, key=None)Retrieve a value from the session; if no key is specified, retrieves the
SessionPkgData container.>>> pkgdata = zc.wsgisessions.sessions.get(request, 'myapp.auth')
>>> zope.session.interfaces.ISessionPkgData.providedBy(pkgdata)
True>>> zc.wsgisessions.sessions.get(request, 'myapp.auth', 'blah') is None
True>>> pkgdata['blah'] = '!!!'
>>> zc.wsgisessions.sessions.get(request, 'myapp.auth', 'blah')
'!!!'>>> zc.wsgisessions.sessions.get(request, 'myapp.auth', 'mydata')
42When specifying a pkg identifier and a key name, the session data object
is not created if it doesn’t already exist.>>> zc.wsgisessions.sessions.get(request, "dontcreateme", "blah") is None
True
>>> adapter = zope.session.interfaces.ISession(request)
>>> adapter.get("dontcreateme") is None
Truestore(request, pkg_id, key, value)Store the key/value pair in the session.>>> obj = object()
>>> zc.wsgisessions.sessions.store(
...     request, 'myapp.auth', 'someobject', obj)
>>> zc.wsgisessions.sessions.get(
...     request, 'myapp.auth', 'someobject') is obj
True>>> obj = object()
>>> zc.wsgisessions.sessions.store(
...     request, 'myapp.data', 'someobject', obj)
>>> zc.wsgisessions.sessions.get(
...     request, 'myapp.auth', 'someobject') is obj
False>>> zc.wsgisessions.sessions.get(
...     request, 'myapp.data', 'someobject') is obj
Trueremove(request, pkg_id, key)Remove a value from the session by key.  If pkg_id is not specified,
the default pkg_id of zc.wsgisessions.sessions.KEY is used.>>> _obj = zc.wsgisessions.sessions.remove(
...     request, 'myapp.auth', 'someobject')
>>> zc.wsgisessions.sessions.get(
...     request, 'myapp.auth', 'someobject') is None
True>>> zc.wsgisessions.sessions.get(
...     request, 'myapp.data', 'someobject') is obj
True>>> zc.wsgisessions.sessions.remove(
...     request, 'myapp.data', 'someobject') is obj
True
>>> zc.wsgisessions.sessions.get(
...     request, 'myapp.data', 'someobject') is None
TrueThe underlying session data mapping is not created if it does not
already exist.>>> zc.wsgisessions.sessions.remove(
...     request, "dontcreateme", "somekey") is None
True
>>> adapter.get("dontcreateme") is None
TrueCHANGES0.6.1 (2013-10-08)Include CHANGES.txt in release.0.6.0 (2013-10-08)Adddomain,max-age, andpathconfiguration options.0.5.1 (2013-06-12)Open-source release.0.5 (2013-03-12)Use a cryptographically secure random number source (os.urandom) for
generating browser ids.Fix a bug in the get/remove helpers that caused SessionData objects
to be created unnecessarily.0.4 (2012-01-03)Accept a database name parameter for session storage.0.3 (2011-11-11)Put arguments to helper functions in a more logical order.Require pkg_id to discourage bad use pattern.0.2 (2011-11-10)Make http-only and secure configurable.Test configuration options.Test database initialization and options.0.1 (2011-11-10)Initial release