Commit b2b8168b authored by Florent Guillaume's avatar Florent Guillaume

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.
parent 4384fcac
...@@ -58,6 +58,10 @@ Zope Changes ...@@ -58,6 +58,10 @@ Zope Changes
Bugs fixed Bugs fixed
- 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.
- Forward ported fix for OFS.CopySupport tests which corrected - Forward ported fix for OFS.CopySupport tests which corrected
signature of a faux security policy's 'validate' method. signature of a faux security policy's 'validate' method.
......
...@@ -43,9 +43,21 @@ class AbstractCatalogBrain(Record.Record, Acquisition.Implicit): ...@@ -43,9 +43,21 @@ class AbstractCatalogBrain(Record.Record, Acquisition.Implicit):
Will return None if the object cannot be found via its cataloged path 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 (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): def getRID(self):
"""Return the record ID for this object.""" """Return the record ID for this object."""
......
...@@ -23,15 +23,17 @@ class Happy(Acquisition.Implicit): ...@@ -23,15 +23,17 @@ class Happy(Acquisition.Implicit):
"""Happy content""" """Happy content"""
def __init__(self, id): def __init__(self, id):
self.id = id self.id = id
def check(self):
pass
class Secret(Happy): class Secret(Happy):
"""Object that raises Unauthorized when accessed""" """Object that raises Unauthorized when accessed"""
def __of__(self, parent): def check(self):
raise Unauthorized raise Unauthorized
class Conflicter(Happy): class Conflicter(Happy):
"""Object that raises ConflictError when accessed""" """Object that raises ConflictError when accessed"""
def __of__(self, parent): def check(self):
raise ConflictError raise ConflictError
class DummyRequest(Acquisition.Implicit): class DummyRequest(Acquisition.Implicit):
...@@ -51,9 +53,19 @@ class DummyCatalog(Acquisition.Implicit): ...@@ -51,9 +53,19 @@ class DummyCatalog(Acquisition.Implicit):
_paths = _objs.keys() + ['/zonked'] _paths = _objs.keys() + ['/zonked']
_paths.sort() _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): def restrictedTraverse(self, path, default=_marker):
if not path.startswith('/'):
path = '/'+path
try: try:
return self._objs[path].__of__(self) ob = self._objs[path].__of__(self)
ob.check()
return ob
except (KeyError, Unauthorized): except (KeyError, Unauthorized):
if default is not _marker: if default is not _marker:
return default return default
......
...@@ -26,6 +26,9 @@ import random ...@@ -26,6 +26,9 @@ import random
import ExtensionClass import ExtensionClass
import OFS.Application import OFS.Application
from AccessControl.SecurityManagement import setSecurityManager
from AccessControl.SecurityManagement import noSecurityManager
from AccessControl import Unauthorized
from Products.ZCatalog import Vocabulary from Products.ZCatalog import Vocabulary
from Products.ZCatalog.Catalog import Catalog from Products.ZCatalog.Catalog import Catalog
from Products.ZCatalog.Catalog import CatalogError from Products.ZCatalog.Catalog import CatalogError
...@@ -64,6 +67,13 @@ def sort(iterable): ...@@ -64,6 +67,13 @@ def sort(iterable):
L.sort() L.sort()
return L return L
from OFS.Folder import Folder as OFS_Folder
class Folder(OFS_Folder):
def __init__(self, id):
self._setId(id)
OFS_Folder.__init__(self)
class CatalogBase: class CatalogBase:
def setUp(self): def setUp(self):
self._vocabulary = Vocabulary.Vocabulary('Vocabulary', 'Vocabulary', self._vocabulary = Vocabulary.Vocabulary('Vocabulary', 'Vocabulary',
...@@ -565,6 +575,77 @@ class TestMerge(unittest.TestCase): ...@@ -565,6 +575,77 @@ class TestMerge(unittest.TestCase):
self.assertEqual(merged_rids, 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 TestZCatalogGetObject(unittest.TestCase):
# Test what objects are returned by brain.getObject()
def setUp(self):
from Products.ZCatalog.ZCatalog import ZCatalog
catalog = 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(): def test_suite():
suite = unittest.TestSuite() suite = unittest.TestSuite()
suite.addTest( unittest.makeSuite( TestAddDelColumn ) ) suite.addTest( unittest.makeSuite( TestAddDelColumn ) )
...@@ -573,6 +654,7 @@ def test_suite(): ...@@ -573,6 +654,7 @@ def test_suite():
suite.addTest( unittest.makeSuite( TestCatalogObject ) ) suite.addTest( unittest.makeSuite( TestCatalogObject ) )
suite.addTest( unittest.makeSuite( TestRS ) ) suite.addTest( unittest.makeSuite( TestRS ) )
suite.addTest( unittest.makeSuite( TestMerge ) ) suite.addTest( unittest.makeSuite( TestMerge ) )
suite.addTest( unittest.makeSuite( TestZCatalogGetObject ) )
return suite return suite
if __name__ == '__main__': if __name__ == '__main__':
......
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