Beispiel #1
0
class TestTraverse(unittest.TestCase):

    def setUp(self):
        import io
        import transaction
        from AccessControl import SecurityManager
        from AccessControl.SecurityManagement import newSecurityManager
        from OFS.Application import Application
        from OFS.Folder import manage_addFolder
        from OFS.Image import manage_addFile
        from Testing.makerequest import makerequest
        from ZODB.DB import DB
        from ZODB.DemoStorage import DemoStorage

        s = DemoStorage()
        self.connection = DB(s).open()

        try:
            r = self.connection.root()
            a = Application()
            r['Application'] = a
            self.root = a
            responseOut = self.responseOut = io.BytesIO()
            self.app = makerequest(self.root, stdout=responseOut)
            manage_addFolder(self.app, 'folder1')
            folder1 = getattr(self.app, 'folder1')
            setattr(folder1, '+something', 'plus')

            folder1.all_meta_types = (
                {'name': 'File',
                 'action': 'manage_addFile',
                 'permission': 'Add images and files'
                 },
            )

            manage_addFile(folder1, 'file',
                           file=b'', content_type='text/plain')

            # Hack, we need a _p_mtime for the file, so we make sure that it
            # has one. We use a subtransaction, which means we can rollback
            # later and pretend we didn't touch the ZODB.
            transaction.commit()
        except Exception:
            self.connection.close()
            raise
        transaction.begin()
        self.folder1 = getattr(self.app, 'folder1')

        self.policy = UnitTestSecurityPolicy()
        self.oldPolicy = SecurityManager.setSecurityPolicy(self.policy)
        newSecurityManager(None, self._makeUser().__of__(self.root))

    def tearDown(self):
        import transaction
        self._setupSecurity()
        del self.oldPolicy
        del self.policy
        del self.folder1
        transaction.abort()
        self.app._p_jar.sync()
        self.connection.close()
        del self.app
        del self.responseOut
        del self.root
        del self.connection

    def _makeUser(self):
        from Acquisition import Implicit

        class UnitTestUser(Implicit):
            """
                Stubbed out manager for unit testing purposes.
            """
            def getId(self):
                return 'unit_tester'
            getUserName = getId

            def allowed(self, object, object_roles=None):
                return 1

        return UnitTestUser()

    def _makeBoboTraversable(self):
        from OFS.SimpleItem import SimpleItem

        class BoboTraversable(SimpleItem):
            __allow_access_to_unprotected_subobjects__ = 1

            def __bobo_traverse__(self, request, name):
                if name == 'bb_subitem':
                    return BoboTraversable().__of__(self)
                elif name == 'bb_method':
                    return self.bb_method
                elif name == 'bb_status':
                    return self.bb_status
                elif name == 'manufactured':
                    return 42
                else:
                    raise KeyError

            def bb_method(self):
                """Test Method"""
                pass

            bb_status = 'screechy'

        return BoboTraversable()

    def _makeBoboTraversableWithAcquisition(self):
        from OFS.SimpleItem import SimpleItem

        class BoboTraversableWithAcquisition(SimpleItem):
            """ A BoboTraversable which may use acquisition to find objects.

            This is similar to how the __bobo_traverse__ behaves).
            """

            def __bobo_traverse__(self, request, name):
                from Acquisition import aq_get
                return aq_get(self, name)

        return BoboTraversableWithAcquisition()

    def _makeRestricted(self, name='dummy'):
        from OFS.SimpleItem import SimpleItem

        class Restricted(SimpleItem):
            """Instance we'll check with ProtectedMethodSecurityPolicy."""

            getId__roles__ = None  # ACCESS_PUBLIC
            def getId(self):  # NOQA: E306  # pseudo decorator
                return self.id

            private__roles__ = ()  # ACCESS_PRIVATE
            def private(self):  # NOQA: E306  # pseudo decorator
                return 'private!'

            # not protected
            def ohno(self):
                return 'ohno!'

        return Restricted(name)

    def _setupSecurity(self, policy=None):
        from AccessControl import SecurityManager
        from AccessControl.SecurityManagement import noSecurityManager
        if policy is None:
            policy = self.oldPolicy
        noSecurityManager()
        SecurityManager.setSecurityPolicy(policy)

    def test_interfaces(self):
        from OFS.interfaces import ITraversable
        from OFS.Traversable import Traversable
        from zope.interface.verify import verifyClass

        verifyClass(ITraversable, Traversable)

    def testTraversePath(self):
        self.assertTrue('file' in self.folder1.objectIds())
        self.assertTrue(
            self.folder1.unrestrictedTraverse(('', 'folder1', 'file')))
        self.assertTrue(self.folder1.unrestrictedTraverse(('', 'folder1')))

    def testTraverseURLNoSlash(self):
        self.assertTrue('file' in self.folder1.objectIds())
        self.assertTrue(self.folder1.unrestrictedTraverse('/folder1/file'))
        self.assertTrue(self.folder1.unrestrictedTraverse('/folder1'))

    def testTraverseURLSlash(self):
        self.assertTrue('file' in self.folder1.objectIds())
        self.assertTrue(self.folder1.unrestrictedTraverse('/folder1/file/'))
        self.assertTrue(self.folder1.unrestrictedTraverse('/folder1/'))

    def testTraverseToNone(self):
        self.assertRaises(
            KeyError,
            self.folder1.unrestrictedTraverse, ('', 'folder1', 'file2'))
        self.assertRaises(
            KeyError, self.folder1.unrestrictedTraverse, '/folder1/file2')
        self.assertRaises(
            KeyError, self.folder1.unrestrictedTraverse, '/folder1/file2/')

    def testTraverseMethodRestricted(self):
        from AccessControl import Unauthorized
        self.root.my = self._makeRestricted('my')
        my = self.root.my
        my.id = 'my'
        self._setupSecurity(ProtectedMethodSecurityPolicy())
        r = my.restrictedTraverse('getId')
        self.assertEqual(r(), 'my')
        self.assertRaises(Unauthorized, my.restrictedTraverse, 'private')
        self.assertRaises(Unauthorized, my.restrictedTraverse, 'ohno')

    def testBoboTraverseToWrappedSubObj(self):
        # Verify it's possible to use __bobo_traverse__ with the
        # Zope security policy.
        self._setupSecurity()
        bb = self._makeBoboTraversable()
        self.assertRaises(KeyError, bb.restrictedTraverse, 'notfound')
        bb.restrictedTraverse('bb_subitem')

    def testBoboTraverseToMethod(self):
        # Verify it's possible to use __bobo_traverse__ to a method.
        self._setupSecurity()
        bb = self._makeBoboTraversable()
        self.assertTrue(
            bb.restrictedTraverse('bb_method') is not bb.bb_method)

    def testBoboTraverseToSimpleAttrValue(self):
        # Verify it's possible to use __bobo_traverse__ to a simple
        # python value
        self._setupSecurity()
        bb = self._makeBoboTraversable()
        self.assertEqual(bb.restrictedTraverse('bb_status'), 'screechy')

    def testBoboTraverseToNonAttrValue(self):
        # Verify it's possible to use __bobo_traverse__ to an
        # arbitrary manufactured object
        # Default security policy always seems to deny in this case, which
        # is fine, but to test the code branch we sub in the forgiving one
        self._setupSecurity(UnitTestSecurityPolicy())
        bb = self._makeBoboTraversable()
        self.assertTrue(
            bb.restrictedTraverse('manufactured') == 42)

    def testBoboTraverseToAcquiredObject(self):
        # Verify it's possible to use a __bobo_traverse__ which retrieves
        # objects by acquisition
        from Acquisition import aq_inner
        self._setupSecurity()
        bb = self._makeBoboTraversableWithAcquisition()
        bb = bb.__of__(self.root)
        self.assertEqual(
            bb.restrictedTraverse('folder1'), bb.folder1)
        self.assertEqual(
            aq_inner(bb.restrictedTraverse('folder1')),
            self.root.folder1)

    def testBoboTraverseToAcquiredProtectedObject(self):
        # Verify it's possible to use a __bobo_traverse__ which retrieves
        # objects by acquisition
        from AccessControl import Unauthorized
        from AccessControl.Permissions import access_contents_information
        self._setupSecurity()
        folder = self.root.folder1
        # restrict the ability to access the retrieved object itself
        folder.manage_permission(access_contents_information, [], 0)
        bb = self._makeBoboTraversableWithAcquisition()
        bb = bb.__of__(self.root)
        self.assertRaises(Unauthorized,
                          bb.restrictedTraverse, 'folder1')

    def testBoboTraverseToAcquiredAttribute(self):
        # Verify it's possible to use __bobo_traverse__ to an acquired
        # attribute
        self._setupSecurity()
        folder = self.root.folder1
        folder.stuff = 'stuff here'
        bb = self._makeBoboTraversableWithAcquisition()
        bb = bb.__of__(folder)
        self.assertEqual(
            bb.restrictedTraverse('stuff'), 'stuff here')

    def testBoboTraverseToAcquiredProtectedAttribute(self):
        # Verify that using __bobo_traverse__ to get an acquired but
        # protected attribute results in Unauthorized
        from AccessControl import Unauthorized
        from AccessControl.Permissions import access_contents_information
        self._setupSecurity()
        folder = self.root.folder1
        # We protect the the attribute by restricting access to the parent
        folder.manage_permission(access_contents_information, [], 0)
        folder.stuff = 'stuff here'
        bb = self._makeBoboTraversableWithAcquisition()
        bb = bb.__of__(folder)
        self.assertRaises(Unauthorized,
                          self.root.folder1.restrictedTraverse, 'stuff')

    def testBoboTraverseTraversalDefault(self):
        from OFS.SimpleItem import SimpleItem
        from ZPublisher.interfaces import UseTraversalDefault

        class BoboTraversableUseTraversalDefault(SimpleItem):
            """
              A BoboTraversable class which may use "UseTraversalDefault"
              (dependent on "name") to indicate that standard traversal should
              be used.
            """
            default = 'Default'

            def __bobo_traverse__(self, request, name):
                if name == 'normal':
                    return 'Normal'
                raise UseTraversalDefault

        bb = BoboTraversableUseTraversalDefault()
        # normal access -- no traversal default used
        self.assertEqual(bb.unrestrictedTraverse('normal'), 'Normal')
        # use traversal default
        self.assertEqual(bb.unrestrictedTraverse('default'), 'Default')
        # test traversal default with acqires attribute
        si = SimpleItem()
        si.default_acquire = 'Default_Acquire'
        si.bb = bb
        self.assertEqual(si.unrestrictedTraverse('bb/default_acquire'),
                         'Default_Acquire')

    def testAcquiredAttributeDenial(self):
        # Verify that restrictedTraverse raises the right kind of exception
        # on denial of access to an acquired attribute.  If it raises
        # AttributeError instead of Unauthorized, the user may never
        # be prompted for HTTP credentials.
        from AccessControl import Unauthorized
        from AccessControl.SecurityManagement import newSecurityManager
        self._setupSecurity(CruelSecurityPolicy())
        newSecurityManager(None, self._makeUser().__of__(self.root))
        self.root.stuff = 'stuff here'
        self.assertRaises(Unauthorized,
                          self.app.folder1.restrictedTraverse, 'stuff')

    def testDefaultValueWhenUnathorized(self):
        # Test that traversing to an unauthorized object returns
        # the default when provided
        from AccessControl.SecurityManagement import newSecurityManager
        self._setupSecurity(CruelSecurityPolicy())
        newSecurityManager(None, self._makeUser().__of__(self.root))
        self.root.stuff = 'stuff here'
        self.assertEqual(
            self.root.folder1.restrictedTraverse('stuff', 42), 42)

    def testNotFoundIsRaised(self):
        from OFS.SimpleItem import SimpleItem
        from zExceptions import NotFound
        from operator import getitem
        self.folder1._setObject('foo', SimpleItem('foo'))
        self.assertRaises(AttributeError, getitem, self.folder1.foo,
                          'doesntexist')
        self.assertRaises(NotFound, self.folder1.unrestrictedTraverse,
                          'foo/doesntexist')
        self.assertRaises(TypeError, getitem,
                          self.folder1.foo.isPrincipiaFolderish, 'doesntexist')
        self.assertRaises(NotFound, self.folder1.unrestrictedTraverse,
                          'foo/isPrincipiaFolderish/doesntexist')

    def testDefaultValueWhenNotFound(self):
        # Test that traversing to a non-existent object returns
        # the default when provided
        self._setupSecurity()
        self.assertEqual(
            self.root.restrictedTraverse('happy/happy', 'joy'), 'joy')

    def testTraverseUp(self):
        # Test that we can traverse upwards
        from Acquisition import aq_base
        self.assertTrue(
            aq_base(self.root.folder1.file.restrictedTraverse('../..')) is
            aq_base(self.root))

    def testTraverseToNameStartingWithPlus(self):
        # Verify it's possible to traverse to a name such as +something
        self.assertTrue(
            self.folder1.unrestrictedTraverse('+something') == 'plus')
Beispiel #2
0
class TestTraverse(unittest.TestCase):
    def setUp(self):
        import io
        import transaction
        from AccessControl import SecurityManager
        from AccessControl.SecurityManagement import newSecurityManager
        from OFS.Application import Application
        from OFS.Folder import manage_addFolder
        from OFS.Image import manage_addFile
        from Testing.makerequest import makerequest
        from ZODB.DB import DB
        from ZODB.DemoStorage import DemoStorage

        s = DemoStorage()
        self.connection = DB(s).open()

        try:
            r = self.connection.root()
            a = Application()
            r['Application'] = a
            self.root = a
            responseOut = self.responseOut = io.BytesIO()
            self.app = makerequest(self.root, stdout=responseOut)
            manage_addFolder(self.app, 'folder1')
            folder1 = getattr(self.app, 'folder1')
            setattr(folder1, '+something', 'plus')

            folder1.all_meta_types = ({
                'name': 'File',
                'action': 'manage_addFile',
                'permission': 'Add images and files'
            }, )

            manage_addFile(folder1,
                           'file',
                           file=b'',
                           content_type='text/plain')

            # Hack, we need a _p_mtime for the file, so we make sure that it
            # has one. We use a subtransaction, which means we can rollback
            # later and pretend we didn't touch the ZODB.
            transaction.commit()
        except Exception:
            self.connection.close()
            raise
        transaction.begin()
        self.folder1 = getattr(self.app, 'folder1')

        self.policy = UnitTestSecurityPolicy()
        self.oldPolicy = SecurityManager.setSecurityPolicy(self.policy)
        newSecurityManager(None, self._makeUser().__of__(self.root))

    def tearDown(self):
        import transaction
        self._setupSecurity()
        del self.oldPolicy
        del self.policy
        del self.folder1
        transaction.abort()
        self.app._p_jar.sync()
        self.connection.close()
        del self.app
        del self.responseOut
        del self.root
        del self.connection

    def _makeUser(self):
        from Acquisition import Implicit

        class UnitTestUser(Implicit):
            """
                Stubbed out manager for unit testing purposes.
            """
            def getId(self):
                return 'unit_tester'

            getUserName = getId

            def allowed(self, object, object_roles=None):
                return 1

        return UnitTestUser()

    def _makeBoboTraversable(self):
        from OFS.SimpleItem import SimpleItem

        class BoboTraversable(SimpleItem):
            __allow_access_to_unprotected_subobjects__ = 1

            def __bobo_traverse__(self, request, name):
                if name == 'bb_subitem':
                    return BoboTraversable().__of__(self)
                elif name == 'bb_method':
                    return self.bb_method
                elif name == 'bb_status':
                    return self.bb_status
                elif name == 'manufactured':
                    return 42
                else:
                    raise KeyError

            def bb_method(self):
                """Test Method"""
                pass

            bb_status = 'screechy'

        return BoboTraversable()

    def _makeBoboTraversableWithAcquisition(self):
        from OFS.SimpleItem import SimpleItem

        class BoboTraversableWithAcquisition(SimpleItem):
            """ A BoboTraversable which may use acquisition to find objects.

            This is similar to how the __bobo_traverse__ behaves).
            """
            def __bobo_traverse__(self, request, name):
                from Acquisition import aq_get
                return aq_get(self, name)

        return BoboTraversableWithAcquisition()

    def _makeRestricted(self, name='dummy'):
        from OFS.SimpleItem import SimpleItem

        class Restricted(SimpleItem):
            """Instance we'll check with ProtectedMethodSecurityPolicy
            """
            getId__roles__ = None  # ACCESS_PUBLIC

            def getId(self):
                return self.id

            private__roles__ = ()  # ACCESS_PRIVATE

            def private(self):
                return 'private!'

            # not protected
            def ohno(self):
                return 'ohno!'

        return Restricted(name)

    def _setupSecurity(self, policy=None):
        from AccessControl import SecurityManager
        from AccessControl.SecurityManagement import noSecurityManager
        if policy is None:
            policy = self.oldPolicy
        noSecurityManager()
        SecurityManager.setSecurityPolicy(policy)

    def test_interfaces(self):
        from OFS.interfaces import ITraversable
        from OFS.Traversable import Traversable
        from zope.interface.verify import verifyClass

        verifyClass(ITraversable, Traversable)

    def testTraversePath(self):
        self.assertTrue('file' in self.folder1.objectIds())
        self.assertTrue(
            self.folder1.unrestrictedTraverse(('', 'folder1', 'file')))
        self.assertTrue(self.folder1.unrestrictedTraverse(('', 'folder1')))

    def testTraverseURLNoSlash(self):
        self.assertTrue('file' in self.folder1.objectIds())
        self.assertTrue(self.folder1.unrestrictedTraverse('/folder1/file'))
        self.assertTrue(self.folder1.unrestrictedTraverse('/folder1'))

    def testTraverseURLSlash(self):
        self.assertTrue('file' in self.folder1.objectIds())
        self.assertTrue(self.folder1.unrestrictedTraverse('/folder1/file/'))
        self.assertTrue(self.folder1.unrestrictedTraverse('/folder1/'))

    def testTraverseToNone(self):
        self.assertRaises(KeyError, self.folder1.unrestrictedTraverse,
                          ('', 'folder1', 'file2'))
        self.assertRaises(KeyError, self.folder1.unrestrictedTraverse,
                          '/folder1/file2')
        self.assertRaises(KeyError, self.folder1.unrestrictedTraverse,
                          '/folder1/file2/')

    def testTraverseMethodRestricted(self):
        from AccessControl import Unauthorized
        self.root.my = self._makeRestricted('my')
        my = self.root.my
        my.id = 'my'
        self._setupSecurity(ProtectedMethodSecurityPolicy())
        r = my.restrictedTraverse('getId')
        self.assertEqual(r(), 'my')
        self.assertRaises(Unauthorized, my.restrictedTraverse, 'private')
        self.assertRaises(Unauthorized, my.restrictedTraverse, 'ohno')

    def testBoboTraverseToWrappedSubObj(self):
        # Verify it's possible to use __bobo_traverse__ with the
        # Zope security policy.
        self._setupSecurity()
        bb = self._makeBoboTraversable()
        self.assertRaises(KeyError, bb.restrictedTraverse, 'notfound')
        bb.restrictedTraverse('bb_subitem')

    def testBoboTraverseToMethod(self):
        # Verify it's possible to use __bobo_traverse__ to a method.
        self._setupSecurity()
        bb = self._makeBoboTraversable()
        self.assertTrue(bb.restrictedTraverse('bb_method') is not bb.bb_method)

    def testBoboTraverseToSimpleAttrValue(self):
        # Verify it's possible to use __bobo_traverse__ to a simple
        # python value
        self._setupSecurity()
        bb = self._makeBoboTraversable()
        self.assertEqual(bb.restrictedTraverse('bb_status'), 'screechy')

    def testBoboTraverseToNonAttrValue(self):
        # Verify it's possible to use __bobo_traverse__ to an
        # arbitrary manufactured object
        # Default security policy always seems to deny in this case, which
        # is fine, but to test the code branch we sub in the forgiving one
        self._setupSecurity(UnitTestSecurityPolicy())
        bb = self._makeBoboTraversable()
        self.assertTrue(bb.restrictedTraverse('manufactured') is 42)

    def testBoboTraverseToAcquiredObject(self):
        # Verify it's possible to use a __bobo_traverse__ which retrieves
        # objects by acquisition
        from Acquisition import aq_inner
        self._setupSecurity()
        bb = self._makeBoboTraversableWithAcquisition()
        bb = bb.__of__(self.root)
        self.assertEqual(bb.restrictedTraverse('folder1'), bb.folder1)
        self.assertEqual(aq_inner(bb.restrictedTraverse('folder1')),
                         self.root.folder1)

    def testBoboTraverseToAcquiredProtectedObject(self):
        # Verify it's possible to use a __bobo_traverse__ which retrieves
        # objects by acquisition
        from AccessControl import Unauthorized
        from AccessControl.Permissions import access_contents_information
        self._setupSecurity()
        folder = self.root.folder1
        # restrict the ability to access the retrieved object itself
        folder.manage_permission(access_contents_information, [], 0)
        bb = self._makeBoboTraversableWithAcquisition()
        bb = bb.__of__(self.root)
        self.assertRaises(Unauthorized, bb.restrictedTraverse, 'folder1')

    def testBoboTraverseToAcquiredAttribute(self):
        # Verify it's possible to use __bobo_traverse__ to an acquired
        # attribute
        self._setupSecurity()
        folder = self.root.folder1
        folder.stuff = 'stuff here'
        bb = self._makeBoboTraversableWithAcquisition()
        bb = bb.__of__(folder)
        self.assertEqual(bb.restrictedTraverse('stuff'), 'stuff here')

    def testBoboTraverseToAcquiredProtectedAttribute(self):
        # Verify that using __bobo_traverse__ to get an acquired but
        # protected attribute results in Unauthorized
        from AccessControl import Unauthorized
        from AccessControl.Permissions import access_contents_information
        self._setupSecurity()
        folder = self.root.folder1
        # We protect the the attribute by restricting access to the parent
        folder.manage_permission(access_contents_information, [], 0)
        folder.stuff = 'stuff here'
        bb = self._makeBoboTraversableWithAcquisition()
        bb = bb.__of__(folder)
        self.assertRaises(Unauthorized, self.root.folder1.restrictedTraverse,
                          'stuff')

    def testBoboTraverseTraversalDefault(self):
        from OFS.SimpleItem import SimpleItem
        from ZPublisher.interfaces import UseTraversalDefault

        class BoboTraversableUseTraversalDefault(SimpleItem):
            """
              A BoboTraversable class which may use "UseTraversalDefault"
              (dependent on "name") to indicate that standard traversal should
              be used.
            """
            default = 'Default'

            def __bobo_traverse__(self, request, name):
                if name == 'normal':
                    return 'Normal'
                raise UseTraversalDefault

        bb = BoboTraversableUseTraversalDefault()
        # normal access -- no traversal default used
        self.assertEqual(bb.unrestrictedTraverse('normal'), 'Normal')
        # use traversal default
        self.assertEqual(bb.unrestrictedTraverse('default'), 'Default')
        # test traversal default with acqires attribute
        si = SimpleItem()
        si.default_acquire = 'Default_Acquire'
        si.bb = bb
        self.assertEqual(si.unrestrictedTraverse('bb/default_acquire'),
                         'Default_Acquire')

    def testAcquiredAttributeDenial(self):
        # Verify that restrictedTraverse raises the right kind of exception
        # on denial of access to an acquired attribute.  If it raises
        # AttributeError instead of Unauthorized, the user may never
        # be prompted for HTTP credentials.
        from AccessControl import Unauthorized
        from AccessControl.SecurityManagement import newSecurityManager
        self._setupSecurity(CruelSecurityPolicy())
        newSecurityManager(None, self._makeUser().__of__(self.root))
        self.root.stuff = 'stuff here'
        self.assertRaises(Unauthorized, self.app.folder1.restrictedTraverse,
                          'stuff')

    def testDefaultValueWhenUnathorized(self):
        # Test that traversing to an unauthorized object returns
        # the default when provided
        from AccessControl.SecurityManagement import newSecurityManager
        self._setupSecurity(CruelSecurityPolicy())
        newSecurityManager(None, self._makeUser().__of__(self.root))
        self.root.stuff = 'stuff here'
        self.assertEqual(self.root.folder1.restrictedTraverse('stuff', 42), 42)

    def testNotFoundIsRaised(self):
        from OFS.SimpleItem import SimpleItem
        from zExceptions import NotFound
        from operator import getitem
        self.folder1._setObject('foo', SimpleItem('foo'))
        self.assertRaises(AttributeError, getitem, self.folder1.foo,
                          'doesntexist')
        self.assertRaises(NotFound, self.folder1.unrestrictedTraverse,
                          'foo/doesntexist')
        self.assertRaises(TypeError, getitem,
                          self.folder1.foo.isPrincipiaFolderish, 'doesntexist')
        self.assertRaises(NotFound, self.folder1.unrestrictedTraverse,
                          'foo/isPrincipiaFolderish/doesntexist')

    def testDefaultValueWhenNotFound(self):
        # Test that traversing to a non-existent object returns
        # the default when provided
        self._setupSecurity()
        self.assertEqual(self.root.restrictedTraverse('happy/happy', 'joy'),
                         'joy')

    def testTraverseUp(self):
        # Test that we can traverse upwards
        from Acquisition import aq_base
        self.assertTrue(
            aq_base(self.root.folder1.file.restrictedTraverse('../..')) is
            aq_base(self.root))

    def testTraverseToNameStartingWithPlus(self):
        # Verify it's possible to traverse to a name such as +something
        self.assertTrue(
            self.folder1.unrestrictedTraverse('+something') is 'plus')
class PKEReporter(object):
    def __init__(self, db='zodb'):
        self._dbname = db
        self._config = get_config(db)
        self._storage = self._config.storages[0].open()
        self._conn = DB(self._storage).open()
        self._app = self._conn.root()
        self._size = self.get_total_count()

    def get_total_count(self):
        connmanager = self._storage._adapter.connmanager
        conn, cursor = connmanager.open()
        try:
            cursor.execute("SELECT count(zoid) from object_state")
            row = cursor.fetchone()
            return long(row[0])
        finally:
            connmanager.close(conn, cursor)

    def update_progress(self, finished, total):
        fraction = finished / float(total)
        bar_width = 40
        done = '=' * int(bar_width * fraction)
        undone = '-' * (bar_width - int(bar_width * fraction))
        sys.stderr.write('[%s%s%s] %s%% complete\r' % (done, '|', undone, int(fraction*100)))
        sys.stderr.flush()

    def analyze(self, parent_oid, child_oid):
        parent_state = self._storage.load(parent_oid)[0]
        pickler = Analyzer(parent_state, child_oid)
        pickler.load()
        result = pickler.load()
        name = None
        # First try to get the name from the pickle state
        try:
            for k, v in result.iteritems():
                if v is pickler._marker:
                    name = k
                    break
        except Exception:
            pass
        if not name:
            # Now load up the child and see if it has an id
            child = self._conn[child_oid]
            try:
                name = child.id
            except Exception:
                try:
                    name = child.getId()
                except Exception:
                    pass
        if not name:
            # Check the actual attributes on the parent
            parent = self._conn[parent_oid]
            try:
                for k, v in parent.__dict__.iteritems():
                    try:
                        if v == child:
                            name = k
                            break
                    except Exception:
                        pass
            except AttributeError:  # catch these errors -  AttributeError: 'BTrees.OIBTree.OIBTree' object has no attribute '__dict__'
                pass
        return name, pickler.klass

    @staticmethod
    def oid_versions(oid):
        u64ed = u64(oid)
        oid_0xstyle = "0x%08x" % u64ed
        repred = repr(oid)
        return u64ed, oid_0xstyle, repred

    def report(self, oid, ancestors):
        parent_oid = ancestors[-2]
        parent_klass = None
        try:
            immediate_parent = self._conn[parent_oid]
            parent_klass = immediate_parent.__class__
            path = immediate_parent.getPrimaryPath()
        except Exception:
            # Not a PrimaryPathObjectManager, do it manually
            path = ['']
            for (a, b) in zip(ancestors[:-2], ancestors[1:-1]):
                name, klass = self.analyze(a, b)
                path.append(name)
            parent_klass = klass
        path = filter(None, path)
        name, klass = self.analyze(*ancestors[-2:])
        sys.stderr.write(' '*80)
        sys.stderr.flush()
        par_u64, par_0x, par_rep = self.oid_versions(parent_oid)
        oid_u64, oid_0x, oid_rep = self.oid_versions(oid)
        print """
FOUND DANGLING REFERENCE
PATH {path}
TYPE {type}
OID {par_0x} {par_rep} {par_u64}
Refers to a missing object:
    NAME {name}
    TYPE {klass}
    OID", {oid_0x} {oid_rep} {oid_u64}
""".format(path='/'.join(path), type=parent_klass, name=name, klass=klass,
          par_u64=par_u64, par_0x=par_0x, par_rep=par_rep,
          oid_u64=oid_u64, oid_0x=oid_0x, oid_rep=oid_rep)

    def verify(self, root):
        seen = set()
        seen_add = seen.add
        path = ()
        stack = deque([(root, path)])
        reported = 0
        while stack:
            oid, path = stack.popleft()
            seen_add(oid)
            if not len(seen) % 1000:
                self.update_progress(len(seen), self._size)
            try:
                state = self._storage.load(oid)[0]
            except POSKeyError:
                self.report(oid, path)
                reported += 1
            else:
                refs = get_refs(state)
                stack.extend((o, path + (o,)) for o in set(refs) - seen)
        return reported, len(seen), self._size


    def run(self):
        print
        print "="*50
        print
        print "   DATABASE INTEGRITY SCAN: ", self._dbname
        print
        print "="*50

        oid = '\x00\x00\x00\x00\x00\x00\x00\x01'
        reported, scanned, total = self.verify(oid)

        sys.stderr.write(' '*80)
        sys.stderr.flush()

        print
        print "SUMMARY:"
        print "Found", reported, "dangling references"
        print "Scanned", scanned, "out of", total, "reachable objects"
        if total > scanned:
            print "(Run zenossdbpack to garbage collect unreachable objects)"
        print