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
......@@ -57,10 +57,14 @@ Zope Changes
text/<foo> types
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
signature of a faux security policy's 'validate' method.
- 'setup.py' did not install the 'Zope' compatibility module
(the old 'Zope' package has been renamed to 'Zope2').
......
......@@ -40,12 +40,24 @@ class AbstractCatalogBrain(Record.Record, Acquisition.Implicit):
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."""
......
......@@ -23,15 +23,17 @@ class Happy(Acquisition.Implicit):
"""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 @@ class DummyCatalog(Acquisition.Implicit):
'/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
......
......@@ -26,6 +26,9 @@ import random
import ExtensionClass
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.Catalog import Catalog
from Products.ZCatalog.Catalog import CatalogError
......@@ -64,6 +67,13 @@ def sort(iterable):
L.sort()
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:
def setUp(self):
self._vocabulary = Vocabulary.Vocabulary('Vocabulary', 'Vocabulary',
......@@ -565,6 +575,77 @@ class TestMerge(unittest.TestCase):
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():
suite = unittest.TestSuite()
suite.addTest( unittest.makeSuite( TestAddDelColumn ) )
......@@ -573,6 +654,7 @@ def test_suite():
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__':
......
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