[Zope3-checkins] CVS: Zope3/src/zope/app/utilities - session.py:1.2 session.stx:1.2 configure.zcml:1.5

Stuart Bishop zen at shangri-la.dropbear.id.au
Mon Feb 9 00:17:08 EST 2004


Update of /cvs-repository/Zope3/src/zope/app/utilities
In directory cvs.zope.org:/tmp/cvs-serv20093/src/zope/app/utilities

Modified Files:
	configure.zcml 
Added Files:
	session.py session.stx 
Log Message:
Session work to HEAD


=== Zope3/src/zope/app/utilities/session.py 1.1 => 1.2 ===
--- /dev/null	Mon Feb  9 00:17:06 2004
+++ Zope3/src/zope/app/utilities/session.py	Mon Feb  9 00:16:32 2004
@@ -0,0 +1,188 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (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.
+#
+##############################################################################
+"""Simplistic session service implemented using cookies.
+
+This is more of a demonstration than a full implementation, but it should
+work.
+"""
+
+# System imports
+import sha, time, string, random, hmac, logging
+from UserDict import IterableUserDict
+
+# Zope3 imports
+from persistence import Persistent
+from persistence.dict import PersistentDict
+from zope.server.http.http_date import build_http_date
+from zope.component import getService
+from zope.interface import implements
+from zope.app import zapi
+from zodb.btrees.OOBTree import OOBTree
+
+# Sibling imports
+from zope.app.interfaces.utilities.session import \
+        IBrowserIdManager, IBrowserId, ICookieBrowserIdManager, \
+        ISessionDataContainer, ISession, IFullMapping
+from zope.app.interfaces.container import IContained
+
+cookieSafeTrans = string.maketrans("+/", "-.")
+
+def digestEncode(s):
+    """Encode SHA digest for cookie."""
+    return s.encode("base64")[:-2].translate(cookieSafeTrans)
+
+
+class BrowserId(str):
+    """A browser id"""
+    implements(IBrowserId)
+
+
+class CookieBrowserIdManager(Persistent):
+    """Session service implemented using cookies."""
+
+    implements(IBrowserIdManager, ICookieBrowserIdManager, IContained)
+
+    __parent__ = __name__ = None
+
+    def __init__(self):
+        self.namespace = "zope3_cs_%x" % (int(time.time()) - 1000000000)
+        self.secret = "%.20f" % random.random()
+        self.cookieLifeSeconds = 3600
+
+    def generateUniqueId(self):
+        """Generate a new, random, unique id."""
+        data = "%.20f%.20f%.20f" % (random.random(), time.time(), time.clock())
+        digest = sha.sha(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(s, self.secret, digestmod=sha).digest()
+        return BrowserId(s + digestEncode(mac))
+
+    def getRequestId(self, request):
+        """Return the IBrowserId encoded in request or None if it's
+        non-existent."""
+        # 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.
+        response_cookie = request.response.getCookie(self.namespace)
+        if response_cookie:
+            sid = response_cookie['value']
+        else:
+            sid = request.cookies.get(self.namespace)
+        if sid is None or len(sid) != 54:
+            return None
+        s, mac = sid[:27], sid[27:]
+        if (digestEncode(hmac.new(s, self.secret, digestmod=sha).digest())
+            != mac):
+            return None
+        else:
+            return BrowserId(sid)
+
+    def setRequestId(self, request, id):
+        """Set cookie with id on request."""
+        # XXX 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 service-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 service instance has a unique namespace for its
+        #     cookie, using ApplicationURL shouldn't be a problem.
+
+        # XXX: Fix this as per documentation in ICookieBrowserIdManager
+        if self.cookieLifeSeconds == 0 or self.cookieLifeSeconds is None:
+            raise NotImplementedError, \
+                    'Need to implement advanced cookie lifetime'
+
+        if self.cookieLifeSeconds:
+            expires = build_http_date(time.time() + self.cookieLifeSeconds)
+        else:
+            expires = None
+        request.response.setCookie(
+                self.namespace,
+                id,
+                expires=expires,
+                path=request.getApplicationURL(path_only=True)
+                )
+
+
+    #######################################
+    # Implementation of IBrowserIdManager #
+
+    def getBrowserId(self, request):
+        sid = self.getRequestId(request)
+        if sid is None:
+            sid = self.generateUniqueId()
+        self.setRequestId(request, sid)
+        return sid
+
+
+class PersistentSessionDataContainer(Persistent, IterableUserDict):
+    ''' A SessionDataContainer that stores data in the ZODB '''
+    __parent__ = __name__ = None
+    implements(ISessionDataContainer, IContained)
+
+    def __init__(self):
+        self.data = OOBTree()
+
+class SessionData(Persistent, IterableUserDict):
+    ''' Mapping nodes in the ISessionDataContainer tree '''
+    implements(IFullMapping)
+    def __init__(self):
+        self.data = OOBTree()
+    def __setitem__(self, key, value):
+        self.data[key] = value
+        self.data._p_changed = 1
+    def __delitem__(self, key):
+        del self.data[key]
+        self.data._p_changed = 1
+
+class Session(IterableUserDict):
+    implements(ISession)
+    def __init__(self, data_manager, browser_id, product_id):
+        browser_id = str(browser_id)
+        product_id = str(product_id)
+        try:
+            data = data_manager[browser_id]
+        except KeyError:
+            data_manager[browser_id] = SessionData()
+            data_manager[browser_id][product_id] = SessionData()
+            self.data = data_manager[browser_id][product_id]
+        else:
+            try:
+                self.data = data[product_id]
+            except KeyError:
+                data[product_id] = SessionData()
+                self.data = data[product_id]
+
+
+def getSession(context, request, product_id, session_data_container=None):
+    ''' Retrieve an ISession. session_data_container defaults to 
+        an ISessionDataContainer utility registered with the name product_id
+    '''
+    if session_data_container is None:
+        dc = zapi.getUtility(context, ISessionDataContainer, product_id)
+    elif ISessionDataContainer.isImplementedBy(session_data_container):
+        dc = session_data_container
+    else:
+        dc = zapi.getUtility(
+                context, ISessionDataContainer, session_data_container
+                )
+
+    bim = zapi.getUtility(context, IBrowserIdManager)
+    browser_id = bim.getBrowserId(request)
+    return Session(dc, browser_id, product_id)
+
+


=== Zope3/src/zope/app/utilities/session.stx 1.1 => 1.2 ===
--- /dev/null	Mon Feb  9 00:17:06 2004
+++ Zope3/src/zope/app/utilities/session.stx	Mon Feb  9 00:16:32 2004
@@ -0,0 +1,47 @@
+Session Support
+---------------
+
+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 IBrowserIdManager Utility provides this unique id. It is responsible
+for propagating this id so that future requests from the browser get
+the same id (eg. by setting an HTTP cookie)
+
+ISessionDataContainer Utilities provide a mapping interface to store
+session data. The ISessionDataContainer is responsible for expiring
+data.
+
+
+Python example::
+
+    >>> browser_id = getAdapter(request, IBrowserId))
+
+    >>> explicit_dm = getUtility(None, ISessionDataContainer, 
+    ...     'zopeproducts.fooprod')
+    >>> session = Session(explicit_dm, browser_id, 'zopeproducts.foorprod')
+    >>> session['color'] = 'red'
+
+    or....
+
+    >>> session = zapi.getSession(request, 'zopeproducts.fooprod')
+    >>> session['color'] = 'red'
+
+Page Template example::
+
+    <tal:x condition="exists:session/zopeproducts.fooprod/count">
+       <tal:x condition="python:
+        session['zopeproducts.fooprod']['count'] += 1" />
+    </tal:x>
+    <tal:x condition="not:exists:session/zopeprodicts.fooprod/count">
+        <tal:x condition="python:
+            session['zopeproducts.fooprod']['count'] = 1 />
+    </tal:x>
+    <span content="session/zopeproducts.fooprod/count">6</span>
+
+TODO
+----
+Do we want to provide one or more 'default' ISessionDataContainer's out of the
+box (eg. 'persistant' and 'transient')?
+


=== Zope3/src/zope/app/utilities/configure.zcml 1.4 => 1.5 ===
--- Zope3/src/zope/app/utilities/configure.zcml:1.4	Sun Sep 21 13:33:48 2003
+++ Zope3/src/zope/app/utilities/configure.zcml	Mon Feb  9 00:16:32 2004
@@ -1,4 +1,40 @@
-<configure xmlns="http://namespaces.zope.org/zope">
+<configure 
+  xmlns="http://namespaces.zope.org/zope"
+  xmlns:browser="http://namespaces.zope.org/browser">
+
+  <!-- Session machinery -->
+
+  <content class=".session.CookieBrowserIdManager">
+    <implements
+      interface="zope.app.interfaces.services.utility.ILocalUtility" />
+    <implements
+      interface="zope.app.interfaces.annotation.IAttributeAnnotatable" />
+    <require
+      interface="zope.app.interfaces.utilities.session.ICookieBrowserIdManager"
+      permission="zope.Public" />
+    <require
+      set_schema="zope.app.interfaces.utilities.session.ICookieBrowserIdManager"
+      permission="zope.ManageContent" />
+  </content>
+
+  <content class=".session.SessionData">
+    <allow interface="zope.app.interfaces.utilities.session.IFullMapping" />
+  </content>
+
+  <content class=".session.PersistentSessionDataContainer">
+    <implements
+      interface="zope.app.interfaces.utilities.session.ISessionDataContainer"/>
+    <implements
+      interface="zope.app.interfaces.services.utility.ILocalUtility" />
+    <implements
+      interface="zope.app.interfaces.annotation.IAttributeAnnotatable" />
+    <require
+      interface="zope.app.interfaces.utilities.session.ISessionDataContainer"
+      permission="zope.Public" />
+    <require
+      set_schema="zope.app.interfaces.utilities.session.ISessionDataContainer"
+      permission="zope.ManageContent" />
+  </content>
 
 <!-- Mutable Schema -->
 




More information about the Zope3-Checkins mailing list