zope.session-3.9.5/0000755000175000017500000000000011621032676015337 5ustar tseavertseaver00000000000000zope.session-3.9.5/buildout.cfg0000644000175000017500000000025211621032632017636 0ustar tseavertseaver00000000000000[buildout] develop = . parts = test py [test] recipe = zc.recipe.testrunner eggs = zope.session [test] [py] recipe = zc.recipe.egg eggs = zope.session interpreter = py zope.session-3.9.5/COPYRIGHT.txt0000644000175000017500000000004011621032632017432 0ustar tseavertseaver00000000000000Zope Foundation and Contributorszope.session-3.9.5/README.txt0000644000175000017500000000021711621032632017025 0ustar tseavertseaver00000000000000This package provides interfaces for client identification and session support and their implementations for zope.publisher's request objects. zope.session-3.9.5/LICENSE.txt0000644000175000017500000000402611621032632017154 0ustar tseavertseaver00000000000000Zope Public License (ZPL) Version 2.1 A copyright notice accompanies this license document that identifies the copyright holders. This license has been certified as open source. It has also been designated as GPL compatible by the Free Software Foundation (FSF). Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions in source code must retain the accompanying copyright notice, this list of conditions, and the following disclaimer. 2. Redistributions in binary form must reproduce the accompanying copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Names of the copyright holders must not be used to endorse or promote products derived from this software without prior written permission from the copyright holders. 4. The right to distribute this software or to use it for any purpose does not give you the right to use Servicemarks (sm) or Trademarks (tm) of the copyright holders. Use of them is covered by separate agreement with the copyright holders. 5. If any files are modified, you must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. Disclaimer THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. zope.session-3.9.5/bootstrap.py0000644000175000017500000000330211621032632017714 0ustar tseavertseaver00000000000000############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Bootstrap a buildout-based project Simply run this script in a directory containing a buildout.cfg. The script accepts buildout command-line options, so you can use the -c option to specify an alternate configuration file. """ import os, shutil, sys, tempfile, urllib2 tmpeggs = tempfile.mkdtemp() ez = {} exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py' ).read() in ez ez['use_setuptools'](to_dir=tmpeggs, download_delay=0) import pkg_resources cmd = 'from setuptools.command.easy_install import main; main()' if sys.platform == 'win32': cmd = '"%s"' % cmd # work around spawn lamosity on windows ws = pkg_resources.working_set assert os.spawnle( os.P_WAIT, sys.executable, sys.executable, '-c', cmd, '-mqNxd', tmpeggs, 'zc.buildout', dict(os.environ, PYTHONPATH= ws.find(pkg_resources.Requirement.parse('setuptools')).location ), ) == 0 ws.add_entry(tmpeggs) ws.require('zc.buildout') import zc.buildout.buildout zc.buildout.buildout.main(sys.argv[1:] + ['bootstrap']) shutil.rmtree(tmpeggs) zope.session-3.9.5/setup.py0000644000175000017500000000506511621032632017047 0ustar tseavertseaver00000000000000############################################################################## # # Copyright (c) 2006 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## # This package is developed by the Zope Toolkit project, documented here: # http://docs.zope.org/zopetoolkit # When developing and releasing this package, please follow the documented # Zope Toolkit policies as described by this documentation. ############################################################################## """Setup for zope.session package """ import os from setuptools import setup, find_packages def read(*rnames): return open(os.path.join(os.path.dirname(__file__), *rnames)).read() setup(name='zope.session', version='3.9.5', author='Zope Foundation and Contributors', author_email='zope-dev@zope.org', description='Client identification and sessions for Zope', long_description=( read('README.txt') + '\n\n.. contents::\n\n' + read('src', 'zope', 'session', 'design.txt') + '\n\n' + read('src', 'zope', 'session', 'api.txt') + '\n\n' + read('CHANGES.txt') ), license='ZPL 2.1', keywords = "zope3 session", classifiers = [ 'Development Status :: 5 - Production/Stable', 'Environment :: Web Environment', 'Intended Audience :: Developers', 'License :: OSI Approved :: Zope Public License', 'Programming Language :: Python', 'Natural Language :: English', 'Operating System :: OS Independent', 'Topic :: Internet :: WWW/HTTP', 'Framework :: Zope3'], url='http://pypi.python.org/pypi/zope.session', packages=find_packages('src'), package_dir = {'': 'src'}, namespace_packages=['zope',], install_requires=[ 'setuptools', 'ZODB3', 'zope.component', 'zope.i18nmessageid', 'zope.interface', 'zope.location', 'zope.publisher', 'zope.minmax', ], extras_require=dict( test=[ 'zope.testing', ]), include_package_data = True, zip_safe = False, ) zope.session-3.9.5/src/0000755000175000017500000000000011621032676016126 5ustar tseavertseaver00000000000000zope.session-3.9.5/src/zope/0000755000175000017500000000000011621032676017103 5ustar tseavertseaver00000000000000zope.session-3.9.5/src/zope/session/0000755000175000017500000000000011621032676020566 5ustar tseavertseaver00000000000000zope.session-3.9.5/src/zope/session/classes.zcml0000644000175000017500000000436611621032632023113 0ustar tseavertseaver00000000000000 zope.session-3.9.5/src/zope/session/session.py0000644000175000017500000004446111621032632022624 0ustar tseavertseaver00000000000000############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Session implementation """ from cStringIO import StringIO import time, string, random, thread from UserDict import IterableUserDict from heapq import heapify, heappop import ZODB import ZODB.MappingStorage import zope.location import zope.minmax import persistent from BTrees.OOBTree import OOBTree import zope.component import zope.interface from zope.component.interfaces import ComponentLookupError from zope.publisher.interfaces import IRequest from zope.session.interfaces import \ IClientIdManager, IClientId, ISession, ISessionDataContainer, \ ISessionPkgData, ISessionData __docformat__ = 'restructuredtext' cookieSafeTrans = string.maketrans("+/", "-.") def digestEncode(s): """Encode SHA digest for cookie.""" return s.encode("base64")[:-2].translate(cookieSafeTrans) class ClientId(str): """See zope.session.interfaces.IClientId >>> import tests >>> request = tests.setUp() >>> id1 = ClientId(request) >>> id2 = ClientId(request) >>> id1 == id2 True >>> tests.tearDown() """ zope.interface.implements(IClientId) zope.component.adapts(IRequest) def __new__(cls, request): return str.__new__(cls, zope.component.getUtility(IClientIdManager).getClientId(request) ) class PersistentSessionDataContainer(zope.location.Location, persistent.Persistent, IterableUserDict): """A SessionDataContainer that stores data in the ZODB""" zope.interface.implements(ISessionDataContainer) _v_last_sweep = 0 # Epoch time sweep last run disable_implicit_sweeps = False def __init__(self): self.data = OOBTree() self.timeout = 1 * 60 * 60 # The "resolution" should be a small fraction of the timeout. self.resolution = 10 * 60 def __getitem__(self, pkg_id): """Retrieve an ISessionData >>> sdc = PersistentSessionDataContainer() >>> sdc.timeout = 60 >>> sdc.resolution = 3 >>> sdc['clientid'] = sd = SessionData() To ensure stale data is removed, we can wind back the clock using undocumented means... >>> sd.setLastAccessTime(sd.getLastAccessTime() - 64) >>> sdc._v_last_sweep = sdc._v_last_sweep - 4 Now the data should be garbage collected >>> sdc['clientid'] Traceback (most recent call last): [...] KeyError: 'clientid' Can you disable the automatic removal of stale data. >>> sdc.disable_implicit_sweeps = True >>> sdc['stale'] = stale = SessionData() Now we try the same method of winding back the clock. >>> stale.setLastAccessTime(sd.getLastAccessTime() - 64) >>> sdc._v_last_sweep = sdc._v_last_sweep - 4 But the data is not automatically removed. >>> sdc['stale'] #doctest: +ELLIPSIS We can manually remove stale data by calling sweep() if stale data isn't being automatically removed. >>> stale.setLastAccessTime(sd.getLastAccessTime() - 64) >>> sdc.sweep() >>> sdc['stale'] Traceback (most recent call last): [...] KeyError: 'stale' Now we turn automatic removal back on. >>> sdc.disable_implicit_sweeps = False Ensure the lastAccessTime on the ISessionData is being updated occasionally. The ISessionDataContainer maintains this whenever the ISessionData is set or retrieved. lastAccessTime on the ISessionData is set when it is added to the ISessionDataContainer >>> sdc['client_id'] = sd = SessionData() >>> sd.getLastAccessTime() > 0 True The lastAccessTime is also updated whenever the ISessionData is retrieved through the ISessionDataContainer, at most once every 'resolution' seconds. >>> then = sd.getLastAccessTime() - 4 >>> sd.setLastAccessTime(then) >>> now = sdc['client_id'].getLastAccessTime() >>> now > then True >>> time.sleep(1) >>> now == sdc['client_id'].getLastAccessTime() True Ensure the lastAccessTime is not modified and no garbage collection occurs when timeout == 0. We test this by faking a stale ISessionData object. >>> sdc.timeout = 0 >>> sd.setLastAccessTime(sd.getLastAccessTime() - 5000) >>> lastAccessTime = sd.getLastAccessTime() >>> sdc['client_id'].getLastAccessTime() == lastAccessTime True Next, we test session expiration functionality beyond transactions. >>> import transaction >>> from ZODB.DB import DB >>> from ZODB.DemoStorage import DemoStorage >>> sdc = PersistentSessionDataContainer() >>> sdc.timeout = 60 >>> sdc.resolution = 3 >>> db = DB(DemoStorage('test_storage')) >>> c = db.open() >>> c.root()['sdc'] = sdc >>> sdc['pkg_id'] = sd = SessionData() >>> sd['name'] = 'bob' >>> transaction.commit() Access immediately. the data should be accessible. >>> c.root()['sdc']['pkg_id']['name'] 'bob' Change the clock time and stale the session data. >>> sdc = c.root()['sdc'] >>> sd = sdc['pkg_id'] >>> sd.setLastAccessTime(sd.getLastAccessTime() - 64) >>> sdc._v_last_sweep = sdc._v_last_sweep - 4 >>> transaction.commit() The data should be garbage collected. >>> c.root()['sdc']['pkg_id']['name'] Traceback (most recent call last): [...] KeyError: 'pkg_id' Then abort transaction and access the same data again. The previous GC was cancelled, but deadline is over. The data should be garbage collected again. >>> transaction.abort() >>> c.root()['sdc']['pkg_id']['name'] Traceback (most recent call last): [...] KeyError: 'pkg_id' """ if self.timeout == 0: return IterableUserDict.__getitem__(self, pkg_id) now = time.time() # TODO: When scheduler exists, sweeping should be done by # a scheduled job since we are currently busy handling a # request and may end up doing simultaneous sweeps # If transaction is aborted after sweep. _v_last_sweep keep # incorrect sweep time. So when self.data is ghost, revert the time # to the previous _v_last_sweep time(_v_old_sweep). if self.data._p_state < 0: try: self._v_last_sweep = self._v_old_sweep del self._v_old_sweep except AttributeError: pass if (self._v_last_sweep + self.resolution < now and not self.disable_implicit_sweeps): self.sweep() if getattr(self, '_v_old_sweep', None) is None: self._v_old_sweep = self._v_last_sweep self._v_last_sweep = now rv = IterableUserDict.__getitem__(self, pkg_id) # Only update the lastAccessTime once every few minutes, rather than # every hit, to avoid ZODB bloat and conflicts if rv.getLastAccessTime() + self.resolution < now: rv.setLastAccessTime(int(now)) return rv def __setitem__(self, pkg_id, session_data): """Set an ISessionPkgData >>> sdc = PersistentSessionDataContainer() >>> sad = SessionData() __setitem__ sets the ISessionData's lastAccessTime >>> sad.getLastAccessTime() 0 >>> sdc['1'] = sad >>> 0 < sad.getLastAccessTime() <= time.time() True We can retrieve the same object we put in >>> sdc['1'] is sad True """ session_data.setLastAccessTime(int(time.time())) return IterableUserDict.__setitem__(self, pkg_id, session_data) def sweep(self): """Clean out stale data >>> sdc = PersistentSessionDataContainer() >>> sdc['1'] = SessionData() >>> sdc['2'] = SessionData() Wind back the clock on one of the ISessionData's so it gets garbage collected >>> sdc['2'].setLastAccessTime( ... sdc['2'].getLastAccessTime() - sdc.timeout * 2) Sweep should leave '1' and remove '2' >>> sdc.sweep() >>> sd1 = sdc['1'] >>> sd2 = sdc['2'] Traceback (most recent call last): [...] KeyError: '2' """ # We only update the lastAccessTime every 'resolution' seconds. # To compensate for this, we factor in the resolution when # calculating the expiry time to ensure that we never remove # data that has been accessed within timeout seconds. expire_time = time.time() - self.timeout - self.resolution heap = [(v.getLastAccessTime(), k) for k,v in self.data.items()] heapify(heap) while heap: lastAccessTime, key = heappop(heap) if lastAccessTime < expire_time: del self.data[key] else: return class RAMSessionDataContainer(PersistentSessionDataContainer): """A SessionDataContainer that stores data in RAM. Currently session data is not shared between Zope clients, so server affinity will need to be maintained to use this in a ZEO cluster. >>> sdc = RAMSessionDataContainer() >>> sdc['1'] = SessionData() >>> sdc['1'] is sdc['1'] True >>> ISessionData.providedBy(sdc['1']) True """ def __init__(self): self.resolution = 5*60 self.timeout = 1 * 60 * 60 # Something unique self.key = '%s.%s.%s' % (time.time(), random.random(), id(self)) _ram_storage = ZODB.MappingStorage.MappingStorage() _ram_db = ZODB.DB(_ram_storage) _conns = {} def _getData(self): # Open a connection to _ram_storage per thread tid = thread.get_ident() if not self._conns.has_key(tid): self._conns[tid] = self._ram_db.open() root = self._conns[tid].root() if not root.has_key(self.key): root[self.key] = OOBTree() return root[self.key] data = property(_getData, None) def sweep(self): super(RAMSessionDataContainer, self).sweep() self._ram_db.pack(time.time()) class Session(object): """See zope.session.interfaces.ISession""" zope.interface.implements(ISession) zope.component.adapts(IRequest) def __init__(self, request): self.client_id = str(IClientId(request)) def _sdc(self, pkg_id): # Locate the ISessionDataContainer by looking up the named # Utility, and falling back to the unnamed one. try: return zope.component.getUtility(ISessionDataContainer, pkg_id) except ComponentLookupError: return zope.component.getUtility(ISessionDataContainer) def get(self, pkg_id, default=None): """See zope.session.interfaces.ISession >>> import tests >>> request = tests.setUp(PersistentSessionDataContainer) If we use get we get None or default returned if the pkg_id is not there. >>> session = Session(request).get('not.there', 'default') >>> session 'default' This method is lazy and does not create the session data. >>> session = Session(request).get('not.there') >>> session is None True The __getitem__ method instead creates the data. >>> session = Session(request)['not.there'] >>> session is None False >>> session = Session(request).get('not.there') >>> session is None False >>> tests.tearDown() """ # The ISessionDataContainer contains two levels: # ISessionDataContainer[client_id] == ISessionData # ISessionDataContainer[client_id][pkg_id] == ISessionPkgData sdc = self._sdc(pkg_id) try: sd = sdc[self.client_id] except KeyError: return default try: return sd[pkg_id] except KeyError: return default def __getitem__(self, pkg_id): """See zope.session.interfaces.ISession >>> import tests >>> request = tests.setUp(PersistentSessionDataContainer) >>> request2 = tests.HTTPRequest(StringIO(''), {}, None) >>> ISession.providedBy(Session(request)) True Setup some sessions, each with a distinct namespace >>> session1 = Session(request)['products.foo'] >>> session2 = Session(request)['products.bar'] >>> session3 = Session(request2)['products.bar'] If we use the same parameters, we should retrieve the same object >>> session1 is Session(request)['products.foo'] True Make sure it returned sane values >>> ISessionPkgData.providedBy(session1) True Make sure that pkg_ids don't share a namespace. >>> session1['color'] = 'red' >>> session2['color'] = 'blue' >>> session3['color'] = 'vomit' >>> session1['color'] 'red' >>> session2['color'] 'blue' >>> session3['color'] 'vomit' >>> tests.tearDown() """ sdc = self._sdc(pkg_id) # The ISessionDataContainer contains two levels: # ISessionDataContainer[client_id] == ISessionData # ISessionDataContainer[client_id][pkg_id] == ISessionPkgData try: sd = sdc[self.client_id] except KeyError: sd = sdc[self.client_id] = SessionData() try: return sd[pkg_id] except KeyError: spd = sd[pkg_id] = SessionPkgData() return spd def __iter__(self): raise NotImplementedError class SessionData(persistent.Persistent, IterableUserDict): """See zope.session.interfaces.ISessionData >>> session = SessionData() >>> ISessionData.providedBy(session) True >>> session.getLastAccessTime() 0 Before the zope.minmax package this class used to have an attribute lastAccessTime initialized in the class itself to zero. To avoid changing the interface, that attribute has been turned into a property. This part tests the behavior of a legacy session which would have the lastAccessTime attribute loaded from the database. The implementation should work for that case as well as with the new session where lastAccessTime is a property. These tests will be removed in a later release (see the comments in the code below). First, create an instance of SessionData and remove a protected attribute _lastAccessTime from it to make it more like the legacy SessionData. The subsequent attempt to get lastAccessTime will return a 0, because the lastAccessTime is not there and the dictionary returns the default value zero supplied to its get() method. >>> legacy_session = SessionData() >>> del legacy_session._lastAccessTime >>> legacy_session.getLastAccessTime() 0 Now, artificially add lastAccessTime to the instance's dictionary. This should make it exactly like the legacy SessionData(). >>> legacy_session.__dict__['lastAccessTime'] = 42 >>> legacy_session.getLastAccessTime() 42 Finally, assign to lastAccessTime. Since the instance now looks like a legacy instance, this will trigger, through the property mechanism, a creation of a zope.minmax.Maximum() object which will take over the handling of this value and its conflict resolution from now on. >>> legacy_session.setLastAccessTime(13) >>> legacy_session._lastAccessTime.value 13 """ zope.interface.implements(ISessionData) # this is for support of legacy sessions; this comment and # the next line will be removed in a later release _lastAccessTime = None def __init__(self): self.data = OOBTree() self._lastAccessTime = zope.minmax.Maximum(0) # we include this for parallelism with setLastAccessTime def getLastAccessTime(self): # this conditional is for legacy sessions; this comment and # the next two lines will be removed in a later release if self._lastAccessTime is None: return self.__dict__.get('lastAccessTime', 0) return self._lastAccessTime.value # we need to set this value with setters in order to get optimal conflict # resolution behavior def setLastAccessTime(self, value): # this conditional is for legacy sessions; this comment and # the next two lines will be removed in a later release if self._lastAccessTime is None: self._lastAccessTime = zope.minmax.Maximum(0) self._lastAccessTime.value = value lastAccessTime = property(fget=getLastAccessTime, fset=setLastAccessTime, # consider deprecating doc='integer value of the last access time') class SessionPkgData(persistent.Persistent, IterableUserDict): """See zope.session.interfaces.ISessionPkgData >>> session = SessionPkgData() >>> ISessionPkgData.providedBy(session) True """ zope.interface.implements(ISessionPkgData) def __init__(self): self.data = OOBTree() zope.session-3.9.5/src/zope/session/http.py0000644000175000017500000005274211621032632022121 0ustar tseavertseaver00000000000000############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Session implementation using cookies """ import hmac import logging import random import re import string import sys import time from cStringIO import StringIO if sys.version_info[:2] >= (2, 5): from hashlib import sha1 from email.utils import formatdate else: import sha as sha1 from email.Utils import formatdate import zope.location from persistent import Persistent from zope import schema, component from zope.interface import implements from zope.publisher.interfaces.http import IHTTPRequest from zope.publisher.interfaces.http import IHTTPApplicationRequest from zope.i18nmessageid import ZopeMessageFactory as _ from zope.session.interfaces import IClientIdManager from zope.schema.fieldproperty import FieldProperty __docformat__ = 'restructuredtext' cookieSafeTrans = string.maketrans("+/", "-.") logger = logging.getLogger() def digestEncode(s): """Encode SHA digest for cookie.""" return s.encode("base64")[:-2].translate(cookieSafeTrans) class MissingClientIdException(Exception): """No ClientId found in Request""" class ICookieClientIdManager(IClientIdManager): """Manages sessions using a cookie""" namespace = schema.ASCIILine( title=_('Cookie Name'), description=_( "Name of cookie used to maintain state. " "Must be unique to the site domain name, and only contain " "ASCII letters, digits and '_'" ), required=True, min_length=1, max_length=30, constraint=re.compile("^[\d\w_]+$").search, ) cookieLifetime = schema.Int( title=_('Cookie Lifetime'), description=_( "Number of seconds until the browser expires the cookie. " "Leave blank expire the cookie when the browser is quit. " "Set to 0 to never expire. " ), min=0, required=False, default=None, missing_value=None, ) thirdparty = schema.Bool( title=_('Third party cookie'), description=_( "Is a third party issuing the identification cookie? " "Servers like Apache or Nginx have capabilities to issue " "identification cookies too. If Third party cookies are " "beeing used, Zope will never send a cookie back, just check " "for them." ), required=False, default=False, ) domain = schema.TextLine( title=_('Effective domain'), description=_( "An identification cookie can be restricted to a specific domain " "using this option. This option sets the ``domain`` attribute " "for the cookie header. It is useful for setting one " "identification cookie for multiple subdomains. So if this " "option is set to ``.example.org``, the cookie will be available " "for subdomains like ``yourname.example.org``. " "Note that if you set this option to some domain, the identification " "cookie won't be available for other domains, so, for example " "you won't be able to login using the SessionCredentials plugin " "via another domain." ), required=False, ) secure = schema.Bool( title=_('Request Secure communication'), required=False, default=False, ) postOnly = schema.Bool( title=_('Only set cookie on POST requests'), required=False, default=False, ) httpOnly = schema.Bool( title=_('The cookie cannot be accessed through client side scripts'), required=False, default=False, ) class CookieClientIdManager(zope.location.Location, Persistent): """Session utility implemented using cookies.""" implements(IClientIdManager, ICookieClientIdManager) thirdparty = FieldProperty(ICookieClientIdManager['thirdparty']) cookieLifetime = FieldProperty(ICookieClientIdManager['cookieLifetime']) secure = FieldProperty(ICookieClientIdManager['secure']) postOnly = FieldProperty(ICookieClientIdManager['postOnly']) domain = FieldProperty(ICookieClientIdManager['domain']) namespace = FieldProperty(ICookieClientIdManager['namespace']) httpOnly = FieldProperty(ICookieClientIdManager['httpOnly']) def __init__(self, namespace=None, secret=None): """Create the cookie-based cleint id manager We can pass namespace (cookie name) and/or secret string for generating client unique ids. If we don't pass either of them, they will be generated automatically, this is very handy when storing id manager in the persistent database, so they are saved between application restarts. >>> manager1 = CookieClientIdManager() >>> len(manager1.namespace) > 0 True >>> len(manager1.secret) > 0 True We can specify cookie name by hand. >>> manager2 = CookieClientIdManager('service_cookie') >>> manager2.namespace 'service_cookie' If we want to use CookieClientIdManager object as a non-persistent utility, we need to specify some constant secret, so it won't be recreated on each application restart. >>> manager3 = CookieClientIdManager(secret='some_secret') >>> manager3.secret 'some_secret' Of course, we can specify both cookie name and secret. >>> manager4 = CookieClientIdManager('service_cookie', 'some_secret') >>> manager4.namespace 'service_cookie' >>> manager4.secret 'some_secret' """ if namespace is None: namespace = "zope3_cs_%x" % (int(time.time()) - 1000000000) if secret is None: secret = '%.20f' % random.random() else: secret = str(secret) self.namespace = namespace self.secret = secret def getClientId(self, request): """Get the client id This creates one if necessary: >>> from zope.publisher.http import HTTPRequest >>> request = HTTPRequest(StringIO(''), {}) >>> bim = CookieClientIdManager() >>> id = bim.getClientId(request) >>> id == bim.getClientId(request) True The id is retained accross requests: >>> request2 = HTTPRequest(StringIO(''), {}) >>> request2._cookies = dict( ... [(name, cookie['value']) ... for (name, cookie) in request.response._cookies.items() ... ]) >>> id == bim.getClientId(request2) True >>> bool(id) True Note that the return value of this function is a string, not an IClientId. This is because this method is used to implement the IClientId Adapter. >>> type(id) == type('') True We don't set the client id unless we need to, so, for example, the second response doesn't have cookies set: >>> request2.response._cookies {} An exception to this is if the cookieLifetime is set to a non-zero integer value, in which case we do set it on every request, regardless of when it was last set: >>> bim.cookieLifetime = 3600 # one hour >>> id == bim.getClientId(request2) True >>> bool(request2.response._cookies) True If the postOnly attribute is set to a true value, then cookies will only be set on POST requests. >>> bim.postOnly = True >>> request = HTTPRequest(StringIO(''), {}) >>> bim.getClientId(request) Traceback (most recent call last): ... MissingClientIdException >>> print request.response.getCookie(bim.namespace) None >>> request = HTTPRequest(StringIO(''), {'REQUEST_METHOD': 'POST'}) >>> id = bim.getClientId(request) >>> id == bim.getClientId(request) True >>> request.response.getCookie(bim.namespace) is not None True >>> bim.postOnly = False It's also possible to use third-party cookies. E.g. Apache `mod_uid` or Nginx `ngx_http_userid_module` are able to issue user tracking cookies in front of Zope. In case thirdparty is activated Zope may not set a cookie. >>> bim.thirdparty = True >>> request = HTTPRequest(StringIO(''), {}) >>> bim.getClientId(request) Traceback (most recent call last): ... MissingClientIdException >>> print request.response.getCookie(bim.namespace) None """ sid = self.getRequestId(request) if sid is None: if (self.thirdparty or (self.postOnly and not (request.method == 'POST')) ): raise MissingClientIdException else: sid = self.generateUniqueId() self.setRequestId(request, sid) elif (not self.thirdparty) and self.cookieLifetime: # If we have a finite cookie lifetime, then set the cookie # on each request to avoid losing it. self.setRequestId(request, sid) return sid def generateUniqueId(self): """Generate a new, random, unique id. >>> bim = CookieClientIdManager() >>> id1 = bim.generateUniqueId() >>> id2 = bim.generateUniqueId() >>> id1 != id2 True """ data = "%.20f%.20f%.20f" % (random.random(), time.time(), time.clock()) # BBB code for Python 2.4, inspired by the fallback in hmac if hasattr(sha1, '__call__'): digest = sha1(data).digest() else: digest = sha1.new(data).digest() s = digestEncode(digest) # we store a HMAC of the random value together with it, which makes # our session ids unforgeable. mac = hmac.new(self.secret, s, digestmod=sha1).digest() return s + digestEncode(mac) def getRequestId(self, request): """Return the browser id encoded in request as a string Return None if an id is not set. For example: >>> from zope.publisher.http import HTTPRequest >>> request = HTTPRequest(StringIO(''), {}, None) >>> bim = CookieClientIdManager() Because no cookie has been set, we get no id: >>> bim.getRequestId(request) is None True We can set an id: >>> id1 = bim.generateUniqueId() >>> bim.setRequestId(request, id1) And get it back: >>> bim.getRequestId(request) == id1 True When we set the request id, we also set a response cookie. We can simulate getting this cookie back in a subsequent request: >>> request2 = HTTPRequest(StringIO(''), {}, None) >>> request2._cookies = dict( ... [(name, cookie['value']) ... for (name, cookie) in request.response._cookies.items() ... ]) And we get the same id back from the new request: >>> bim.getRequestId(request) == bim.getRequestId(request2) True Test a corner case where Python 2.6 hmac module does not allow unicode as input: >>> id_uni = unicode(bim.generateUniqueId()) >>> bim.setRequestId(request, id_uni) >>> bim.getRequestId(request) == id_uni True If another server is managing the ClientId cookies (Apache, Nginx) we're returning their value without checking: >>> bim.namespace = 'uid' >>> bim.thirdparty = True >>> request3 = HTTPRequest(StringIO(''), {}, None) >>> request3._cookies = {'uid': 'AQAAf0Y4gjgAAAQ3AwMEAg=='} >>> bim.getRequestId(request3) 'AQAAf0Y4gjgAAAQ3AwMEAg==' """ response_cookie = request.response.getCookie(self.namespace) if response_cookie: sid = response_cookie['value'] else: request = IHTTPApplicationRequest(request) sid = request.getCookies().get(self.namespace, None) if self.thirdparty: return sid else: # If there is an id set on the response, use that but # don't trust it. We need to check the response in case # there has already been a new session created during the # course of this request. if sid is None or len(sid) != 54: return None s, mac = sid[:27], sid[27:] # call encode() on value s a workaround a bug where the hmac # module only accepts str() types in Python 2.6 if (digestEncode(hmac.new( self.secret, s.encode(), digestmod=sha1 ).digest()) != mac): return None else: return sid def setRequestId(self, request, id): """Set cookie with id on request. This sets the response cookie: See the examples in getRequestId. Note that the id is checked for validity. Setting an invalid value is silently ignored: >>> from zope.publisher.http import HTTPRequest >>> request = HTTPRequest(StringIO(''), {}, None) >>> bim = CookieClientIdManager() >>> bim.getRequestId(request) >>> bim.setRequestId(request, 'invalid id') >>> bim.getRequestId(request) For now, the cookie path is the application URL: >>> cookie = request.response.getCookie(bim.namespace) >>> cookie['path'] == request.getApplicationURL(path_only=True) True By default, session cookies don't expire: >>> cookie.has_key('expires') False Expiry time of 0 means never (well - close enough) >>> bim.cookieLifetime = 0 >>> request = HTTPRequest(StringIO(''), {}, None) >>> bid = bim.getClientId(request) >>> cookie = request.response.getCookie(bim.namespace) >>> cookie['expires'] 'Tue, 19 Jan 2038 00:00:00 GMT' A non-zero value means to expire after than number of seconds: >>> bim.cookieLifetime = 3600 >>> request = HTTPRequest(StringIO(''), {}, None) >>> bid = bim.getClientId(request) >>> cookie = request.response.getCookie(bim.namespace) >>> import rfc822 >>> expires = time.mktime(rfc822.parsedate(cookie['expires'])) >>> expires > time.mktime(time.gmtime()) + 55*60 True If another server in front of Zope (Apache, Nginx) is managing the cookies we won't set any ClientId cookies: >>> request = HTTPRequest(StringIO(''), {}, None) >>> bim.thirdparty = True >>> bim.setRequestId(request, '1234') >>> cookie = request.response.getCookie(bim.namespace) >>> cookie If the secure attribute is set to a true value, then the secure cookie option is included. >>> bim.thirdparty = False >>> bim.cookieLifetime = None >>> request = HTTPRequest(StringIO(''), {}, None) >>> bim.secure = True >>> bim.setRequestId(request, '1234') >>> print request.response.getCookie(bim.namespace) {'path': '/', 'secure': True, 'value': '1234'} If the domain is specified, it will be set as a cookie attribute. >>> bim.domain = u'.example.org' >>> bim.setRequestId(request, '1234') >>> print request.response.getCookie(bim.namespace) {'path': '/', 'domain': u'.example.org', 'secure': True, 'value': '1234'} When the cookie is set, cache headers are added to the response to try to prevent the cookie header from being cached: >>> request.response.getHeader('Cache-Control') 'no-cache="Set-Cookie,Set-Cookie2"' >>> request.response.getHeader('Pragma') 'no-cache' >>> request.response.getHeader('Expires') 'Mon, 26 Jul 1997 05:00:00 GMT' If the httpOnly attribute is set to a true value, then the HttpOnly cookie option is included. >>> request = HTTPRequest(StringIO(''), {}, None) >>> bim.secure = False >>> bim.httpOnly = True >>> bim.setRequestId(request, '1234') >>> print request.response.getCookie(bim.namespace) {'path': '/', 'domain': u'.example.org', 'value': '1234', 'httponly': True} """ # TODO: Currently, the path is the ApplicationURL. This is reasonable, # and will be adequate for most purposes. # A better path to use would be that of the folder that contains # the site manager this service is registered within. However, # that would be expensive to look up on each request, and would # have to be altered to take virtual hosting into account. # Seeing as this utility instance has a unique namespace for its # cookie, using ApplicationURL shouldn't be a problem. if self.thirdparty: logger.warning('ClientIdManager is using thirdparty cookies, ' 'ignoring setIdRequest call') return response = request.response options = {} if self.cookieLifetime is not None: if self.cookieLifetime: expires = formatdate(time.time() + self.cookieLifetime, localtime=False, usegmt=True) else: expires = 'Tue, 19 Jan 2038 00:00:00 GMT' options['expires'] = expires if self.secure: options['secure'] = True if self.domain: options['domain'] = self.domain if self.httpOnly: options['HttpOnly'] = True response.setCookie( self.namespace, id, path=request.getApplicationURL(path_only=True), **options) response.setHeader('Cache-Control', 'no-cache="Set-Cookie,Set-Cookie2"') response.setHeader('Pragma', 'no-cache') response.setHeader('Expires', 'Mon, 26 Jul 1997 05:00:00 GMT') def notifyVirtualHostChanged(event): """Adjust cookie paths when IVirtualHostRequest information changes. Given an event, this method should call a CookieClientIdManager's setRequestId if a cookie is present in the response for that manager. To demonstrate we create a dummy manager object and event: >>> class DummyManager(object): ... implements(ICookieClientIdManager) ... namespace = 'foo' ... thirdparty = False ... request_id = None ... def setRequestId(self, request, id): ... self.request_id = id ... >>> manager = DummyManager() >>> component.provideUtility(manager, IClientIdManager) >>> from zope.publisher.http import HTTPRequest >>> class DummyEvent (object): ... request = HTTPRequest(StringIO(''), {}, None) >>> event = DummyEvent() With no cookies present, the manager should not be called: >>> notifyVirtualHostChanged(event) >>> manager.request_id is None True However, when a cookie *has* been set, the manager is called so it can update the cookie if need be: >>> event.request.response.setCookie('foo', 'bar') >>> notifyVirtualHostChanged(event) >>> manager.request_id 'bar' If a server in front of Zope manages the ClientIds (Apache, Nginx), we don't need to take care about the cookies >>> manager2 = DummyManager() >>> manager2.thirdparty = True >>> event2 = DummyEvent() However, when a cookie *has* been set, the manager is called so it can update the cookie if need be: >>> event2.request.response.setCookie('foo2', 'bar2') >>> notifyVirtualHostChanged(event2) >>> id = manager2.request_id >>> id is None True Cleanup of the utility registration: >>> import zope.component.testing >>> zope.component.testing.tearDown() """ # the event sends us a IHTTPApplicationRequest, but we need a # IHTTPRequest for the response attribute, and so does the cookie- # manager. request = IHTTPRequest(event.request, None) if event.request is None: return for name, manager in component.getUtilitiesFor(IClientIdManager): if manager and ICookieClientIdManager.providedBy(manager): # Third party ClientId Managers need no modification at all if not manager.thirdparty: cookie = request.response.getCookie(manager.namespace) if cookie: manager.setRequestId(request, cookie['value']) zope.session-3.9.5/src/zope/session/interfaces.py0000644000175000017500000001246211621032632023260 0ustar tseavertseaver00000000000000############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Interfaces for session utility. """ from zope.interface import Interface from zope.interface.common.mapping import IMapping, IReadMapping, IWriteMapping from zope import schema from zope.i18nmessageid import ZopeMessageFactory as _ __docformat__ = 'restructuredtext' class IClientIdManager(Interface): """Manages sessions - fake state over multiple client requests.""" def getClientId(request): """Return the client id for the given request as a string. If the request doesn't have an attached sessionId a new one will be generated. This will do whatever is possible to do the HTTP request to ensure the session id will be preserved. Depending on the specific method, further action might be necessary on the part of the user. See the documentation for the specific implementation and its interfaces. """ class IClientId(Interface): """A unique id representing a session""" def __str__(): """As a unique ASCII string""" class ISessionDataContainer(IReadMapping, IWriteMapping): """Stores data objects for sessions. The object implementing this interface is responsible for expiring data as it feels appropriate. Usage:: session_data_container[client_id][product_id][key] = value Note that this interface does not support the full mapping interface - the keys need to remain secret so we can't give access to keys(), values() etc. """ timeout = schema.Int( title=_(u"Timeout"), description=_( "Number of seconds before data becomes stale and may " "be removed. A value of '0' means no expiration."), default=3600, required=True, min=0, ) resolution = schema.Int( title=_("Timeout resolution (in seconds)"), description=_( "Defines what the 'resolution' of item timeout is. " "Setting this higher allows the transience machinery to " "do fewer 'writes' at the expense of causing items to time " "out later than the 'Data object timeout value' by a factor " "of (at most) this many seconds." ), default=10*60, required=True, min=0, ) def __getitem__(self, product_id): """Return an ISessionPkgData""" def __setitem__(self, product_id, value): """Store an ISessionPkgData""" class ISession(Interface): """This object allows retrieval of the correct ISessionData for a particular product id >>> session = ISession(request)[product_id] >>> session['color'] = 'red' True >>> ISessionData.providedBy(session) True """ def __getitem__(product_id): """Return the relevant ISessionPkgData This involves locating the correct ISessionDataContainer for the given product id, determining the client id, and returning the relevant ISessionPkgData. Caution: This method implicitly creates a new session for the user when it does not exist yet. """ def get(product_id, default=None): """Return the relevant ISessionPkgData or default if not available.""" class ISessionData(IReadMapping, IMapping): """Storage for a particular product id's session data Contains 0 or more ISessionPkgData instances """ def getLastAccessTime(): "return approximate epoch time this ISessionData was last retrieved" def setLastAccessTime(): "An API for ISessionDataContainer to set the last retrieved epoch time" # consider deprecating this property, or at least making it readonly. The # setter should be used instead of setting this property because of # conflict resolution: see https://bugs.launchpad.net/zope3/+bug/239531 lastAccessTime = schema.Int( title=_("Last Access Time"), description=_( "Approximate epoch time this ISessionData was last retrieved " "from its ISessionDataContainer" ), default=0, required=True, ) # Note that only IReadMapping and IWriteMaping are implemented. # We cannot give access to the keys, as they need to remain secret. def __getitem__(self, client_id): """Return an ISessionPkgData""" def __setitem__(self, client_id, session_pkg_data): """Store an ISessionPkgData""" class ISessionPkgData(IMapping): """Storage for a particular product id and browser id's session data Data is stored persistently and transactionally. Data stored must be persistent or picklable. """ zope.session-3.9.5/src/zope/session/api.txt0000644000175000017500000001160711621032632022075 0ustar tseavertseaver00000000000000Zope3 Session Implementation ============================ Overview -------- .. CAUTION:: Session data is maintained on the server. This gives a security advantage in that we can assume that a client has not tampered with the data. However, this can have major implications for scalability as modifying session data too frequently can put a significant load on servers and in extreme situations render your site unusable. Developers should keep this in mind when writing code or risk problems when their application is run in a production environment. Applications requiring write-intensive session implementations (such as page counters) should consider using cookies or specialized session implementations. Sessions allow us to fake state over a stateless protocol - HTTP. We do this by having a unique identifier stored across multiple HTTP requests, be it a cookie or some id mangled into the URL. The `IClientIdManager` Utility provides this unique id. It is responsible for propagating this id so that future requests from the client get the same id (eg. by setting an HTTP cookie). This utility is used when we adapt the request to the unique client id: >>> client_id = IClientId(request) The `ISession` adapter gives us a mapping that can be used to store and retrieve session data. A unique key (the package id) is used to avoid namespace clashes: >>> pkg_id = 'products.foo' >>> session = ISession(request)[pkg_id] >>> session['color'] = 'red' >>> session2 = ISession(request)['products.bar'] >>> session2['color'] = 'blue' >>> session['color'] 'red' >>> session2['color'] 'blue' Data Storage ------------ The actual data is stored in an `ISessionDataContainer` utility. `ISession` chooses which `ISessionDataContainer` should be used by looking up as a named utility using the package id. This allows the site administrator to configure where the session data is actually stored by adding a registration for desired `ISessionDataContainer` with the correct name. >>> import zope.component >>> sdc = zope.component.getUtility(ISessionDataContainer, pkg_id) >>> sdc[client_id][pkg_id] is session True >>> sdc[client_id][pkg_id]['color'] 'red' If no `ISessionDataContainer` utility can be located by name using the package id, then the unnamed `ISessionDataContainer` utility is used as a fallback. An unnamed `ISessionDataContainer` is automatically created for you, which may replaced with a different implementation if desired. >>> ISession(request)['unknown'] \ ... is zope.component.getUtility(ISessionDataContainer)[client_id]\ ... ['unknown'] True The `ISessionDataContainer` contains `ISessionData` objects, and `ISessionData` objects in turn contain `ISessionPkgData` objects. You should never need to know this unless you are writing administrative views for the session machinery. >>> ISessionData.providedBy(sdc[client_id]) True >>> ISessionPkgData.providedBy(sdc[client_id][pkg_id]) True The `ISessionDataContainer` is responsible for expiring session data. The expiry time can be configured by settings its `timeout` attribute. >>> sdc.timeout = 1200 # 1200 seconds or 20 minutes Restrictions ------------ Data stored in the session must be persistent or picklable. >>> session['oops'] = open(__file__) >>> import transaction >>> transaction.commit() Traceback (most recent call last): [...] TypeError: can't pickle file objects Clean up: >>> transaction.abort() Page Templates -------------- Session data may be accessed in page template documents using TALES:: green or::
Session Timeout --------------- Sessions have a timeout (defaulting to an hour, in seconds). >>> import zope.session.session >>> data_container = zope.session.session.PersistentSessionDataContainer() >>> data_container.timeout 3600 We need to keep up with when the session was last used (to know when it needs to be expired), but it would be too resource-intensive to write the last access time every, single time the session data is touched. The session machinery compromises by only recording the last access time periodically. That period is called the "resolution". That also means that if the last-access-time + the-resolution < now, then the session is considered to have timed out. The default resolution is 10 minutes (600 seconds), meaning that a users session will actually time out sometime between 50 and 60 minutes. >>> data_container.resolution 600 zope.session-3.9.5/src/zope/session/configure.zcml0000644000175000017500000000031711621032632023427 0ustar tseavertseaver00000000000000 zope.session-3.9.5/src/zope/session/design.txt0000644000175000017500000001113211621032632022566 0ustar tseavertseaver00000000000000Sessions ======== Sessions provide a way to temporarily associate information with a client without requiring the authentication of a principal. We associate an identifier with a particular client. Whenever we get a request from that client, we compute the identifier and use the identifier to look up associated information, which is stored on the server. A major disadvantage of sessions is that they require management of information on the server. This can have major implications for scalability. It is possible for a framework to make use of session data very easy for the developer. This is great if scalability is not an issue, otherwise, it is a booby trap. Design Issues ------------- Sessions introduce a number of issues to be considered: - Clients have to be identified. A number of approaches are possible, including: o Using HTTP cookies. The application assigns a client identifier, which is stored in a cookie. This technique is the most straightforward, but can be defeated if the client does not support HTTP cookies (usually because the feature has been disabled). o Using URLs. The application assigns a client identifier, which is stored in the URL. This makes URLs a bit uglier and requires some care. If people copy URLs and send them to others, then you could end up with multiple clients with the same session identifier. There are a number of ways to reduce the risk of accidental reuse of session identifiers: - Embed the client IP address in the identifier - Expire the identifier o Use hidden form variables. This complicates applications. It requires all requests to be POST requests and requires the maintenance of the hidden variables. o Use the client IP address This doesn't work very well, because an IP address may be shared by many clients. - Data storage Data can be simply stored in the object database. This provides lots of flexibility. You can store pretty much anything you want as long as it is persistent. You get the full benefit of the object database, such as transactions, transparency, clustering, and so on. Using the object database is especially useful when: - Writes are infrequent - Data are complex If writes are frequent, then the object database introduces scalability problems. Really, any transactional database is likely to introduce problems with frequent writes. If you are tempted to update session data on every request, think very hard about it. You are creating a scalability problem. If you know that scalability is not (and never will be) an issue, you can just use the object database. If you have client data that needs to be updated often (as in every request), consider storing the data on the client. (Like all data received from a client, it may be tainted and, in most instances, should not be trusted. Sensitive information that the user should not see should likewise not be stored on the client, unless encrypted with a key the client has no access to.) If you can't store it on the client, then consider some other storage mechanism, like a fast database, possibly without transaction support. You may be tempted to store session data in memory for speed. This doesn't turn out to work very well. If you need scalability, then you need to be able to use an application-server cluster and storage of session data in memory defeats that. You can use "server-affinity" to assure that requests from a client always go back to the same server, but not all load balancers support server affinity, and, for those that do, enabling server affinity tends to defeat load balancing. - Session expiration You may wish to ensure that sessions terminate after some period of time. This may be for security reasons, or to avoid accidental sharing of a session among multiple clients. The policy might be expressed in terms of total session time, or maximum inactive time, or some combination. There are a number of ways to approach this. You can expire client ids. You can expire session data. - Data expiration Because HTTP is a stateless protocol, you can't tell whether a user is thinking about a task or has simply stopped working on it. Some means is needed to free server session storage that is no-longer needed. The simplest strategy is to never remove data. This strategy has some obvious disadvantages. Other strategies can be viewed as optimizations of the basic strategy. It is important to realize that a data expiration strategy can be informed by, but need not be constrained by a session-expiration strategy. zope.session-3.9.5/src/zope/session/tests.py0000644000175000017500000001331511621032632022275 0ustar tseavertseaver00000000000000############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Session tests """ from cStringIO import StringIO from zope.testing import cleanup import doctest import os import os.path import transaction import unittest import zope.component from zope.component import provideHandler, getGlobalSiteManager from zope.session.interfaces import IClientId, IClientIdManager, ISession from zope.session.interfaces import ISessionDataContainer from zope.session.interfaces import ISessionPkgData, ISessionData from zope.session.session import ClientId, Session from zope.session.session import PersistentSessionDataContainer from zope.session.session import RAMSessionDataContainer from zope.session.http import CookieClientIdManager from zope.publisher.interfaces import IRequest from zope.publisher.http import HTTPRequest def setUp(session_data_container_class=PersistentSessionDataContainer): cleanup.setUp() zope.component.provideAdapter(ClientId, (IRequest,), IClientId) zope.component.provideAdapter(Session, (IRequest,), ISession) zope.component.provideUtility(CookieClientIdManager(), IClientIdManager) sdc = session_data_container_class() for product_id in ('', 'products.foo', 'products.bar', 'products.baz'): zope.component.provideUtility(sdc, ISessionDataContainer, product_id) request = HTTPRequest(StringIO(), {}, None) return request def tearDown(): cleanup.tearDown() # Test the code in our API documentation is correct def test_documentation(): pass test_documentation.__doc__ = ''' >>> request = setUp(RAMSessionDataContainer) %s >>> tearDown() ''' % (open(os.path.join(os.path.dirname(__file__), 'api.txt')).read(),) def tearDownTransaction(test): transaction.abort() def testConflicts(): """The SessionData objects have been plagued with unnecessary ConflictErrors. The current implementation makes the most common source of ConflictErrors in the past, setting the lastAccessTime, no longer a problem in this regard. To illustrate this, we will do a bit of an integration test. We'll begin by getting a connection and putting a session data container in the root, within transaction manager "A". >>> try: ... # ZODB 3.8 ... from ZODB.DB import DB ... from ZODB.tests.util import ConflictResolvingMappingStorage ... db = DB(ConflictResolvingMappingStorage()) ... except ImportError: ... # ZODB 3.9 (ConflictResolvingMappingStorage no longer exists) ... import ZODB.DB ... db = ZODB.DB('Data.fs') >>> from zope.session.session import ( ... PersistentSessionDataContainer, SessionData) >>> import transaction >>> tm_A = transaction.TransactionManager() >>> conn_A = db.open(transaction_manager=tm_A) >>> root_A = conn_A.root() >>> sdc_A = root_A['sdc'] = PersistentSessionDataContainer() >>> sdc_A.resolution = 3 >>> sd_A = sdc_A['clientid'] = SessionData() >>> then = sd_A.getLastAccessTime() - 4 >>> sd_A.setLastAccessTime(then) >>> tm_A.commit() Now we have a session data container with a session data lastAccessTime that is set to four seconds ago. Since we set the resolution to three seconds, the next time the session is accessed, the lastAccessTime should be updated. We will access the session simultaneously in two transactions, which will set the updated lastAccessTime on both objects, and then commit. Because of the conflict resolution code in zope.minmax, both commits will succeed, which is what we wanted to demonstrate. >>> tm_B = transaction.TransactionManager() >>> conn_B = db.open(transaction_manager=tm_B) >>> root_B = conn_B.root() >>> sdc_B = root_B['sdc'] >>> sd_B = sdc_B['clientid'] # has side effect of updating lastAccessTime >>> sd_B.getLastAccessTime() > then True >>> sd_A is sdc_A['clientid'] # has side effect of updating lastAccessTime True >>> sd_A.getLastAccessTime() > then True >>> tm_A.commit() >>> tm_B.commit() Q.E.D. """ def testSessionIterationBug(): """ The zope.session.session.Session ISession implementation defines an `__iter__` method that raises NotImplementedError in order to avoid an infinite loop if iteration or a test for containment is attempted on an instance. >>> import zope.session.session >>> request = setUp() >>> session = zope.session.session.Session(request) >>> try: ... "blah" in session ... except TypeError: ... pass ... else: ... raise Exception("Should have raised TypeError") >>> for i in session: ... raise Exception("Should have raised NotImplementedError") Traceback (most recent call last): ... NotImplementedError >>> tearDown() """ def test_suite(): suite = unittest.TestSuite() suite.addTest(doctest.DocTestSuite()) suite.addTest(doctest.DocTestSuite('zope.session.session', tearDown=tearDownTransaction)) suite.addTest(doctest.DocTestSuite('zope.session.http', optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,) ) return suite if __name__ == '__main__': unittest.main() zope.session-3.9.5/src/zope/session/apidoc.zcml0000644000175000017500000000070011621032632022701 0ustar tseavertseaver00000000000000 zope.session-3.9.5/src/zope/session/__init__.py0000644000175000017500000000125611621032632022673 0ustar tseavertseaver00000000000000############################################################################## # # Copyright (c) 2004 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Core session interfaces and implementation """ zope.session-3.9.5/src/zope/session/adapters.zcml0000644000175000017500000000067611621032632023261 0ustar tseavertseaver00000000000000 zope.session-3.9.5/src/zope/session/subscribers.zcml0000644000175000017500000000032111621032632023767 0ustar tseavertseaver00000000000000 zope.session-3.9.5/src/zope/__init__.py0000644000175000017500000000007011621032632021201 0ustar tseavertseaver00000000000000__import__('pkg_resources').declare_namespace(__name__) zope.session-3.9.5/src/zope.session.egg-info/0000755000175000017500000000000011621032676022257 5ustar tseavertseaver00000000000000zope.session-3.9.5/src/zope.session.egg-info/not-zip-safe0000644000175000017500000000000111621032646024502 0ustar tseavertseaver00000000000000 zope.session-3.9.5/src/zope.session.egg-info/namespace_packages.txt0000644000175000017500000000000511621032676026605 0ustar tseavertseaver00000000000000zope zope.session-3.9.5/src/zope.session.egg-info/requires.txt0000644000175000017500000000017711621032676024664 0ustar tseavertseaver00000000000000setuptools ZODB3 zope.component zope.i18nmessageid zope.interface zope.location zope.publisher zope.minmax [test] zope.testingzope.session-3.9.5/src/zope.session.egg-info/SOURCES.txt0000644000175000017500000000134311621032676024144 0ustar tseavertseaver00000000000000CHANGES.txt COPYRIGHT.txt LICENSE.txt README.txt bootstrap.py buildout.cfg setup.py src/zope/__init__.py src/zope.session.egg-info/PKG-INFO src/zope.session.egg-info/SOURCES.txt src/zope.session.egg-info/dependency_links.txt src/zope.session.egg-info/namespace_packages.txt src/zope.session.egg-info/not-zip-safe src/zope.session.egg-info/requires.txt src/zope.session.egg-info/top_level.txt src/zope/session/__init__.py src/zope/session/adapters.zcml src/zope/session/api.txt src/zope/session/apidoc.zcml src/zope/session/classes.zcml src/zope/session/configure.zcml src/zope/session/design.txt src/zope/session/http.py src/zope/session/interfaces.py src/zope/session/session.py src/zope/session/subscribers.zcml src/zope/session/tests.pyzope.session-3.9.5/src/zope.session.egg-info/top_level.txt0000644000175000017500000000000511621032676025004 0ustar tseavertseaver00000000000000zope zope.session-3.9.5/src/zope.session.egg-info/dependency_links.txt0000644000175000017500000000000111621032676026325 0ustar tseavertseaver00000000000000 zope.session-3.9.5/src/zope.session.egg-info/PKG-INFO0000644000175000017500000004453311621032676023365 0ustar tseavertseaver00000000000000Metadata-Version: 1.0 Name: zope.session Version: 3.9.5 Summary: Client identification and sessions for Zope Home-page: http://pypi.python.org/pypi/zope.session Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: This package provides interfaces for client identification and session support and their implementations for zope.publisher's request objects. .. contents:: Sessions ======== Sessions provide a way to temporarily associate information with a client without requiring the authentication of a principal. We associate an identifier with a particular client. Whenever we get a request from that client, we compute the identifier and use the identifier to look up associated information, which is stored on the server. A major disadvantage of sessions is that they require management of information on the server. This can have major implications for scalability. It is possible for a framework to make use of session data very easy for the developer. This is great if scalability is not an issue, otherwise, it is a booby trap. Design Issues ------------- Sessions introduce a number of issues to be considered: - Clients have to be identified. A number of approaches are possible, including: o Using HTTP cookies. The application assigns a client identifier, which is stored in a cookie. This technique is the most straightforward, but can be defeated if the client does not support HTTP cookies (usually because the feature has been disabled). o Using URLs. The application assigns a client identifier, which is stored in the URL. This makes URLs a bit uglier and requires some care. If people copy URLs and send them to others, then you could end up with multiple clients with the same session identifier. There are a number of ways to reduce the risk of accidental reuse of session identifiers: - Embed the client IP address in the identifier - Expire the identifier o Use hidden form variables. This complicates applications. It requires all requests to be POST requests and requires the maintenance of the hidden variables. o Use the client IP address This doesn't work very well, because an IP address may be shared by many clients. - Data storage Data can be simply stored in the object database. This provides lots of flexibility. You can store pretty much anything you want as long as it is persistent. You get the full benefit of the object database, such as transactions, transparency, clustering, and so on. Using the object database is especially useful when: - Writes are infrequent - Data are complex If writes are frequent, then the object database introduces scalability problems. Really, any transactional database is likely to introduce problems with frequent writes. If you are tempted to update session data on every request, think very hard about it. You are creating a scalability problem. If you know that scalability is not (and never will be) an issue, you can just use the object database. If you have client data that needs to be updated often (as in every request), consider storing the data on the client. (Like all data received from a client, it may be tainted and, in most instances, should not be trusted. Sensitive information that the user should not see should likewise not be stored on the client, unless encrypted with a key the client has no access to.) If you can't store it on the client, then consider some other storage mechanism, like a fast database, possibly without transaction support. You may be tempted to store session data in memory for speed. This doesn't turn out to work very well. If you need scalability, then you need to be able to use an application-server cluster and storage of session data in memory defeats that. You can use "server-affinity" to assure that requests from a client always go back to the same server, but not all load balancers support server affinity, and, for those that do, enabling server affinity tends to defeat load balancing. - Session expiration You may wish to ensure that sessions terminate after some period of time. This may be for security reasons, or to avoid accidental sharing of a session among multiple clients. The policy might be expressed in terms of total session time, or maximum inactive time, or some combination. There are a number of ways to approach this. You can expire client ids. You can expire session data. - Data expiration Because HTTP is a stateless protocol, you can't tell whether a user is thinking about a task or has simply stopped working on it. Some means is needed to free server session storage that is no-longer needed. The simplest strategy is to never remove data. This strategy has some obvious disadvantages. Other strategies can be viewed as optimizations of the basic strategy. It is important to realize that a data expiration strategy can be informed by, but need not be constrained by a session-expiration strategy. Zope3 Session Implementation ============================ Overview -------- .. CAUTION:: Session data is maintained on the server. This gives a security advantage in that we can assume that a client has not tampered with the data. However, this can have major implications for scalability as modifying session data too frequently can put a significant load on servers and in extreme situations render your site unusable. Developers should keep this in mind when writing code or risk problems when their application is run in a production environment. Applications requiring write-intensive session implementations (such as page counters) should consider using cookies or specialized session implementations. Sessions allow us to fake state over a stateless protocol - HTTP. We do this by having a unique identifier stored across multiple HTTP requests, be it a cookie or some id mangled into the URL. The `IClientIdManager` Utility provides this unique id. It is responsible for propagating this id so that future requests from the client get the same id (eg. by setting an HTTP cookie). This utility is used when we adapt the request to the unique client id: >>> client_id = IClientId(request) The `ISession` adapter gives us a mapping that can be used to store and retrieve session data. A unique key (the package id) is used to avoid namespace clashes: >>> pkg_id = 'products.foo' >>> session = ISession(request)[pkg_id] >>> session['color'] = 'red' >>> session2 = ISession(request)['products.bar'] >>> session2['color'] = 'blue' >>> session['color'] 'red' >>> session2['color'] 'blue' Data Storage ------------ The actual data is stored in an `ISessionDataContainer` utility. `ISession` chooses which `ISessionDataContainer` should be used by looking up as a named utility using the package id. This allows the site administrator to configure where the session data is actually stored by adding a registration for desired `ISessionDataContainer` with the correct name. >>> import zope.component >>> sdc = zope.component.getUtility(ISessionDataContainer, pkg_id) >>> sdc[client_id][pkg_id] is session True >>> sdc[client_id][pkg_id]['color'] 'red' If no `ISessionDataContainer` utility can be located by name using the package id, then the unnamed `ISessionDataContainer` utility is used as a fallback. An unnamed `ISessionDataContainer` is automatically created for you, which may replaced with a different implementation if desired. >>> ISession(request)['unknown'] \ ... is zope.component.getUtility(ISessionDataContainer)[client_id]\ ... ['unknown'] True The `ISessionDataContainer` contains `ISessionData` objects, and `ISessionData` objects in turn contain `ISessionPkgData` objects. You should never need to know this unless you are writing administrative views for the session machinery. >>> ISessionData.providedBy(sdc[client_id]) True >>> ISessionPkgData.providedBy(sdc[client_id][pkg_id]) True The `ISessionDataContainer` is responsible for expiring session data. The expiry time can be configured by settings its `timeout` attribute. >>> sdc.timeout = 1200 # 1200 seconds or 20 minutes Restrictions ------------ Data stored in the session must be persistent or picklable. >>> session['oops'] = open(__file__) >>> import transaction >>> transaction.commit() Traceback (most recent call last): [...] TypeError: can't pickle file objects Clean up: >>> transaction.abort() Page Templates -------------- Session data may be accessed in page template documents using TALES:: green or::
Session Timeout --------------- Sessions have a timeout (defaulting to an hour, in seconds). >>> import zope.session.session >>> data_container = zope.session.session.PersistentSessionDataContainer() >>> data_container.timeout 3600 We need to keep up with when the session was last used (to know when it needs to be expired), but it would be too resource-intensive to write the last access time every, single time the session data is touched. The session machinery compromises by only recording the last access time periodically. That period is called the "resolution". That also means that if the last-access-time + the-resolution < now, then the session is considered to have timed out. The default resolution is 10 minutes (600 seconds), meaning that a users session will actually time out sometime between 50 and 60 minutes. >>> data_container.resolution 600 CHANGES ======= 3.9.5 (2011-08-11) ------------------ - LP #824355: enable support for HttpOnly cookies. - Fix a bug in zope.session.session.Session that would trigger an infinite loop if either iteration or a containment test were attempted on an instance. 3.9.4 (2011-03-07) ------------------ - Added an explicit `provides` to the IClientId adapter declaration in adapter.zcml. - Added option to disable implicit sweeps in PersistentSessionDataContainer. 3.9.3 (2010-09-25) ------------------ - Added test extra to declare test dependency on ``zope.testing``. - Using Python's ``doctest`` module instead of depreacted ``zope.testing.doctest``. 3.9.2 (2009-11-23) ------------------ - Fix Python 2.4 hmac compatibility issue by only using hashlib in Python versions 2.5 and above. - Use the CookieClientIdManager's secret as the hmac key instead of the message when constructing and verifying client ids. - Make it possible to construct CookieClientIdManager passing cookie namespace and/or secret as constructor's arguments. - Use zope.schema.fieldproperty.FieldProperty for "namespace" attribute of CookieClientIdManager, just like for other attributes in its interface. Also, make ICookieClientIdManager's "namespace" field an ASCIILine, so it accepts only non-unicode strings for cookie names. 3.9.1 (2009-04-20) ------------------ - Restore compatibility with Python 2.4. 3.9.0 (2009-03-19) ------------------ - Don't raise deprecation warnings on Python 2.6. - Drop dependency on ``zope.annotation``. Instead, we make classes implement `IAttributeAnnotatable` in ZCML configuration, only if ``zope.annotation`` is available. If your code relies on annotatable `CookieClientIdManager` and `PersistentSessionDataContainer` and you don't include the zcml classes configuration of this package, you'll need to use `classImplements` function from ``zope.interface`` to make those classes implement `IAttributeAnnotatable` again. - Drop dependency on zope.app.http, use standard date formatting function from the ``email.utils`` module. - Zope 3 application bootstrapping code for session utilities was moved into zope.app.appsetup package, thus drop dependency on zope.app.appsetup in this package. - Drop testing dependencies, as we don't need anything behind zope.testing and previous dependencies was simply migrated from zope.app.session before. - Remove zpkg files and zcml slugs. - Update package's description a bit. 3.8.1 (2009-02-23) ------------------ - Add an ability to set cookie effective domain for CookieClientIdManager. This is useful for simple cases when you have your application set up on one domain and you want your identification cookie be active for subdomains. - Python 2.6 compatibility change. Encode strings before calling hmac.new() as the function no longer accepts the unicode() type. 3.8.0 (2008-12-31) ------------------ - Add missing test dependency on ``zope.site`` and ``zope.app.publication``. 3.7.1 (2008-12-30) ------------------ - Specify i18n_domain for titles in apidoc.zcml - ZODB 3.9 no longer contains ZODB.utils.ConflictResolvingMappingStorage, fixed tests, so they work both with ZODB 3.8 and 3.9. 3.7.0 (2008-10-03) ------------------ New features: - Added a 'postOnly' option on CookieClientIdManagers to only allow setting the client id cookie on POST requests. This is to further reduce risk from broken caches handing the same client id out to multiple users. (Of course, it doesn't help if caches are broken enough to cache POSTs.) 3.6.0 (2008-08-12) ------------------ New features: - Added a 'secure' option on CookieClientIdManagers to cause the secure set-cookie option to be used, which tells the browser not to send the cookie over http. This provides enhanced security for ssl-only applications. - Only set the client-id cookie if it isn't already set and try to prevent the header from being cached. This is to minimize risk from broken caches handing the same client id out to multiple users. 3.5.2 (2008-06-12) ------------------ - Remove ConflictErrors caused on SessionData caused by setting ``lastAccessTime``. 3.5.1 (2008-04-30) ------------------ - Split up the ZCML to make it possible to re-use more reasonably. 3.5.0 (2008-03-11) ------------------ - Change the default session "resolution" to a sane value and document/test it. 3.4.1 (2007-09-25) ------------------ - Fixed some meta data and switch to tgz release. 3.4.0 (2007-09-25) ------------------ - Initial release - Moved parts from ``zope.app.session`` to this packages Keywords: zope3 session Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3 zope.session-3.9.5/CHANGES.txt0000644000175000017500000001104611621032632017142 0ustar tseavertseaver00000000000000CHANGES ======= 3.9.5 (2011-08-11) ------------------ - LP #824355: enable support for HttpOnly cookies. - Fix a bug in zope.session.session.Session that would trigger an infinite loop if either iteration or a containment test were attempted on an instance. 3.9.4 (2011-03-07) ------------------ - Added an explicit `provides` to the IClientId adapter declaration in adapter.zcml. - Added option to disable implicit sweeps in PersistentSessionDataContainer. 3.9.3 (2010-09-25) ------------------ - Added test extra to declare test dependency on ``zope.testing``. - Using Python's ``doctest`` module instead of depreacted ``zope.testing.doctest``. 3.9.2 (2009-11-23) ------------------ - Fix Python 2.4 hmac compatibility issue by only using hashlib in Python versions 2.5 and above. - Use the CookieClientIdManager's secret as the hmac key instead of the message when constructing and verifying client ids. - Make it possible to construct CookieClientIdManager passing cookie namespace and/or secret as constructor's arguments. - Use zope.schema.fieldproperty.FieldProperty for "namespace" attribute of CookieClientIdManager, just like for other attributes in its interface. Also, make ICookieClientIdManager's "namespace" field an ASCIILine, so it accepts only non-unicode strings for cookie names. 3.9.1 (2009-04-20) ------------------ - Restore compatibility with Python 2.4. 3.9.0 (2009-03-19) ------------------ - Don't raise deprecation warnings on Python 2.6. - Drop dependency on ``zope.annotation``. Instead, we make classes implement `IAttributeAnnotatable` in ZCML configuration, only if ``zope.annotation`` is available. If your code relies on annotatable `CookieClientIdManager` and `PersistentSessionDataContainer` and you don't include the zcml classes configuration of this package, you'll need to use `classImplements` function from ``zope.interface`` to make those classes implement `IAttributeAnnotatable` again. - Drop dependency on zope.app.http, use standard date formatting function from the ``email.utils`` module. - Zope 3 application bootstrapping code for session utilities was moved into zope.app.appsetup package, thus drop dependency on zope.app.appsetup in this package. - Drop testing dependencies, as we don't need anything behind zope.testing and previous dependencies was simply migrated from zope.app.session before. - Remove zpkg files and zcml slugs. - Update package's description a bit. 3.8.1 (2009-02-23) ------------------ - Add an ability to set cookie effective domain for CookieClientIdManager. This is useful for simple cases when you have your application set up on one domain and you want your identification cookie be active for subdomains. - Python 2.6 compatibility change. Encode strings before calling hmac.new() as the function no longer accepts the unicode() type. 3.8.0 (2008-12-31) ------------------ - Add missing test dependency on ``zope.site`` and ``zope.app.publication``. 3.7.1 (2008-12-30) ------------------ - Specify i18n_domain for titles in apidoc.zcml - ZODB 3.9 no longer contains ZODB.utils.ConflictResolvingMappingStorage, fixed tests, so they work both with ZODB 3.8 and 3.9. 3.7.0 (2008-10-03) ------------------ New features: - Added a 'postOnly' option on CookieClientIdManagers to only allow setting the client id cookie on POST requests. This is to further reduce risk from broken caches handing the same client id out to multiple users. (Of course, it doesn't help if caches are broken enough to cache POSTs.) 3.6.0 (2008-08-12) ------------------ New features: - Added a 'secure' option on CookieClientIdManagers to cause the secure set-cookie option to be used, which tells the browser not to send the cookie over http. This provides enhanced security for ssl-only applications. - Only set the client-id cookie if it isn't already set and try to prevent the header from being cached. This is to minimize risk from broken caches handing the same client id out to multiple users. 3.5.2 (2008-06-12) ------------------ - Remove ConflictErrors caused on SessionData caused by setting ``lastAccessTime``. 3.5.1 (2008-04-30) ------------------ - Split up the ZCML to make it possible to re-use more reasonably. 3.5.0 (2008-03-11) ------------------ - Change the default session "resolution" to a sane value and document/test it. 3.4.1 (2007-09-25) ------------------ - Fixed some meta data and switch to tgz release. 3.4.0 (2007-09-25) ------------------ - Initial release - Moved parts from ``zope.app.session`` to this packages zope.session-3.9.5/setup.cfg0000644000175000017500000000007311621032676017160 0ustar tseavertseaver00000000000000[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 zope.session-3.9.5/PKG-INFO0000644000175000017500000004453311621032676016445 0ustar tseavertseaver00000000000000Metadata-Version: 1.0 Name: zope.session Version: 3.9.5 Summary: Client identification and sessions for Zope Home-page: http://pypi.python.org/pypi/zope.session Author: Zope Foundation and Contributors Author-email: zope-dev@zope.org License: ZPL 2.1 Description: This package provides interfaces for client identification and session support and their implementations for zope.publisher's request objects. .. contents:: Sessions ======== Sessions provide a way to temporarily associate information with a client without requiring the authentication of a principal. We associate an identifier with a particular client. Whenever we get a request from that client, we compute the identifier and use the identifier to look up associated information, which is stored on the server. A major disadvantage of sessions is that they require management of information on the server. This can have major implications for scalability. It is possible for a framework to make use of session data very easy for the developer. This is great if scalability is not an issue, otherwise, it is a booby trap. Design Issues ------------- Sessions introduce a number of issues to be considered: - Clients have to be identified. A number of approaches are possible, including: o Using HTTP cookies. The application assigns a client identifier, which is stored in a cookie. This technique is the most straightforward, but can be defeated if the client does not support HTTP cookies (usually because the feature has been disabled). o Using URLs. The application assigns a client identifier, which is stored in the URL. This makes URLs a bit uglier and requires some care. If people copy URLs and send them to others, then you could end up with multiple clients with the same session identifier. There are a number of ways to reduce the risk of accidental reuse of session identifiers: - Embed the client IP address in the identifier - Expire the identifier o Use hidden form variables. This complicates applications. It requires all requests to be POST requests and requires the maintenance of the hidden variables. o Use the client IP address This doesn't work very well, because an IP address may be shared by many clients. - Data storage Data can be simply stored in the object database. This provides lots of flexibility. You can store pretty much anything you want as long as it is persistent. You get the full benefit of the object database, such as transactions, transparency, clustering, and so on. Using the object database is especially useful when: - Writes are infrequent - Data are complex If writes are frequent, then the object database introduces scalability problems. Really, any transactional database is likely to introduce problems with frequent writes. If you are tempted to update session data on every request, think very hard about it. You are creating a scalability problem. If you know that scalability is not (and never will be) an issue, you can just use the object database. If you have client data that needs to be updated often (as in every request), consider storing the data on the client. (Like all data received from a client, it may be tainted and, in most instances, should not be trusted. Sensitive information that the user should not see should likewise not be stored on the client, unless encrypted with a key the client has no access to.) If you can't store it on the client, then consider some other storage mechanism, like a fast database, possibly without transaction support. You may be tempted to store session data in memory for speed. This doesn't turn out to work very well. If you need scalability, then you need to be able to use an application-server cluster and storage of session data in memory defeats that. You can use "server-affinity" to assure that requests from a client always go back to the same server, but not all load balancers support server affinity, and, for those that do, enabling server affinity tends to defeat load balancing. - Session expiration You may wish to ensure that sessions terminate after some period of time. This may be for security reasons, or to avoid accidental sharing of a session among multiple clients. The policy might be expressed in terms of total session time, or maximum inactive time, or some combination. There are a number of ways to approach this. You can expire client ids. You can expire session data. - Data expiration Because HTTP is a stateless protocol, you can't tell whether a user is thinking about a task or has simply stopped working on it. Some means is needed to free server session storage that is no-longer needed. The simplest strategy is to never remove data. This strategy has some obvious disadvantages. Other strategies can be viewed as optimizations of the basic strategy. It is important to realize that a data expiration strategy can be informed by, but need not be constrained by a session-expiration strategy. Zope3 Session Implementation ============================ Overview -------- .. CAUTION:: Session data is maintained on the server. This gives a security advantage in that we can assume that a client has not tampered with the data. However, this can have major implications for scalability as modifying session data too frequently can put a significant load on servers and in extreme situations render your site unusable. Developers should keep this in mind when writing code or risk problems when their application is run in a production environment. Applications requiring write-intensive session implementations (such as page counters) should consider using cookies or specialized session implementations. Sessions allow us to fake state over a stateless protocol - HTTP. We do this by having a unique identifier stored across multiple HTTP requests, be it a cookie or some id mangled into the URL. The `IClientIdManager` Utility provides this unique id. It is responsible for propagating this id so that future requests from the client get the same id (eg. by setting an HTTP cookie). This utility is used when we adapt the request to the unique client id: >>> client_id = IClientId(request) The `ISession` adapter gives us a mapping that can be used to store and retrieve session data. A unique key (the package id) is used to avoid namespace clashes: >>> pkg_id = 'products.foo' >>> session = ISession(request)[pkg_id] >>> session['color'] = 'red' >>> session2 = ISession(request)['products.bar'] >>> session2['color'] = 'blue' >>> session['color'] 'red' >>> session2['color'] 'blue' Data Storage ------------ The actual data is stored in an `ISessionDataContainer` utility. `ISession` chooses which `ISessionDataContainer` should be used by looking up as a named utility using the package id. This allows the site administrator to configure where the session data is actually stored by adding a registration for desired `ISessionDataContainer` with the correct name. >>> import zope.component >>> sdc = zope.component.getUtility(ISessionDataContainer, pkg_id) >>> sdc[client_id][pkg_id] is session True >>> sdc[client_id][pkg_id]['color'] 'red' If no `ISessionDataContainer` utility can be located by name using the package id, then the unnamed `ISessionDataContainer` utility is used as a fallback. An unnamed `ISessionDataContainer` is automatically created for you, which may replaced with a different implementation if desired. >>> ISession(request)['unknown'] \ ... is zope.component.getUtility(ISessionDataContainer)[client_id]\ ... ['unknown'] True The `ISessionDataContainer` contains `ISessionData` objects, and `ISessionData` objects in turn contain `ISessionPkgData` objects. You should never need to know this unless you are writing administrative views for the session machinery. >>> ISessionData.providedBy(sdc[client_id]) True >>> ISessionPkgData.providedBy(sdc[client_id][pkg_id]) True The `ISessionDataContainer` is responsible for expiring session data. The expiry time can be configured by settings its `timeout` attribute. >>> sdc.timeout = 1200 # 1200 seconds or 20 minutes Restrictions ------------ Data stored in the session must be persistent or picklable. >>> session['oops'] = open(__file__) >>> import transaction >>> transaction.commit() Traceback (most recent call last): [...] TypeError: can't pickle file objects Clean up: >>> transaction.abort() Page Templates -------------- Session data may be accessed in page template documents using TALES:: green or::
Session Timeout --------------- Sessions have a timeout (defaulting to an hour, in seconds). >>> import zope.session.session >>> data_container = zope.session.session.PersistentSessionDataContainer() >>> data_container.timeout 3600 We need to keep up with when the session was last used (to know when it needs to be expired), but it would be too resource-intensive to write the last access time every, single time the session data is touched. The session machinery compromises by only recording the last access time periodically. That period is called the "resolution". That also means that if the last-access-time + the-resolution < now, then the session is considered to have timed out. The default resolution is 10 minutes (600 seconds), meaning that a users session will actually time out sometime between 50 and 60 minutes. >>> data_container.resolution 600 CHANGES ======= 3.9.5 (2011-08-11) ------------------ - LP #824355: enable support for HttpOnly cookies. - Fix a bug in zope.session.session.Session that would trigger an infinite loop if either iteration or a containment test were attempted on an instance. 3.9.4 (2011-03-07) ------------------ - Added an explicit `provides` to the IClientId adapter declaration in adapter.zcml. - Added option to disable implicit sweeps in PersistentSessionDataContainer. 3.9.3 (2010-09-25) ------------------ - Added test extra to declare test dependency on ``zope.testing``. - Using Python's ``doctest`` module instead of depreacted ``zope.testing.doctest``. 3.9.2 (2009-11-23) ------------------ - Fix Python 2.4 hmac compatibility issue by only using hashlib in Python versions 2.5 and above. - Use the CookieClientIdManager's secret as the hmac key instead of the message when constructing and verifying client ids. - Make it possible to construct CookieClientIdManager passing cookie namespace and/or secret as constructor's arguments. - Use zope.schema.fieldproperty.FieldProperty for "namespace" attribute of CookieClientIdManager, just like for other attributes in its interface. Also, make ICookieClientIdManager's "namespace" field an ASCIILine, so it accepts only non-unicode strings for cookie names. 3.9.1 (2009-04-20) ------------------ - Restore compatibility with Python 2.4. 3.9.0 (2009-03-19) ------------------ - Don't raise deprecation warnings on Python 2.6. - Drop dependency on ``zope.annotation``. Instead, we make classes implement `IAttributeAnnotatable` in ZCML configuration, only if ``zope.annotation`` is available. If your code relies on annotatable `CookieClientIdManager` and `PersistentSessionDataContainer` and you don't include the zcml classes configuration of this package, you'll need to use `classImplements` function from ``zope.interface`` to make those classes implement `IAttributeAnnotatable` again. - Drop dependency on zope.app.http, use standard date formatting function from the ``email.utils`` module. - Zope 3 application bootstrapping code for session utilities was moved into zope.app.appsetup package, thus drop dependency on zope.app.appsetup in this package. - Drop testing dependencies, as we don't need anything behind zope.testing and previous dependencies was simply migrated from zope.app.session before. - Remove zpkg files and zcml slugs. - Update package's description a bit. 3.8.1 (2009-02-23) ------------------ - Add an ability to set cookie effective domain for CookieClientIdManager. This is useful for simple cases when you have your application set up on one domain and you want your identification cookie be active for subdomains. - Python 2.6 compatibility change. Encode strings before calling hmac.new() as the function no longer accepts the unicode() type. 3.8.0 (2008-12-31) ------------------ - Add missing test dependency on ``zope.site`` and ``zope.app.publication``. 3.7.1 (2008-12-30) ------------------ - Specify i18n_domain for titles in apidoc.zcml - ZODB 3.9 no longer contains ZODB.utils.ConflictResolvingMappingStorage, fixed tests, so they work both with ZODB 3.8 and 3.9. 3.7.0 (2008-10-03) ------------------ New features: - Added a 'postOnly' option on CookieClientIdManagers to only allow setting the client id cookie on POST requests. This is to further reduce risk from broken caches handing the same client id out to multiple users. (Of course, it doesn't help if caches are broken enough to cache POSTs.) 3.6.0 (2008-08-12) ------------------ New features: - Added a 'secure' option on CookieClientIdManagers to cause the secure set-cookie option to be used, which tells the browser not to send the cookie over http. This provides enhanced security for ssl-only applications. - Only set the client-id cookie if it isn't already set and try to prevent the header from being cached. This is to minimize risk from broken caches handing the same client id out to multiple users. 3.5.2 (2008-06-12) ------------------ - Remove ConflictErrors caused on SessionData caused by setting ``lastAccessTime``. 3.5.1 (2008-04-30) ------------------ - Split up the ZCML to make it possible to re-use more reasonably. 3.5.0 (2008-03-11) ------------------ - Change the default session "resolution" to a sane value and document/test it. 3.4.1 (2007-09-25) ------------------ - Fixed some meta data and switch to tgz release. 3.4.0 (2007-09-25) ------------------ - Initial release - Moved parts from ``zope.app.session`` to this packages Keywords: zope3 session Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Zope Public License Classifier: Programming Language :: Python Classifier: Natural Language :: English Classifier: Operating System :: OS Independent Classifier: Topic :: Internet :: WWW/HTTP Classifier: Framework :: Zope3