[Zope3-checkins] SVN: Zope3/trunk/ added third party ClientId
management
Jodok Batlogg
jodok.batlogg at lovelysystems.com
Wed May 2 10:13:19 EDT 2007
Log message for revision 75004:
added third party ClientId management
Changed:
U Zope3/trunk/doc/CHANGES.txt
U Zope3/trunk/src/zope/app/session/http.py
U Zope3/trunk/src/zope/app/session/tests.py
-=-
Modified: Zope3/trunk/doc/CHANGES.txt
===================================================================
--- Zope3/trunk/doc/CHANGES.txt 2007-05-02 14:05:04 UTC (rev 75003)
+++ Zope3/trunk/doc/CHANGES.txt 2007-05-02 14:13:19 UTC (rev 75004)
@@ -10,6 +10,10 @@
New features
+ - Added support for third party ClientId Cookie Managers. Reverse
+ Proxies like Apache with mod_id or Nginx with ngx_http_userid_module
+ are able to handle client identification. zope.app.session
+
- Added salt to MD5/SHA1 password managers. (Thanks Mark Giovannetti for
the patch)
Modified: Zope3/trunk/src/zope/app/session/http.py
===================================================================
--- Zope3/trunk/src/zope/app/session/http.py 2007-05-02 14:05:04 UTC (rev 75003)
+++ Zope3/trunk/src/zope/app/session/http.py 2007-05-02 14:13:19 UTC (rev 75004)
@@ -16,6 +16,7 @@
$Id$
"""
import hmac
+import logging
import random
import re
import sha
@@ -33,16 +34,22 @@
from zope.app.session.i18n import ZopeMessageFactory as _
from zope.app.session.interfaces import IClientIdManager
+from zope.schema.fieldproperty import FieldProperty
from zope.app.http.httpdate import build_http_date
__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"""
@@ -72,15 +79,31 @@
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,
+ )
+
+
class CookieClientIdManager(zope.location.Location, Persistent):
"""Session utility implemented using cookies."""
implements(IClientIdManager, ICookieClientIdManager, IAttributeAnnotatable)
+ thirdparty = FieldProperty(ICookieClientIdManager['thirdparty'])
+ cookieLifetime = FieldProperty(ICookieClientIdManager['cookieLifetime'])
+
def __init__(self):
self.namespace = "zope3_cs_%x" % (int(time.time()) - 1000000000)
self.secret = "%.20f" % random.random()
- self.cookieLifetime = None
def getClientId(self, request):
"""Get the client id
@@ -113,21 +136,42 @@
>>> type(id) == type('')
True
+ 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
+ >>> request3 = HTTPRequest(StringIO(''), {}, None)
+ >>> bim.getClientId(request3)
+ Traceback (most recent call last):
+ ...
+ MissingClientIdException
+ >>> cookie = request3.response.getCookie(bim.namespace)
+ >>> cookie is None
+ True
+
"""
sid = self.getRequestId(request)
if sid is None:
- sid = self.generateUniqueId()
- self.setRequestId(request, sid)
+ if self.thirdparty:
+ raise MissingClientIdException
+ else:
+ sid = self.generateUniqueId()
+
+ if not self.thirdparty:
+ 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
+ >>> bim = CookieClientIdManager()
+ >>> id1 = bim.generateUniqueId()
+ >>> id2 = bim.generateUniqueId()
+ >>> id1 != id2
+ True
"""
data = "%.20f%.20f%.20f" % (random.random(), time.time(), time.clock())
@@ -145,57 +189,70 @@
For example:
- >>> from zope.publisher.http import HTTPRequest
- >>> request = HTTPRequest(StringIO(''), {}, None)
- >>> bim = CookieClientIdManager()
+ >>> 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
+ >>> bim.getRequestId(request) is None
+ True
We can set an id:
- >>> id1 = bim.generateUniqueId()
- >>> bim.setRequestId(request, id1)
+ >>> id1 = bim.generateUniqueId()
+ >>> bim.setRequestId(request, id1)
And get it back:
- >>> bim.getRequestId(request) == id1
- True
+ >>> 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()
- ... ])
+ >>> 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
+ >>> bim.getRequestId(request) == bim.getRequestId(request2)
+ 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=='
+
"""
- # 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:
request = IHTTPApplicationRequest(request)
sid = request.getCookies().get(self.namespace, None)
- 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
+ if self.thirdparty:
+ return sid
else:
- return sid
+ # 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:]
+ if (digestEncode(hmac.new(s, self.secret, digestmod=sha).digest())
+ != mac):
+ return None
+ else:
+ return sid
def setRequestId(self, request, id):
"""Set cookie with id on request.
@@ -247,7 +304,15 @@
>>> 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
"""
# TODO: Currently, the path is the ApplicationURL. This is reasonable,
# and will be adequate for most purposes.
@@ -258,20 +323,24 @@
# Seeing as this utility instance has a unique namespace for its
# cookie, using ApplicationURL shouldn't be a problem.
- if self.cookieLifetime is not None:
- if self.cookieLifetime:
- expires = build_http_date(time.time() + self.cookieLifetime)
+ if self.thirdparty:
+ logger.warning('ClientIdManager is using thirdparty cookies, '
+ 'ignoring setIdRequest call')
+ else:
+ if self.cookieLifetime is not None:
+ if self.cookieLifetime:
+ expires = build_http_date(time.time() + self.cookieLifetime)
+ else:
+ expires = 'Tue, 19 Jan 2038 00:00:00 GMT'
+ request.response.setCookie(
+ self.namespace, id, expires=expires,
+ path=request.getApplicationURL(path_only=True)
+ )
else:
- expires = 'Tue, 19 Jan 2038 00:00:00 GMT'
- request.response.setCookie(
- self.namespace, id, expires=expires,
- path=request.getApplicationURL(path_only=True)
- )
- else:
- request.response.setCookie(
- self.namespace, id,
- path=request.getApplicationURL(path_only=True)
- )
+ request.response.setCookie(
+ self.namespace, id,
+ path=request.getApplicationURL(path_only=True)
+ )
def notifyVirtualHostChanged(event):
"""Adjust cookie paths when IVirtualHostRequest information changes.
@@ -283,6 +352,7 @@
>>> class DummyManager(object):
... implements(ICookieClientIdManager)
... namespace = 'foo'
+ ... thirdparty = False
... request_id = None
... def setRequestId(self, request, id):
... self.request_id = id
@@ -307,7 +377,23 @@
>>> 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
@@ -318,8 +404,12 @@
# 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 request and ICookieClientIdManager.providedBy(manager):
- cookie = request.response.getCookie(manager.namespace)
- if cookie:
- manager.setRequestId(request, cookie['value'])
+ 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'])
\ No newline at end of file
Modified: Zope3/trunk/src/zope/app/session/tests.py
===================================================================
--- Zope3/trunk/src/zope/app/session/tests.py 2007-05-02 14:05:04 UTC (rev 75003)
+++ Zope3/trunk/src/zope/app/session/tests.py 2007-05-02 14:13:19 UTC (rev 75004)
@@ -194,8 +194,10 @@
suite.addTest(unittest.makeSuite(TestBootstrap))
suite.addTest(doctest.DocTestSuite())
suite.addTest(doctest.DocTestSuite('zope.app.session.session',
- tearDown=tearDownTransaction))
- suite.addTest(doctest.DocTestSuite('zope.app.session.http'))
+ tearDown=tearDownTransaction))
+ suite.addTest(doctest.DocTestSuite('zope.app.session.http',
+ optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,)
+ )
suite.addTest(unittest.makeSuite(ZPTSessionTest))
suite.addTest(unittest.makeSuite(VirtualHostSessionTest))
return suite
More information about the Zope3-Checkins
mailing list