I made yesterday some checkins on the Zope-2.7 branch, which didn't appear on the zope-checkins list. FYI they're below. I'm porting them to trunk right now. Florent Index: doc/CHANGES.txt =================================================================== RCS file: /cvs-repository/Zope/doc/Attic/CHANGES.txt,v retrieving revision 1.625.2.309 diff -u -r1.625.2.309 CHANGES.txt --- doc/CHANGES.txt 18 Mar 2005 15:31:01 -0000 1.625.2.309 +++ doc/CHANGES.txt 24 Mar 2005 18:32:34 -0000 @@ -11,6 +11,10 @@ - DateIndex/DateRangeIndexes did not reset their __len__ attribute properly when cleared + - Fixed brain.getObject() to correctly traverse to an object even + if one of its parents is not accessible, to be close to what the + Publisher does. + Zope 2.7.5 final (2005/03/20) Bugs fixed Index: lib/python/Products/ZCatalog/CatalogBrains.py =================================================================== RCS file: /cvs-repository/Products/ZCatalog/Attic/CatalogBrains.py,v retrieving revision 1.8.44.1 diff -u -r1.8.44.1 CatalogBrains.py --- lib/python/Products/ZCatalog/CatalogBrains.py 23 Mar 2004 20:27:23 -0000 1.8.44.1 +++ lib/python/Products/ZCatalog/CatalogBrains.py 24 Mar 2005 18:32:35 -0000 @@ -42,12 +42,24 @@ def getObject(self, REQUEST=None): """Return the object for this record - + Will return None if the object cannot be found via its cataloged path (i.e., it was deleted or moved without recataloging), or if the user is - not authorized to access an object along the path. + not authorized to access the object. + + This method mimicks a subset of what publisher's traversal does, + so it allows access if the final object can be accessed even + if intermediate objects cannot. """ - return self.aq_parent.restrictedTraverse(self.getPath(), None) + path = self.getPath().split('/') + if not path: + return None + parent = self.aq_parent + if len(path) > 1: + parent = parent.unrestrictedTraverse('/'.join(path[:-1]), None) + if parent is None: + return None + return parent.restrictedTraverse(path[-1], None) def getRID(self): """Return the record ID for this object.""" Index: lib/python/Products/ZCatalog/tests/testBrains.py =================================================================== RCS file: /cvs-repository/Products/ZCatalog/tests/Attic/testBrains.py,v retrieving revision 1.1.4.1 diff -u -r1.1.4.1 testBrains.py --- lib/python/Products/ZCatalog/tests/testBrains.py 23 Mar 2004 20:27:23 -0000 1.1.4.1 +++ lib/python/Products/ZCatalog/tests/testBrains.py 24 Mar 2005 18:32:35 -0000 @@ -23,15 +23,17 @@ """Happy content""" def __init__(self, id): self.id = id + def check(self): + pass class Secret(Happy): """Object that raises Unauthorized when accessed""" - def __of__(self, parent): + def check(self): raise Unauthorized class Conflicter(Happy): """Object that raises ConflictError when accessed""" - def __of__(self, parent): + def check(self): raise ConflictError class DummyRequest(Acquisition.Implicit): @@ -50,10 +52,20 @@ '/conflicter':Conflicter('conflicter')} _paths = _objs.keys() + ['/zonked'] _paths.sort() - + + # This is sooooo ugly + + def unrestrictedTraverse(self, path, default=None): + assert path == '' # for these tests... + return self + def restrictedTraverse(self, path, default=_marker): + if not path.startswith('/'): + path = '/'+path try: - return self._objs[path].__of__(self) + ob = self._objs[path].__of__(self) + ob.check() + return ob except (KeyError, Unauthorized): if default is not _marker: return default Index: lib/python/Products/ZCatalog/tests/testCatalog.py =================================================================== RCS file: /cvs-repository/Products/ZCatalog/tests/Attic/testCatalog.py,v retrieving revision 1.22.12.6 diff -u -r1.22.12.6 testCatalog.py --- lib/python/Products/ZCatalog/tests/testCatalog.py 18 May 2004 14:48:49 -0000 1.22.12.6 +++ lib/python/Products/ZCatalog/tests/testCatalog.py 24 Mar 2005 18:32:35 -0000 @@ -26,6 +26,10 @@ from Products.ZCatalog import ZCatalog,Vocabulary from Products.ZCatalog.Catalog import Catalog, CatalogError import ExtensionClass +from OFS.Folder import Folder as OFS_Folder +from AccessControl.SecurityManagement import setSecurityManager +from AccessControl.SecurityManagement import noSecurityManager +from AccessControl import Unauthorized from Products.PluginIndexes.FieldIndex.FieldIndex import FieldIndex from Products.PluginIndexes.TextIndex.TextIndex import TextIndex @@ -60,18 +64,6 @@ # XXX What's this mean? What does this comment apply to? ################################################################################ -# XXX These imports and class don't appear to be needed? -## from AccessControl.SecurityManagement import newSecurityManager -## from AccessControl.SecurityManagement import noSecurityManager - -## class DummyUser: - -## def __init__( self, name ): -## self._name = name - -## def getUserName( self ): -## return self._name - def sort(iterable): L = list(iterable) L.sort() @@ -584,7 +576,82 @@ expected.sort() expected = [rid for sortkey, rid, getitem in expected] self.assertEqual(merged_rids, expected) - + + +class PickySecurityManager: + def __init__(self, badnames=[]): + self.badnames = badnames + def validateValue(self, value): + return 1 + def validate(self, accessed, container, name, value): + if name not in self.badnames: + return 1 + raise Unauthorized(name) + +class Folder(OFS_Folder): + def __init__(self, id): + self._setId(id) + OFS_Folder.__init__(self) + +class TestZCatalogGetObject(unittest.TestCase): + # Test what objects are returned by brain.getObject() + + def setUp(self): + catalog = ZCatalog.ZCatalog('catalog') + catalog.addIndex('id', 'FieldIndex') + root = Folder('') + root.getPhysicalRoot = lambda: root + self.root = root + self.root.catalog = catalog + + def tearDown(self): + noSecurityManager() + + def test_getObject_found(self): + # Check normal traversal + root = self.root + catalog = root.catalog + root.ob = Folder('ob') + catalog.catalog_object(root.ob) + brain = catalog.searchResults()[0] + self.assertEqual(brain.getPath(), '/ob') + self.assertEqual(brain.getObject().getId(), 'ob') + + def test_getObject_missing(self): + # Check that if the object is missing None is returned + root = self.root + catalog = root.catalog + root.ob = Folder('ob') + catalog.catalog_object(root.ob) + brain = catalog.searchResults()[0] + del root.ob + self.assertEqual(brain.getObject(), None) + + def test_getObject_restricted(self): + # Check that if the object's security does not allow traversal, + # None is returned + root = self.root + catalog = root.catalog + root.fold = Folder('fold') + root.fold.ob = Folder('ob') + catalog.catalog_object(root.fold.ob) + brain = catalog.searchResults()[0] + # allow all accesses + pickySecurityManager = PickySecurityManager() + setSecurityManager(pickySecurityManager) + self.assertEqual(brain.getObject().getId(), 'ob') + # disallow just 'ob' access + pickySecurityManager = PickySecurityManager(['ob']) + setSecurityManager(pickySecurityManager) + self.assertEqual(brain.getObject(), None) + # disallow just 'fold' access + pickySecurityManager = PickySecurityManager(['fold']) + setSecurityManager(pickySecurityManager) + ob = brain.getObject() + self.failIf(ob is None) + self.assertEqual(ob.getId(), 'ob') + + def test_suite(): suite = unittest.TestSuite() suite.addTest( unittest.makeSuite( TestAddDelColumn ) ) @@ -593,6 +660,7 @@ suite.addTest( unittest.makeSuite( TestCatalogObject ) ) suite.addTest( unittest.makeSuite( TestRS ) ) suite.addTest( unittest.makeSuite( TestMerge ) ) + suite.addTest( unittest.makeSuite( TestZCatalogGetObject ) ) return suite if __name__ == '__main__': -- Florent Guillaume, Nuxeo (Paris, France) CTO, Director of R&D +33 1 40 33 71 59 http://nuxeo.com fg@nuxeo.com