Commit 463185cb authored by Chris McDonough's avatar Chris McDonough

Added the capability for browser ids to be encoded in URLs.

When a set of knobs is set on the browser_id_manager "settings" screen,
("look for browser id name in..." and 'automatically encode browser ids..."),
a traversal hook is installed in the browser id manager's container which
causes a) the request to be searched for a browser id name and a browser
id as the first two elements of the URL path and b) for Zope-generated URLs
to contain these path elements.

Various documentation and interface updates.  No interface methods
were harmed in the filming of this checkin, but a few were added or extended
with defaults.
parent 4e42440b
......@@ -25,11 +25,11 @@ import SessionInterfaces
from SessionPermissions import *
from types import StringType
from common import DEBUG
from BrowserIdManager import isAWellFormedBrowserId, getNewBrowserId,\
BROWSERID_MANAGER_NAME
from ZPublisher.BeforeTraverse import registerBeforeTraverse, \
unregisterBeforeTraverse
BID_MGR_NAME = 'browser_id_manager'
bad_path_chars_in=re.compile('[^a-zA-Z0-9-_~\,\. \/]').search
class SessionDataManagerErr(Exception): pass
......@@ -107,10 +107,11 @@ class SessionDataManager(Item, Implicit, Persistent, RoleManager, Owned, Tabs):
security.declareProtected(ACCESS_CONTENTS_PERM, 'getBrowserIdManager')
def getBrowserIdManager(self):
""" """
mgr = getattr(self, BID_MGR_NAME, None)
mgr = getattr(self, BROWSERID_MANAGER_NAME, None)
if mgr is None:
raise SessionDataManagerErr,(
'No browser id manager named %s could be found.' % BID_MGR_NAME
'No browser id manager named %s could be found.' %
BROWSERID_MANAGER_NAME
)
return mgr
......@@ -251,6 +252,12 @@ class SessionDataManagerTraverser(Persistent):
self._sessionDataManager = sessionDataManagerName
def __call__(self, container, request, StringType=StringType):
"""
This method places a session data object reference in
the request. It is called on each and every request to Zope in
Zopes after 2.5.0 when there is a session data manager installed
in the root.
"""
try:
sdmName = self._sessionDataManager
if not isinstance(sdmName, StringType):
......@@ -268,7 +275,10 @@ class SessionDataManagerTraverser(Persistent):
msg = 'Session automatic traversal failed to get session data'
LOG('Session Tracking', WARNING, msg, error=sys.exc_info())
return
# set the getSessionData method in the "lazy" namespace
if self._requestSessionName is not None:
request.set_lazy(self._requestSessionName, getSessionData)
Globals.InitializeClass(SessionDataManager)
......@@ -12,7 +12,7 @@
############################################################################
"""
Sessioning-related Object APIs
Session APIs
See Also
......@@ -32,12 +32,24 @@ class BrowserIdManagerInterface(
visitors, and for servicing requests from Session Data Managers
related to the browser id.
"""
def encodeUrl(url):
def encodeUrl(url, style='querystring'):
"""
Encodes a provided URL with the current request's browser id
and returns the result. For example, the call
encodeUrl('http://foo.com/amethod') might return
'http://foo.com/amethod?_ZopeId=as9dfu0adfu0ad'.
and returns the result. Two forms of URL-encoding are supported:
'querystring' and 'inline'. 'querystring' is the default.
If the 'querystring' form is used, the browser id name/value pair
are postfixed onto the URL as a query string. If the 'inline'
form is used, the browser id name/value pair are prefixed onto
the URL as the first two path segment elements.
For example:
The call encodeUrl('http://foo.com/amethod', style='querystring')
might return 'http://foo.com/amethod?_ZopeId=as9dfu0adfu0ad'.
The call encodeUrl('http://foo.com/amethod, style='inline')
might return 'http://foo.com/_ZopeId/as9dfu0adfu0ad/amethod'.
Permission required: Access contents information
......
......@@ -6,7 +6,6 @@
)">
<FORM ACTION="constructBrowserIdManager" METHOD="POST">
<input type=hidden name="id" value="browser_id_manager">
<TABLE CELLSPACING="2">
<tr>
<td>&nbsp;</td>
......@@ -60,28 +59,23 @@ by interacting with the Zope sessioning machinery.
</tr>
<tr>
<td>
<div align=left class="form-label">Look for Browser Id Name in</th>
<div align=left class="form-label">Look for Browser Id in</th>
</td>
<td>
<table border=0>
<tr>
<td align=left>
<input type="radio" name="location" value="cookiesonly"> Cookies only
<input type="checkbox" name="location:list" value="cookies" CHECKED> Cookies
</td>
</tr>
<tr>
<td align=left>
<input type="radio" name="location" value="cookiesthenform" CHECKED> Cookies then form
<input type="checkbox" name="location:list" value="form" CHECKED> Forms and Query Strings
</td>
</tr>
<tr>
<td align=left>
<input type="radio" name="location" value="formonly"> Form only
</td>
</tr>
<tr>
<td align=left>
<input type="radio" name="location" value="formthencookies"> Form then cookies
<input type="checkbox" name="location:list" value="url" CHECKED> URLs
</td>
</tr>
</table>
......@@ -93,6 +87,16 @@ by interacting with the Zope sessioning machinery.
<td>&nbsp;</td>
</tr>
<TR>
<TD ALIGN="LEFT" VALIGN="TOP">
<div class="form-label">
Automatically Encode Zope-Generated<br>URLs With A Browser Id
</div>
</TD>
<TD ALIGN="LEFT" VALIGN="TOP">
<INPUT TYPE="checkbox" NAME="auto_url_encoding" SIZE="20">
</TD>
</TR>
<TR>
<TD ALIGN="LEFT" VALIGN="TOP">
<div class="form-label">
......
......@@ -37,35 +37,29 @@
</TD>
</TR>
<dtml-let loc=getBrowserIdLocation>
<dtml-let namespaces=getBrowserIdNamespaces>
<tr valign="top">
<td>
<div align=left class="form-label">Look for Browser Id Name in</th>
<div align=left class="form-label">Look for Browser Id in</th>
</td>
<td>
<table border=0>
<tr>
<td align=left>
<input type="radio" name="location" value="cookiesonly"
<dtml-if "loc=='cookiesonly'">CHECKED</dtml-if>> Cookies only
<input type="checkbox" name="location:list" value="cookies"
<dtml-if "'cookies' in namespaces">CHECKED</dtml-if>> Cookies
</td>
</tr>
<tr>
<td align=left>
<input type="radio" name="location" value="cookiesthenform"
<dtml-if "loc=='cookiesthenform'">CHECKED</dtml-if>> Cookies then form
<input type="checkbox" name="location:list" value="form"
<dtml-if "'form' in namespaces">CHECKED</dtml-if>> Forms and Query Strings
</td>
</tr>
<tr>
<td align=left>
<input type="radio" name="location" value="formonly"
<dtml-if "loc=='formonly'">CHECKED</dtml-if>> Form only
</td>
</tr>
<tr>
<td align=left>
<input type="radio" name="location" value="formthencookies"
<dtml-if "loc=='formthencookies'">CHECKED</dtml-if>> Form then cookies
<input type="checkbox" name="location:list" value="url"
<dtml-if "'url' in namespaces">CHECKED</dtml-if>> URLs
</td>
</tr>
</table>
......@@ -73,6 +67,17 @@
</tr>
</dtml-let>
<TR>
<TD ALIGN="LEFT" VALIGN="TOP">
<div class="form-label">
Automatically Encode Zope-Generated<br>URLs With A Browser Id
</div>
</TD>
<TD ALIGN="LEFT" VALIGN="TOP">
<INPUT TYPE="checkbox" NAME="auto_url_encoding"
<dtml-if getAutoUrlEncoding>CHECKED</dtml-if>>
</TD>
</TR>
<TR>
<TD ALIGN="LEFT" VALIGN="TOP">
<div class="form-label">
......
......@@ -32,12 +32,24 @@ class BrowserIdManagerInterface(
visitors, and for servicing requests from Session Data Managers
related to the browser id.
"""
def encodeUrl(url):
def encodeUrl(url, style='querystring'):
"""
Encodes a provided URL with the current request's browser id
and returns the result. For example, the call
encodeUrl('http://foo.com/amethod') might return
'http://foo.com/amethod?_ZopeId=as9dfu0adfu0ad'.
and returns the result. Two forms of URL-encoding are supported:
'querystring' and 'inline'. 'querystring' is the default.
If the 'querystring' form is used, the browser id name/value pair
are postfixed onto the URL as a query string. If the 'inline'
form is used, the browser id name/value pair are prefixed onto
the URL as the first two path segment elements.
For example:
The call encodeUrl('http://foo.com/amethod', style='querystring')
might return 'http://foo.com/amethod?_ZopeId=as9dfu0adfu0ad'.
The call encodeUrl('http://foo.com/amethod, style='inline')
might return 'http://foo.com/_ZopeId/as9dfu0adfu0ad/amethod'.
Permission required: Access contents information
......
......@@ -15,14 +15,21 @@ Browser Id Manager - Add
Title -- the browser id manager title.
Look for browser id name in -- the cookie name and/or form variable name
used for this browser id manager instance. This will be the
Browser Id Name -- the cookie name and/or form variable
name used for this browser id manager instance. This will be the
name looked up in the 'cookies' or 'form' REQUEST namespaces
when the browser id manager attempts to find a cookie or form
variable with a browser id in it.
Browser id location -- select from one of the available
lookup ordering schemes involving cookies and forms
Look for Browser Id In -- choose any of 'Forms and Query Strings',
'URLs', or 'Cookies'. The browser id name/value will be looked
for within these places.
Automatically Encode Zope-Generated URLs With A Browser Id -- if
this is selected, URLs generated by Zope (such as URLs which come
as a result of calling an object's 'absolute_url' method) will be
encoded with a browser name and browser id as the first two
elements of the URL path.
Cookie path -- this is the 'path' element which should be sent
in the session token cookie. For more information, see the
......
......@@ -4,14 +4,20 @@ Browser Id Manager - Change
Title -- the browser id manager title.
Browser id name -- the cookie name and/or form variable name
Browser id name -- the cookie or forms variable name
used for this browser id manager instance. This will be the
name looked up in the 'cookies' or 'form' REQUEST namespaces
when the browser id manager attempts to find a cookie or form
variable with a browser id in it.
Look for browser id name in -- select from one of the available
lookup ordering schemes involving cookies and forms
name looked up in the namespaces specified by "Look for browser
id name in" (below).
Look for Browser Id In -- choose any of 'Forms and Query Strings',
'URLs', or 'Cookies'. The browser id name/value will be looked
for within these places.
Automatically Encode Zope-Generated URLs With A Browser Id -- if
this is selected, URLs generated by Zope (such as URLs which come
as a result of calling an object's 'absolute_url' method) will be
encoded with a browser name and browser id as the first two
elements of the URL path.
Cookie path -- this is the 'path' element which should be sent
in the session token cookie. For more information, see the
......
......@@ -18,7 +18,7 @@ from Testing import makerequest
import ZODB # in order to get Persistence.Persistent working
import Acquisition
from Acquisition import aq_base
from Products.Sessions.BrowserIdManager import BrowserIdManager
from Products.Sessions.BrowserIdManager import BrowserIdManager, getNewBrowserId
from Products.Sessions.SessionDataManager import \
SessionDataManager, SessionDataManagerErr
from Products.Transience.Transience import \
......@@ -149,7 +149,7 @@ class BaseReaderWriter(threading.Thread):
self.conn = db.open()
self.app = self.conn.root()['Application']
self.app = makerequest.makerequest(self.app)
token = self.app.browser_id_manager._getNewBrowserId()
token = getNewBrowserId()
self.app.REQUEST.browser_id_ = token
self.iters = iters
self.sdm_name = sdm_name
......
......@@ -13,30 +13,41 @@
"""
Test suite for session id manager.
$Id: testBrowserIdManager.py,v 1.11 2002/08/14 22:25:10 mj Exp $
$Id: testBrowserIdManager.py,v 1.12 2002/08/19 19:50:18 chrism Exp $
"""
__version__ = "$Revision: 1.11 $"[11:-2]
__version__ = "$Revision: 1.12 $"[11:-2]
import sys
import ZODB
from Products.Sessions.BrowserIdManager import BrowserIdManager, BrowserIdManagerErr
from Products.Sessions.BrowserIdManager import BrowserIdManager, \
BrowserIdManagerErr, BrowserIdManagerTraverser, \
isAWellFormedBrowserId
from unittest import TestCase, TestSuite, TextTestRunner, makeSuite
from ZPublisher.HTTPRequest import HTTPRequest
from ZPublisher.HTTPResponse import HTTPResponse
from ZPublisher.BeforeTraverse import queryBeforeTraverse
from sys import stdin
from os import environ
from OFS.Application import Application
class TestBrowserIdManager(TestCase):
def setUp(self):
self.m = BrowserIdManager('foo')
self.app = Application()
self.app.id = 'App'
mgr = BrowserIdManager('browser_id_manager')
self.app._setObject('browser_id_manager', mgr)
self.m = self.app.browser_id_manager
resp = HTTPResponse()
environ['SERVER_NAME']='fred'
environ['SERVER_PORT']='80'
req = HTTPRequest(stdin, environ, resp)
self.m.REQUEST = req
self.req = HTTPRequest(stdin, environ, resp)
self.req['TraversalRequestNameStack'] = ['foo', 'bar']
self.app.REQUEST = self.req
def tearDown(self):
del self.m
self.app.REQUEST = None
del self.app
def testSetBrowserIdName(self):
self.m.setBrowserIdName('foo')
......@@ -49,30 +60,16 @@ class TestBrowserIdManager(TestCase):
lambda self=self: self.m.setBrowserIdName(1))
def testSetBadNamespaces(self):
d = {1:'gummy', 2:'froopy'}
d = ('gummy', 'froopy')
self.assertRaises(BrowserIdManagerErr,
lambda self=self,d=d:
self.m.setBrowserIdNamespaces(d))
def testSetGoodNamespaces(self):
d = {1:'cookies', 2:'form'}
d = ('cookies', 'url', 'form')
self.m.setBrowserIdNamespaces(d)
self.failUnless(self.m.getBrowserIdNamespaces() == d)
def testSetNamespacesByLocation(self):
self.m.setBrowserIdLocation('cookiesonly')
self.failUnless(self.m.getBrowserIdNamespaces() == {1:'cookies'})
self.failUnless(self.m.getBrowserIdLocation() == 'cookiesonly')
self.m.setBrowserIdLocation('cookiesthenform')
self.failUnless(self.m.getBrowserIdNamespaces()=={1:'cookies',2:'form'})
self.failUnless(self.m.getBrowserIdLocation() == 'cookiesthenform')
self.m.setBrowserIdLocation('formonly')
self.failUnless(self.m.getBrowserIdNamespaces() == {1:'form'})
self.failUnless(self.m.getBrowserIdLocation() == 'formonly')
self.m.setBrowserIdLocation('formthencookies')
self.failUnless(self.m.getBrowserIdNamespaces()=={1:'form',2:'cookies'})
self.failUnless(self.m.getBrowserIdLocation() == 'formthencookies')
def testSetBadCookiePath(self):
path = '/;'
self.assertRaises(BrowserIdManagerErr,
......@@ -148,33 +145,13 @@ class TestBrowserIdManager(TestCase):
a = self.m.getBrowserId()
self.failUnless( self.m.isBrowserIdNew() )
def testIsBrowserIdFromCookieFirst(self):
token = self.m.getBrowserId()
self.m.REQUEST.browser_id_ = token
self.m.REQUEST.browser_id_ns_ = 'cookies'
tokenkey = self.m.getBrowserIdName()
self.m.REQUEST.cookies[tokenkey] = token
self.m.setBrowserIdNamespaces({1:'cookies', 2:'form'})
a = self.m.getBrowserId()
self.failUnless( self.m.isBrowserIdFromCookie() )
def testIsBrowserIdFromFormFirst(self):
token = self.m.getBrowserId()
self.m.REQUEST.browser_id_ = token
self.m.REQUEST.browser_id_ns_ = 'form'
tokenkey = self.m.getBrowserIdName()
self.m.REQUEST.form[tokenkey] = token
self.m.setBrowserIdNamespaces({1:'form', 2:'cookies'})
a = self.m.getBrowserId()
self.failUnless( self.m.isBrowserIdFromForm() )
def testIsBrowserIdFromCookieOnly(self):
token = self.m.getBrowserId()
self.m.REQUEST.browser_id_ = token
self.m.REQUEST.browser_id_ns_ = 'cookies'
tokenkey = self.m.getBrowserIdName()
self.m.REQUEST.form[tokenkey] = token
self.m.setBrowserIdNamespaces({1:'cookies'})
self.m.setBrowserIdNamespaces(('cookies',))
a = self.m.getBrowserId()
self.failUnless( self.m.isBrowserIdFromCookie() )
self.failUnless( not self.m.isBrowserIdFromForm() )
......@@ -185,11 +162,20 @@ class TestBrowserIdManager(TestCase):
self.m.REQUEST.browser_id_ns_ = 'form'
tokenkey = self.m.getBrowserIdName()
self.m.REQUEST.form[tokenkey] = token
self.m.setBrowserIdNamespaces({1:'form'})
self.m.setBrowserIdNamespaces(('form',))
a = self.m.getBrowserId()
self.failUnless( not self.m.isBrowserIdFromCookie() )
self.failUnless( self.m.isBrowserIdFromForm() )
def testIsBrowserIdFromUrlOnly(self):
token = self.m.getBrowserId()
self.m.REQUEST.browser_id_ = token
self.m.REQUEST.browser_id_ns_ = 'url'
self.m.setBrowserIdNamespaces(('url',))
a = self.m.getBrowserId()
self.failUnless( not self.m.isBrowserIdFromCookie() )
self.failUnless( self.m.isBrowserIdFromUrl() )
def testFlushBrowserIdCookie(self):
token = self.m.getBrowserId()
self.m.REQUEST.browser_id_ = token
......@@ -226,6 +212,8 @@ class TestBrowserIdManager(TestCase):
u = 'http://www.zope.org/Members/mcdonc?foo=bar&spam=eggs'
r = self.m.encodeUrl(u)
self.failUnless( r == '%s&amp;%s=%s' % (u, keystring, key) )
r = self.m.encodeUrl(u, style='inline')
self.failUnless( r == 'http://www.zope.org/%s/%s/Members/mcdonc?foo=bar&spam=eggs' % (keystring, key))
def testGetHiddenFormField(self):
keystring = self.m.getBrowserIdName()
......@@ -235,6 +223,47 @@ class TestBrowserIdManager(TestCase):
(keystring, key))
self.failUnless( html == expected )
def testAutoUrlEncoding(self):
self.m.setAutoUrlEncoding(1)
self.m.setBrowserIdNamespaces(('url',))
self.m.updateTraversalData()
traverser = BrowserIdManagerTraverser()
traverser(self.app, self.req)
self.failUnless(isAWellFormedBrowserId(self.req.browser_id_))
print self.req.browser_id_
self.failUnless(self.req.browser_id_ns_ == None)
self.failUnless(self.req._script[-1] == self.req.browser_id_)
self.failUnless(self.req._script[-2] == '_ZopeId')
def testUrlBrowserIdIsFound(self):
bid = '43295340A0bpcu4nkCI'
name = '_ZopeId'
resp = HTTPResponse()
environ['SERVER_NAME']='fred'
environ['SERVER_PORT']='80'
self.req = HTTPRequest(stdin, environ, resp)
self.req['TraversalRequestNameStack'] = ['foo', 'bar', bid, name]
self.app.REQUEST = self.req
self.m.setAutoUrlEncoding(1)
self.m.setBrowserIdNamespaces(('url',))
self.m.updateTraversalData()
traverser = BrowserIdManagerTraverser()
traverser(self.app, self.req)
self.failUnless(isAWellFormedBrowserId(self.req.browser_id_))
self.failUnless(self.req.browser_id_ns_ == 'url')
self.failUnless(self.req._script[-1] == self.req.browser_id_)
self.failUnless(self.req._script[-2] == '_ZopeId')
self.failUnless(self.req['TraversalRequestNameStack'] == ['foo','bar'])
def testUpdateTraversalData(self):
self.m.setBrowserIdNamespaces(('url',))
self.m.updateTraversalData()
self.failUnless(self.m.hasTraversalHook(self.app))
self.failUnless(queryBeforeTraverse(self.app, 'BrowserIdManager'))
self.m.setBrowserIdNamespaces(('cookies', 'form'))
self.m.updateTraversalData()
self.failUnless(not queryBeforeTraverse(self.app,'BrowserIdManager'))
def test_suite():
testsuite = makeSuite(TestBrowserIdManager, 'test')
return testsuite
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment