m .Ec@sdZdkZdkZdkZdkZdkZdkZdklZlZdk l Z dk l Z dk l Z dklZdklZdklZd klZd klZd klZd fd YZdfdYZdfdYZdeifdYZdefdYZdefdYZdefdYZ dZ!dei"fdYZ#dk$Z$dk%Z%e&djo$e'e$i(i)e%i(ddnd e*fd!YZ+d"Z,d#Z-d$Z.e&djoei/d%d&ndS('sNTraverse unit tests. $Id: testTraverse.py 69331 2006-08-01 19:30:42Z alecm $ N(sSecurityManagers Unauthorized(saccess_contents_information(snewSecurityManager(snoSecurityManager(saq_base(s Application(smanage_addFolder(smanage_addFile(s SimpleItem(s makerequesttUnitTestSecurityPolicycBs2tZdZeeeeeedZdZRS(sN Stub out the existing security policy for unit testing purposes. c OsdS(Ni(( tselftaccessedt containertnametvaluetcontexttrolestargstkw((t5/data/zmath/zope/lib/python/OFS/tests/testTraverse.pytvalidate+s cCsdS(Ni((Rt permissiontobjectR((R tcheckPermission6s(t__name__t __module__t__doc__tNoneR R(((R R$s  tCruelSecurityPolicycBs tZdZdZdZRS(sDenies everything cGs t|dS(N(t UnauthorizedR(RRRRRR((R R @scCsdS(Ni((RR R R((R RCs(RRRR R(((R R:s  tProtectedMethodSecurityPolicycBstZdZdZRS(s.Check security strictly on bound methods. cGstt|dddjodSn|djo tn|ii}t||dt }|djodSnt|dS(Ntim_selfit __roles__( tgetattrtaq_baseRRRRRt __class__tklassR R(RRRRRRRR((R R Js    (RRRR (((R RGs t UnitTestUsercBs)tZdZdZeZedZRS(s< Stubbed out manager for unit testing purposes. cCsdS(Nt unit_tester((R((R tgetId]scCsdS(Ni((RR t object_roles((R tallowedbs(RRRRt getUserNameRR (((R RYs  tBoboTraversablecBs&tZdZdZdZdZRS(NicCsp|djoti|SnL|djo |iSn4|djo |iSn|djodSntdS(Nt bb_subitemt bb_methodt bb_statust manufacturedi*(RR"t__of__RR$R%tKeyError(RtrequestR((R t__bobo_traverse__is      cCsdS(s Test MethodN((R((R R$ustscreechy(RRt*__allow_access_to_unprotected_subobjects__R*R$R%(((R R"fs t RestrictedcBs5tZdZeZdZfZdZdZRS(s<Instance we'll check with ProtectedMethodSecurityPolicy cCs|iS(N(Rtid(R((R RscCsdS(Nsprivate!((R((R tprivatescCsdS(Nsohno!((R((R tohnos( RRRRtgetId__roles__Rtprivate__roles__R/R0(((R R-|s   tBoboTraversableWithAcquisitioncBstZdZdZRS(s A BoboTraversable class which may use acquisition to find objects. This is similar to how the __bobo_traverse__ added by Five behaves). cCsti||S(N(t Acquisitiontaq_getRR(RR)R((R R*s(RRRR*(((R R3s cCs<dk}dkl}|ddd>}|i|iS(N(s DemoStoragetquotaii(tZODBtZODB.DemoStoraget DemoStoragetstDBtopen(R:R7R9((R tmakeConnections  t TestTraversecBstZdZdZdZdZdZdZdZdZ d Z d Z d Z d Z d ZdZdZdZdZdZdZdZRS(NcCsLt|_y|ii}t}||d<||_ti}|_ t |id||_ t |i dt |i d}hdd<dd<dd s( &         t__main__is framework.pyt SimpleClasscBstZdZRS(s Class with no __bobo_traverse__.(RRR(((R Rs cCsdS(s Test the behaviour of unrestrictedTraverse and views. The tests are copies from Five.browser.tests.test_traversable, but instead of publishing they do unrestrictedTraverse. >>> import Products.Five >>> from Products.Five import zcml >>> zcml.load_config("configure.zcml", Products.Five) >>> from Testing.makerequest import makerequest >>> self.app = makerequest(self.app) ``SimpleContent`` is a traversable class by default. Its fallback traverser should raise NotFound when traversal fails. (Note: If we return None in __fallback_traverse__, this test passes but for the wrong reason: None doesn't have a docstring so BaseRequest raises NotFoundError.) >>> from Products.Five.tests.testing.simplecontent import manage_addSimpleContent >>> manage_addSimpleContent(self.folder, 'testoid', 'Testoid') >>> from zExceptions import NotFound >>> try: ... self.folder.testoid.unrestrictedTraverse('doesntexist') ... except NotFound: ... pass Now let's take class which already has a __bobo_traverse__ method. Five should correctly use that as a fallback. >>> configure_zcml = ''' ... ... ... ... ... ... ... ... ... ... ... ''' >>> zcml.load_string(configure_zcml) >>> from Products.Five.tests.testing.fancycontent import manage_addFancyContent >>> info = manage_addFancyContent(self.folder, 'fancy', '') In the following test we let the original __bobo_traverse__ method kick in: >>> self.folder.fancy.unrestrictedTraverse('something-else').index_html({}) 'something-else' Once we have a custom __bobo_traverse__ method, though, it always takes over. Therefore, unless it raises AttributeError or KeyError, it will be the only way traversal is done. >>> self.folder.fancy.unrestrictedTraverse('fancyview').index_html({}) 'fancyview' Note that during publishing, if the original __bobo_traverse__ method *does* raise AttributeError or KeyError, we can get normal view look-up. In unrestrictedTraverse, we don't. Maybe we should? Needs discussing. >>> self.folder.fancy.unrestrictedTraverse('raise-attributeerror')() u'Fancy, fancy' >>> self.folder.fancy.unrestrictedTraverse('raise-keyerror')() u'Fancy, fancy' >>> try: ... self.folder.fancy.unrestrictedTraverse('raise-valueerror') ... except ValueError: ... pass In the Zope 2 ZPublisher, an object with a __bobo_traverse__ will not do attribute lookup unless the __bobo_traverse__ method itself does it (i.e. the __bobo_traverse__ is the only element used for traversal lookup). Let's demonstrate: >>> from Products.Five.tests.testing.fancycontent import manage_addNonTraversableFancyContent >>> info = manage_addNonTraversableFancyContent(self.folder, 'fancy_zope2', '') >>> self.folder.fancy_zope2.an_attribute = 'This is an attribute' >>> self.folder.fancy_zope2.unrestrictedTraverse('an_attribute').index_html({}) 'an_attribute' Without a __bobo_traverse__ method this would have returned the attribute value 'This is an attribute'. Let's make sure the same thing happens for an object that has been marked traversable by Five: >>> self.folder.fancy.an_attribute = 'This is an attribute' >>> self.folder.fancy.unrestrictedTraverse('an_attribute').index_html({}) 'an_attribute' Clean up: >>> from zope.app.testing.placelesssetup import tearDown >>> tearDown() Verify that after cleanup, there's no cruft left from five:traversable:: >>> from Products.Five.browser.tests.test_traversable import SimpleClass >>> hasattr(SimpleClass, '__bobo_traverse__') False >>> hasattr(SimpleClass, '__fallback_traverse__') False >>> from Products.Five.tests.testing.fancycontent import FancyContent >>> hasattr(FancyContent, '__bobo_traverse__') True >>> hasattr(FancyContent.__bobo_traverse__, '__five_method__') False >>> hasattr(FancyContent, '__fallback_traverse__') False N((((R ttest_traversablescCsdS(s Test that views don't shadow attributes, e.g. items in a folder. Let's first define a browser page for object managers called ``eagle``: >>> configure_zcml = ''' ... ... ... ... ... ... ''' >>> import Products.Five >>> from Products.Five import zcml >>> zcml.load_config("configure.zcml", Products.Five) >>> zcml.load_string(configure_zcml) Then we create a traversable folder... >>> from Products.Five.tests.testing.folder import manage_addFiveTraversableFolder >>> manage_addFiveTraversableFolder(self.folder, 'ftf') and add an object called ``eagle`` to it: >>> from Products.Five.tests.testing.simplecontent import manage_addIndexSimpleContent >>> manage_addIndexSimpleContent(self.folder.ftf, 'eagle', 'Eagle') When we publish the ``ftf/eagle`` now, we expect the attribute to take precedence over the view during traversal: >>> self.folder.ftf.unrestrictedTraverse('eagle').index_html({}) 'Default index_html called' Of course, unless we explicitly want to lookup the view using @@: >>> self.folder.ftf.unrestrictedTraverse('@@eagle')() u'The eagle has landed' Some weird implementations of __bobo_traverse__, like the one found in OFS.Application, raise NotFound. Five still knows how to deal with this, hence views work there too: >>> self.app.unrestrictedTraverse('@@eagle')() u'The eagle has landed' However, acquired attributes *should* be shadowed. See discussion on http://codespeak.net/pipermail/z3-five/2006q2/001474.html >>> manage_addIndexSimpleContent(self.folder, 'mouse', 'Mouse') >>> self.folder.ftf.unrestrictedTraverse('mouse')() u'The mouse has been eaten by the eagle' Head requests have some unusual behavior in Zope 2, in particular, a failed item lookup on an ObjectManager returns a NullResource, rather than raising a KeyError. We need to make sure that this doesn't result in acquired attributes being shadowed by the NullResource, but that unknown names still give NullResources: >>> self.app.REQUEST.maybe_webdav_client = True >>> self.app.REQUEST['REQUEST_METHOD'] = 'HEAD' >>> self.folder.ftf.unrestrictedTraverse('mouse')() u'The mouse has been eaten by the eagle' >>> self.folder.ftf.unrestrictedTraverse('nonsense') Clean up: >>> from zope.app.testing.placelesssetup import tearDown >>> tearDown() N((((R t!test_view_doesnt_shadow_attributesXcCsCti}|ititdkl}|i||S(N(sFunctionalDocTestSuite(tunittestt TestSuitetsuitetaddTestt makeSuiteR>tTesting.ZopeTestCasetFunctionalDocTestSuite(RR((R t test_suitels   t defaultTestR(0RRRLRSR7R4t AccessControlRXRtAccessControl.PermissionsRt AccessControl.SecurityManagementR[R]RtOFS.ApplicationR?t OFS.FolderRQt OFS.ImageRDtOFS.SimpleItemt SimpleItemtTesting.makerequestRORRRtImplicitRR"R-R3R=tTestCaseR>tostsysRtexecfiletpathtjoinR RRRRtmain(RLRXR]RRR?R3RQRR>RRRR-RDRR"RRR[RRRR7R4RSR=RORRR((R t?s@                $ Z