zope.session-3.9.5/ 0000755 0001750 0001750 00000000000 11621032676 015337 5 ustar tseaver tseaver 0000000 0000000 zope.session-3.9.5/buildout.cfg 0000644 0001750 0001750 00000000252 11621032632 017636 0 ustar tseaver tseaver 0000000 0000000 [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.txt 0000644 0001750 0001750 00000000040 11621032632 017432 0 ustar tseaver tseaver 0000000 0000000 Zope Foundation and Contributors zope.session-3.9.5/README.txt 0000644 0001750 0001750 00000000217 11621032632 017025 0 ustar tseaver tseaver 0000000 0000000 This package provides interfaces for client identification and session
support and their implementations for zope.publisher's request objects.
zope.session-3.9.5/LICENSE.txt 0000644 0001750 0001750 00000004026 11621032632 017154 0 ustar tseaver tseaver 0000000 0000000 Zope 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.py 0000644 0001750 0001750 00000003302 11621032632 017714 0 ustar tseaver tseaver 0000000 0000000 ##############################################################################
#
# 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.py 0000644 0001750 0001750 00000005065 11621032632 017047 0 ustar tseaver tseaver 0000000 0000000 ##############################################################################
#
# 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/ 0000755 0001750 0001750 00000000000 11621032676 016126 5 ustar tseaver tseaver 0000000 0000000 zope.session-3.9.5/src/zope/ 0000755 0001750 0001750 00000000000 11621032676 017103 5 ustar tseaver tseaver 0000000 0000000 zope.session-3.9.5/src/zope/session/ 0000755 0001750 0001750 00000000000 11621032676 020566 5 ustar tseaver tseaver 0000000 0000000 zope.session-3.9.5/src/zope/session/classes.zcml 0000644 0001750 0001750 00000004366 11621032632 023113 0 ustar tseaver tseaver 0000000 0000000
zope.session-3.9.5/src/zope/session/session.py 0000644 0001750 0001750 00000044461 11621032632 022624 0 ustar tseaver tseaver 0000000 0000000 ##############################################################################
#
# 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.py 0000644 0001750 0001750 00000052742 11621032632 022121 0 ustar tseaver tseaver 0000000 0000000 ##############################################################################
#
# 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.py 0000644 0001750 0001750 00000012462 11621032632 023260 0 ustar tseaver tseaver 0000000 0000000 ##############################################################################
#
# 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.txt 0000644 0001750 0001750 00000011607 11621032632 022075 0 ustar tseaver tseaver 0000000 0000000 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
zope.session-3.9.5/src/zope/session/configure.zcml 0000644 0001750 0001750 00000000317 11621032632 023427 0 ustar tseaver tseaver 0000000 0000000
zope.session-3.9.5/src/zope/session/design.txt 0000644 0001750 0001750 00000011132 11621032632 022566 0 ustar tseaver tseaver 0000000 0000000 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.
zope.session-3.9.5/src/zope/session/tests.py 0000644 0001750 0001750 00000013315 11621032632 022275 0 ustar tseaver tseaver 0000000 0000000 ##############################################################################
#
# 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.zcml 0000644 0001750 0001750 00000000700 11621032632 022701 0 ustar tseaver tseaver 0000000 0000000
zope.session-3.9.5/src/zope/session/__init__.py 0000644 0001750 0001750 00000001256 11621032632 022673 0 ustar tseaver tseaver 0000000 0000000 ##############################################################################
#
# 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.zcml 0000644 0001750 0001750 00000000676 11621032632 023261 0 ustar tseaver tseaver 0000000 0000000
zope.session-3.9.5/src/zope/session/subscribers.zcml 0000644 0001750 0001750 00000000321 11621032632 023767 0 ustar tseaver tseaver 0000000 0000000
zope.session-3.9.5/src/zope/__init__.py 0000644 0001750 0001750 00000000070 11621032632 021201 0 ustar tseaver tseaver 0000000 0000000 __import__('pkg_resources').declare_namespace(__name__)
zope.session-3.9.5/src/zope.session.egg-info/ 0000755 0001750 0001750 00000000000 11621032676 022257 5 ustar tseaver tseaver 0000000 0000000 zope.session-3.9.5/src/zope.session.egg-info/not-zip-safe 0000644 0001750 0001750 00000000001 11621032646 024502 0 ustar tseaver tseaver 0000000 0000000
zope.session-3.9.5/src/zope.session.egg-info/namespace_packages.txt 0000644 0001750 0001750 00000000005 11621032676 026605 0 ustar tseaver tseaver 0000000 0000000 zope
zope.session-3.9.5/src/zope.session.egg-info/requires.txt 0000644 0001750 0001750 00000000177 11621032676 024664 0 ustar tseaver tseaver 0000000 0000000 setuptools
ZODB3
zope.component
zope.i18nmessageid
zope.interface
zope.location
zope.publisher
zope.minmax
[test]
zope.testing zope.session-3.9.5/src/zope.session.egg-info/SOURCES.txt 0000644 0001750 0001750 00000001343 11621032676 024144 0 ustar tseaver tseaver 0000000 0000000 CHANGES.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.py zope.session-3.9.5/src/zope.session.egg-info/top_level.txt 0000644 0001750 0001750 00000000005 11621032676 025004 0 ustar tseaver tseaver 0000000 0000000 zope
zope.session-3.9.5/src/zope.session.egg-info/dependency_links.txt 0000644 0001750 0001750 00000000001 11621032676 026325 0 ustar tseaver tseaver 0000000 0000000
zope.session-3.9.5/src/zope.session.egg-info/PKG-INFO 0000644 0001750 0001750 00000044533 11621032676 023365 0 ustar tseaver tseaver 0000000 0000000 Metadata-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.txt 0000644 0001750 0001750 00000011046 11621032632 017142 0 ustar tseaver tseaver 0000000 0000000 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
zope.session-3.9.5/setup.cfg 0000644 0001750 0001750 00000000073 11621032676 017160 0 ustar tseaver tseaver 0000000 0000000 [egg_info]
tag_build =
tag_date = 0
tag_svn_revision = 0
zope.session-3.9.5/PKG-INFO 0000644 0001750 0001750 00000044533 11621032676 016445 0 ustar tseaver tseaver 0000000 0000000 Metadata-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