[CMF-checkins] SVN: CMF/trunk/C Replaced deprecated 'manage_afterAdd' and 'manage_beforeDelete' hooks in CookieCrumbler with a Z3-style event subscriber

Tres Seaver tseaver at palladion.com
Tue Jan 31 12:37:30 EST 2006


Log message for revision 41514:
  Replaced deprecated 'manage_afterAdd' and 'manage_beforeDelete' hooks in CookieCrumbler with a Z3-style event subscriber
  
  o This subscriber does the same registration with the container's
    __before_traverse__ which the old hooks did.
  
  o Tidied up the CookieCrumbler tests.
  
  

Changed:
  U   CMF/trunk/CHANGES.txt
  U   CMF/trunk/CMFCore/CookieCrumbler.py
  U   CMF/trunk/CMFCore/configure.zcml
  U   CMF/trunk/CMFCore/tests/test_CookieCrumbler.py

-=-
Modified: CMF/trunk/CHANGES.txt
===================================================================
--- CMF/trunk/CHANGES.txt	2006-01-31 16:13:13 UTC (rev 41513)
+++ CMF/trunk/CHANGES.txt	2006-01-31 17:37:30 UTC (rev 41514)
@@ -181,6 +181,10 @@
 
   Others
 
+    - Replaced deprecated 'manage_afterAdd' and 'manage_beforeDelete' hooks
+      in CookieCrumbler with a Z3-style event subscriber which does the
+      registration with the container's __before_traverse__.
+
     - Moved GenericSetup out of the CMF package, it is now a standalone
       product, but still distributed as part of the CMF package.
 

Modified: CMF/trunk/CMFCore/CookieCrumbler.py
===================================================================
--- CMF/trunk/CMFCore/CookieCrumbler.py	2006-01-31 16:13:13 UTC (rev 41513)
+++ CMF/trunk/CMFCore/CookieCrumbler.py	2006-01-31 17:37:30 UTC (rev 41514)
@@ -28,6 +28,8 @@
 from OFS.Folder import Folder
 from zExceptions import Redirect
 from zope.interface import implements
+from zope.app.container.interfaces import IObjectMovedEvent
+from OFS.interfaces import IObjectWillBeMovedEvent
 
 from interfaces import ICookieCrumbler
 
@@ -370,20 +372,6 @@
         # We should not normally get here.
         return 'Logged out.'
 
-    # Installation and removal of traversal hooks.
-
-    def manage_beforeDelete(self, item, container):
-        if item is self:
-            handle = self.meta_type + '/' + self.getId()
-            BeforeTraverse.unregisterBeforeTraverse(container, handle)
-
-    def manage_afterAdd(self, item, container):
-        if item is self:
-            handle = self.meta_type + '/' + self.getId()
-            container = container.this()
-            nc = BeforeTraverse.NameCaller(self.getId())
-            BeforeTraverse.registerBeforeTraverse(container, nc, handle)
-
     security.declarePublic('propertyLabel')
     def propertyLabel(self, id):
         """Return a label for the given property id
@@ -395,7 +383,24 @@
 
 Globals.InitializeClass(CookieCrumbler)
 
+def handleCookieCrumblerEvent(ob, event):
+    """ Event subscriber for (un)registering a CC as a before traverse hook.
+    """
+    if not ICookieCrumbler.providedBy(ob):
+        return
 
+    if IObjectMovedEvent.providedBy(event):
+        if event.newParent is not None:
+            # register before traverse hook
+            handle = ob.meta_type + '/' + ob.getId()
+            nc = BeforeTraverse.NameCaller(ob.getId())
+            BeforeTraverse.registerBeforeTraverse(event.newParent, nc, handle)
+    elif IObjectWillBeMovedEvent.providedBy(event):
+        if event.oldParent is not None:
+            # unregister before traverse hook
+            handle = ob.meta_type + '/' + ob.getId()
+            BeforeTraverse.unregisterBeforeTraverse(event.newParent, handle)
+
 class ResponseCleanup:
     def __init__(self, resp):
         self.resp = resp

Modified: CMF/trunk/CMFCore/configure.zcml
===================================================================
--- CMF/trunk/CMFCore/configure.zcml	2006-01-31 16:13:13 UTC (rev 41513)
+++ CMF/trunk/CMFCore/configure.zcml	2006-01-31 17:37:30 UTC (rev 41514)
@@ -39,4 +39,10 @@
       global="False"
       />
 
+  <subscriber
+    for=".interfaces.ICookieCrumbler
+         zope.app.event.interfaces.IObjectEvent"
+    handler=".CookieCrumbler.handleCookieCrumblerEvent"
+    />
+
 </configure>

Modified: CMF/trunk/CMFCore/tests/test_CookieCrumbler.py
===================================================================
--- CMF/trunk/CMFCore/tests/test_CookieCrumbler.py	2006-01-31 16:13:13 UTC (rev 41513)
+++ CMF/trunk/CMFCore/tests/test_CookieCrumbler.py	2006-01-31 17:37:30 UTC (rev 41514)
@@ -14,27 +14,15 @@
 
 $Id$
 """
-
-import base64
-from cStringIO import StringIO
 import unittest
-import urllib
+from Products.CMFCore.tests.base.testcase import PlacelessSetup
 
-import Testing
-from OFS.DTMLMethod import DTMLMethod
-from OFS.Folder import Folder
-from zExceptions.unauthorized import Unauthorized
-from AccessControl.User import UserFolder
-from AccessControl.SecurityManagement import noSecurityManager
-from ZPublisher.HTTPRequest import HTTPRequest
-from ZPublisher.HTTPResponse import HTTPResponse
-
-from Products.CMFCore.CookieCrumbler \
-     import CookieCrumbler, manage_addCC, Redirect
-
-
 def makerequest(root, stdout, stdin=None):
     # Customized version of Testing.makerequest.makerequest()
+    from cStringIO import StringIO
+    from ZPublisher.HTTPRequest import HTTPRequest
+    from ZPublisher.HTTPResponse import HTTPResponse
+
     resp = HTTPResponse(stdout=stdout)
     environ = {}
     environ['SERVER_NAME'] = 'example.com'
@@ -47,11 +35,48 @@
     return req
 
 
-class CookieCrumblerTests (unittest.TestCase):
+class CookieCrumblerTests(unittest.TestCase, PlacelessSetup):
 
+    _CC_ID = 'cookie_authentication'
+
+    def _getTargetClass(self):
+        from Products.CMFCore.CookieCrumbler  import CookieCrumbler
+        return CookieCrumbler
+
+    def _makeOne(self, *args, **kw):
+        return self._getTargetClass()(*args, **kw)
+
     def setUp(self):
+        from zope.component import provideHandler
+        from zope.app.event.interfaces import IObjectEvent
+        from Products.CMFCore.interfaces import ICookieCrumbler
+        from Products.CMFCore.CookieCrumbler import handleCookieCrumblerEvent
+
+        PlacelessSetup.setUp(self)
+        self._finally = None
+
+        provideHandler(handleCookieCrumblerEvent,
+                       adapts=(ICookieCrumbler, IObjectEvent))
+
+    def tearDown(self):
+        from AccessControl.SecurityManagement import noSecurityManager
+
+        if self._finally is not None:
+            self._finally()
+
+        noSecurityManager()
+        PlacelessSetup.tearDown(self)
+
+    def _makeSite(self):
+        import base64
+        from cStringIO import StringIO
+        import urllib
+
+        from AccessControl.User import UserFolder
+        from OFS.Folder import Folder
+        from OFS.DTMLMethod import DTMLMethod
+
         root = Folder()
-        self.root = root
         root.isTopLevelPrincipiaApplicationObject = 1  # User folder needs this
         root.getPhysicalPath = lambda: ()  # hack
         root._View_Permission = ('Anonymous',)
@@ -62,10 +87,9 @@
         users._doAddUser('isaac', 'pass-w', ('Son',), ())
         root._setObject(users.id, users)
 
-        cc = CookieCrumbler()
-        cc.id = 'cookie_authentication'
+        cc = self._makeOne()
+        cc.id = self._CC_ID
         root._setObject(cc.id, cc)
-        self.cc = getattr(root, cc.id)
 
         index = DTMLMethod()
         index.munge('This is the default view')
@@ -83,105 +107,113 @@
         protected._setId('protected')
         root._setObject(protected.getId(), protected)
 
-        self.responseOut = StringIO()
-        self.req = makerequest(root, self.responseOut)
+        req = makerequest(root, StringIO())
+        self._finally = req.close
 
-        self.credentials = urllib.quote(
+        credentials = urllib.quote(
             base64.encodestring('abraham:pass-w').rstrip())
 
+        return root, cc, req, credentials
 
-    def tearDown(self):
-        self.req.close()
-        noSecurityManager()
-
     def test_z3interfaces(self):
         from zope.interface.verify import verifyClass
         from Products.CMFCore.interfaces import ICookieCrumbler
 
-        verifyClass(ICookieCrumbler, CookieCrumbler)
+        verifyClass(ICookieCrumbler, self._getTargetClass())
 
     def testNoCookies(self):
         # verify the cookie crumbler doesn't break when no cookies are given
-        self.req.traverse('/')
-        self.assertEqual(self.req['AUTHENTICATED_USER'].getUserName(),
+        root, cc, req, credentials = self._makeSite()
+        req.traverse('/')
+        self.assertEqual(req['AUTHENTICATED_USER'].getUserName(),
                          'Anonymous User')
 
-
     def testCookieLogin(self):
         # verify the user and auth cookie get set
-        self.req.cookies['__ac_name'] = 'abraham'
-        self.req.cookies['__ac_password'] = 'pass-w'
-        self.req.traverse('/')
+        root, cc, req, credentials = self._makeSite()
 
-        self.assert_(self.req.has_key('AUTHENTICATED_USER'))
-        self.assertEqual(self.req['AUTHENTICATED_USER'].getUserName(),
+        req.cookies['__ac_name'] = 'abraham'
+        req.cookies['__ac_password'] = 'pass-w'
+        req.traverse('/')
+
+        self.failUnless(req.has_key('AUTHENTICATED_USER'))
+        self.assertEqual(req['AUTHENTICATED_USER'].getUserName(),
                          'abraham')
-        resp = self.req.response
-        self.assert_(resp.cookies.has_key('__ac'))
+        resp = req.response
+        self.failUnless(resp.cookies.has_key('__ac'))
         self.assertEqual(resp.cookies['__ac']['value'],
-                         self.credentials)
+                         credentials)
         self.assertEqual(resp.cookies['__ac']['path'], '/')
 
-
     def testCookieResume(self):
         # verify the cookie crumbler continues the session
-        self.req.cookies['__ac'] = self.credentials
-        self.req.traverse('/')
-        self.assert_(self.req.has_key('AUTHENTICATED_USER'))
-        self.assertEqual(self.req['AUTHENTICATED_USER'].getUserName(),
+        root, cc, req, credentials = self._makeSite()
+        req.cookies['__ac'] = credentials
+        req.traverse('/')
+        self.failUnless(req.has_key('AUTHENTICATED_USER'))
+        self.assertEqual(req['AUTHENTICATED_USER'].getUserName(),
                          'abraham')
 
-
     def testPasswordShredding(self):
         # verify the password is shredded before the app gets the request
-        self.req.cookies['__ac_name'] = 'abraham'
-        self.req.cookies['__ac_password'] = 'pass-w'
-        self.assert_(self.req.has_key('__ac_password'))
-        self.req.traverse('/')
-        self.assert_(not self.req.has_key('__ac_password'))
-        self.assert_(not self.req.has_key('__ac'))
+        root, cc, req, credentials = self._makeSite()
+        req.cookies['__ac_name'] = 'abraham'
+        req.cookies['__ac_password'] = 'pass-w'
+        self.failUnless(req.has_key('__ac_password'))
+        req.traverse('/')
+        self.failIf( req.has_key('__ac_password'))
+        self.failIf( req.has_key('__ac'))
 
-
     def testCredentialsNotRevealed(self):
         # verify the credentials are shredded before the app gets the request
-        self.req.cookies['__ac'] = self.credentials
-        self.assert_(self.req.has_key('__ac'))
-        self.req.traverse('/')
-        self.assert_(not self.req.has_key('__ac'))
+        root, cc, req, credentials = self._makeSite()
+        req.cookies['__ac'] = credentials
+        self.failUnless(req.has_key('__ac'))
+        req.traverse('/')
+        self.failIf( req.has_key('__ac'))
 
-
     def testAutoLoginRedirection(self):
         # Redirect unauthorized anonymous users to the login page
-        self.assertRaises(Redirect, self.req.traverse, '/protected')
+        from Products.CMFCore.CookieCrumbler  import Redirect
 
+        root, cc, req, credentials = self._makeSite()
+        self.assertRaises(Redirect, req.traverse, '/protected')
 
     def testDisabledAutoLoginRedirection(self):
         # When disable_cookie_login__ is set, don't redirect.
-        self.req['disable_cookie_login__'] = 1
-        self.assertRaises(Unauthorized, self.req.traverse, '/protected')
+        from zExceptions.unauthorized import Unauthorized
 
+        root, cc, req, credentials = self._makeSite()
+        req['disable_cookie_login__'] = 1
+        self.assertRaises(Unauthorized, req.traverse, '/protected')
 
+
     def testNoRedirectAfterAuthenticated(self):
         # Don't redirect already-authenticated users to the login page,
         # even when they try to access things they can't get.
-        self.req.cookies['__ac'] = self.credentials
-        self.assertRaises(Unauthorized, self.req.traverse, '/protected')
+        from zExceptions.unauthorized import Unauthorized
 
+        root, cc, req, credentials = self._makeSite()
+        req.cookies['__ac'] = credentials
+        self.assertRaises(Unauthorized, req.traverse, '/protected')
 
     def testRetryLogin(self):
         # After a failed login, CookieCrumbler should give the user an
         # opportunity to try to log in again.
-        self.req.cookies['__ac_name'] = 'israel'
-        self.req.cookies['__ac_password'] = 'pass-w'
+        from Products.CMFCore.CookieCrumbler  import Redirect
+
+        root, cc, req, credentials = self._makeSite()
+        req.cookies['__ac_name'] = 'israel'
+        req.cookies['__ac_password'] = 'pass-w'
         try:
-            self.req.traverse('/protected')
+            req.traverse('/protected')
         except Redirect, s:
             # Test passed
             if hasattr(s, 'args'):
                 s = s.args[0]
-            self.assert_(s.find('came_from=') >= 0)
-            self.assert_(s.find('retry=1') >= 0)
-            self.assert_(s.find('disable_cookie_login__=1') >= 0)
+            self.failUnless(s.find('came_from=') >= 0)
+            self.failUnless(s.find('retry=1') >= 0)
+            self.failUnless(s.find('disable_cookie_login__=1') >= 0)
         else:
             self.fail('Did not redirect')
 
@@ -189,135 +221,173 @@
     def testLoginRestoresQueryString(self):
         # When redirecting for login, the came_from form field should
         # include the submitted URL as well as the query string.
-        self.req['PATH_INFO'] = '/protected'
-        self.req['QUERY_STRING'] = 'a:int=1&x:string=y'
+        import urllib
+        from Products.CMFCore.CookieCrumbler  import Redirect
+
+        root, cc, req, credentials = self._makeSite()
+        req['PATH_INFO'] = '/protected'
+        req['QUERY_STRING'] = 'a:int=1&x:string=y'
         try:
-            self.req.traverse('/protected')
+            req.traverse('/protected')
         except Redirect, s:
             if hasattr(s, 'args'):
                 s = s.args[0]
-            to_find = urllib.quote('/protected?' + self.req['QUERY_STRING'])
-            self.assert_(s.find(to_find) >= 0, s)
+            to_find = urllib.quote('/protected?' + req['QUERY_STRING'])
+            self.failUnless(s.find(to_find) >= 0, s)
         else:
             self.fail('Did not redirect')
 
-
     def testCacheHeaderAnonymous(self):
         # Should not set cache-control
-        self.req.traverse('/')
+        root, cc, req, credentials = self._makeSite()
+        req.traverse('/')
         self.assertEqual(
-            self.req.response.headers.get('cache-control', ''), '')
+            req.response.headers.get('cache-control', ''), '')
 
-
     def testCacheHeaderLoggingIn(self):
         # Should set cache-control
-        self.req.cookies['__ac_name'] = 'abraham'
-        self.req.cookies['__ac_password'] = 'pass-w'
-        self.req.traverse('/')
-        self.assertEqual(self.req.response['cache-control'], 'private')
+        root, cc, req, credentials = self._makeSite()
+        req.cookies['__ac_name'] = 'abraham'
+        req.cookies['__ac_password'] = 'pass-w'
+        req.traverse('/')
+        self.assertEqual(req.response['cache-control'], 'private')
 
-
     def testCacheHeaderAuthenticated(self):
         # Should set cache-control
-        self.req.cookies['__ac'] = self.credentials
-        self.req.traverse('/')
-        self.assertEqual(self.req.response['cache-control'], 'private')
+        root, cc, req, credentials = self._makeSite()
+        req.cookies['__ac'] = credentials
+        req.traverse('/')
+        self.assertEqual(req.response['cache-control'], 'private')
 
-
     def testCacheHeaderDisabled(self):
         # Should not set cache-control
-        self.cc.cache_header_value = ''
-        self.req.cookies['__ac'] = self.credentials
-        self.req.traverse('/')
+        root, cc, req, credentials = self._makeSite()
+        cc.cache_header_value = ''
+        req.cookies['__ac'] = credentials
+        req.traverse('/')
         self.assertEqual(
-            self.req.response.headers.get('cache-control', ''), '')
+            req.response.headers.get('cache-control', ''), '')
 
-
     def testDisableLoginDoesNotPreventPasswordShredding(self):
         # Even if disable_cookie_login__ is set, read the cookies
         # anyway to avoid revealing the password to the app.
         # (disable_cookie_login__ does not mean disable cookie
         # authentication, it only means disable the automatic redirect
         # to the login page.)
-        self.req.cookies['__ac_name'] = 'abraham'
-        self.req.cookies['__ac_password'] = 'pass-w'
-        self.req['disable_cookie_login__'] = 1
-        self.req.traverse('/')
-        self.assertEqual(self.req['AUTHENTICATED_USER'].getUserName(),
+        root, cc, req, credentials = self._makeSite()
+        req.cookies['__ac_name'] = 'abraham'
+        req.cookies['__ac_password'] = 'pass-w'
+        req['disable_cookie_login__'] = 1
+        req.traverse('/')
+        self.assertEqual(req['AUTHENTICATED_USER'].getUserName(),
                          'abraham')
         # Here is the real test: the password should have been shredded.
-        self.assert_(not self.req.has_key('__ac_password'))
+        self.failIf( req.has_key('__ac_password'))
 
-
     def testDisableLoginDoesNotPreventPasswordShredding2(self):
-        self.req.cookies['__ac'] = self.credentials
-        self.req['disable_cookie_login__'] = 1
-        self.req.traverse('/')
-        self.assertEqual(self.req['AUTHENTICATED_USER'].getUserName(),
+        root, cc, req, credentials = self._makeSite()
+        req.cookies['__ac'] = credentials
+        req['disable_cookie_login__'] = 1
+        req.traverse('/')
+        self.assertEqual(req['AUTHENTICATED_USER'].getUserName(),
                          'abraham')
-        self.assert_(not self.req.has_key('__ac'))
+        self.failIf( req.has_key('__ac'))
 
-
     def testMidApplicationAutoLoginRedirection(self):
         # Redirect anonymous users to login page if Unauthorized
         # occurs in the middle of the app
-        self.req.traverse('/')
+        from zExceptions.unauthorized import Unauthorized
+
+        root, cc, req, credentials = self._makeSite()
+        req.traverse('/')
         try:
             raise Unauthorized
         except:
-            self.req.response.exception()
-            self.assertEqual(self.req.response.status, 302)
+            req.response.exception()
+            self.assertEqual(req.response.status, 302)
 
-
     def testMidApplicationAuthenticationButUnauthorized(self):
         # Don't redirect already-authenticated users to the login page,
         # even when Unauthorized happens in the middle of the app.
-        self.req.cookies['__ac'] = self.credentials
-        self.req.traverse('/')
+        from zExceptions.unauthorized import Unauthorized
+
+        root, cc, req, credentials = self._makeSite()
+        req.cookies['__ac'] = credentials
+        req.traverse('/')
         try:
             raise Unauthorized
         except:
-            self.req.response.exception()
-            self.assertEqual(self.req.response.status, 401)
+            req.response.exception()
+            self.assertEqual(req.response.status, 401)
 
-
     def testRedirectOnUnauthorized(self):
         # Redirect already-authenticated users to the unauthorized
         # handler page if that's what the sysadmin really wants.
-        self.root.cookie_authentication.unauth_page = 'login_form'
-        self.req.cookies['__ac'] = self.credentials
-        self.assertRaises(Redirect, self.req.traverse, '/protected')
+        from Products.CMFCore.CookieCrumbler  import Redirect
 
+        root, cc, req, credentials = self._makeSite()
+        cc.unauth_page = 'login_form'
+        req.cookies['__ac'] = credentials
+        self.assertRaises(Redirect, req.traverse, '/protected')
 
     def testLoginRatherThanResume(self):
         # When the user presents both a session resume and new
         # credentials, choose the new credentials (so that it's
         # possible to log in without logging out)
-        self.req.cookies['__ac_name'] = 'isaac'
-        self.req.cookies['__ac_password'] = 'pass-w'
-        self.req.cookies['__ac'] = self.credentials
-        self.req.traverse('/')
+        root, cc, req, credentials = self._makeSite()
+        req.cookies['__ac_name'] = 'isaac'
+        req.cookies['__ac_password'] = 'pass-w'
+        req.cookies['__ac'] = credentials
+        req.traverse('/')
 
-        self.assert_(self.req.has_key('AUTHENTICATED_USER'))
-        self.assertEqual(self.req['AUTHENTICATED_USER'].getUserName(),
+        self.failUnless(req.has_key('AUTHENTICATED_USER'))
+        self.assertEqual(req['AUTHENTICATED_USER'].getUserName(),
                          'isaac')
 
-
     def testCreateForms(self):
         # Verify the factory creates the login forms.
-        if CookieCrumbler.__module__.find('CMFCore') >= 0:
+        from Products.CMFCore.CookieCrumbler  import manage_addCC
+
+        if 'CMFCore' in self._getTargetClass().__module__:
             # This test is disabled in CMFCore.
             return
-        self.root._delObject('cookie_authentication')
-        manage_addCC(self.root, 'login', create_forms=1)
-        ids = self.root.login.objectIds()
+
+        root, cc, req, credentials = self._makeSite()
+        root._delObject('cookie_authentication')
+        manage_addCC(root, 'login', create_forms=1)
+        ids = root.login.objectIds()
         ids.sort()
         self.assertEqual(tuple(ids), (
             'index_html', 'logged_in', 'logged_out', 'login_form',
             'standard_login_footer', 'standard_login_header'))
 
+    def test_before_traverse_hooks(self):
+        from OFS.Folder import Folder
+        container = Folder()
+        cc = self._makeOne()
+        cc._setId(self._CC_ID)
+
+        marker = []
+        bt_before = getattr(container, '__before_traverse__', marker)
+        self.failUnless(bt_before is marker)
+
+        container._setObject(self._CC_ID, cc)
+
+        bt_added = getattr(container, '__before_traverse__')
+        self.assertEqual(len(bt_added.items()), 1)
+        k, v = bt_added.items()[0]
+        self.failUnless(k[1].startswith(self._getTargetClass().meta_type))
+        self.assertEqual(v.name, self._CC_ID)
+
+        container._delObject(self._CC_ID)
+
+        bt_removed = getattr(container, '__before_traverse__')
+        self.assertEqual(len(bt_removed.items()), 1)
+
 def test_suite():
-    return unittest.makeSuite(CookieCrumblerTests)
+    return unittest.TestSuite((
+                        unittest.makeSuite(CookieCrumblerTests),
+                        ))
 
 if __name__ == '__main__':
     unittest.main(defaultTest='test_suite')



More information about the CMF-checkins mailing list