[CMF-checkins] CVS: Products/CMFCore/tests - test_PortalFolder.py:1.35

Tres Seaver tseaver at zope.com
Mon Aug 9 14:47:48 EDT 2004


Update of /cvs-repository/Products/CMFCore/tests
In directory cvs.zope.org:/tmp/cvs-serv4944/CMFCore/tests

Modified Files:
	test_PortalFolder.py 
Log Message:


 - CMFCore.PortalFolder: Enforce check of "Delete objects" permission
   during cut + paste. (http://zope.org/Collectors/259)

   N.B.  This fix depends on an update to the underlying Zope software,
         e.g., Zope 2.7.3 or later.  Two new unit tests fail on
         Zope 2.7.2 and earlier.


=== Products/CMFCore/tests/test_PortalFolder.py 1.34 => 1.35 ===
--- Products/CMFCore/tests/test_PortalFolder.py:1.34	Sat Jul 31 11:57:42 2004
+++ Products/CMFCore/tests/test_PortalFolder.py	Mon Aug  9 14:47:48 2004
@@ -2,18 +2,26 @@
 import Testing
 import Zope
 Zope.startup()
+  
+import cStringIO
 
+from AccessControl import SecurityManager
+from Acquisition import Implicit
+from Acquisition import aq_base
 from DateTime import DateTime
 from webdav.WriteLockInterface import WriteLockInterface
+from OFS.Application import Application
+from OFS.Image import manage_addFile
+from OFS.tests.testCopySupport import makeConnection
+from Testing.makerequest import makerequest
 
 from Products.CMFCore.CatalogTool import CatalogTool
 from Products.CMFCore.interfaces.Dynamic import DynamicType as IDynamicType
-from Products.CMFCore.PortalFolder import ContentFilter
-from Products.CMFCore.PortalFolder import PortalFolder
 from Products.CMFCore.tests.base.dummy import DummyContent
 from Products.CMFCore.tests.base.dummy import DummyFactory
 from Products.CMFCore.tests.base.security import OmnipotentUser
 from Products.CMFCore.tests.base.testcase import newSecurityManager
+from Products.CMFCore.tests.base.testcase import noSecurityManager
 from Products.CMFCore.tests.base.testcase import SecurityTest
 from Products.CMFCore.tests.base.tidata import FTIDATA_DUMMY
 from Products.CMFCore.tests.base.utils import has_path
@@ -29,6 +37,7 @@
 class PortalFolderFactoryTests( SecurityTest ):
 
     def setUp( self ):
+        from Products.CMFCore.PortalFolder import PortalFolder
         SecurityTest.setUp( self )
 
         self.root._setObject( 'portal_types', TypesTool() )
@@ -46,6 +55,7 @@
         types_tool._setObject( 'Dummy Content', FTI(**fti) )
 
     def _makeOne( self, id ):
+        from Products.CMFCore.PortalFolder import PortalFolder
         return PortalFolder( id ).__of__( self.root )
 
     def test_invokeFactory( self ):
@@ -85,6 +95,7 @@
 class PortalFolderTests( SecurityTest ):
 
     def setUp( self ):
+        from Products.CMFCore.PortalFolder import PortalFolder
         SecurityTest.setUp(self)
 
         root = self.root
@@ -157,6 +168,8 @@
         #   is not being uncatalogued.  Try creating a subfolder with
         #   content object, and test.
         #
+        from Products.CMFCore.PortalFolder import PortalFolder
+
         test = self.root.test
 
         self.root._setObject( 'portal_types', TypesTool() )
@@ -190,6 +203,8 @@
         #
         #   Does the catalog stay synched when folders are moved?
         #
+        from Products.CMFCore.PortalFolder import PortalFolder
+
         test = self.root.test
 
         self.root._setObject( 'portal_types', TypesTool() )
@@ -244,6 +259,8 @@
         #
         #   Does MKDIR/MKCOL intercept work?
         #
+        from Products.CMFCore.PortalFolder import PortalFolder
+
         test = self.root.test
         test._setPortalTypeName( 'Folder' )
         self.root.reindexObject = lambda: 0
@@ -302,6 +319,8 @@
         #
         #   Does copy / paste work?
         #
+        from Products.CMFCore.PortalFolder import PortalFolder
+
         test = self.root.test
 
         self.root._setObject( 'portal_types', TypesTool() )
@@ -373,6 +392,8 @@
         #
         #   _verifyObjectPaste() should honor allowed content types
         #
+        from Products.CMFCore.PortalFolder import PortalFolder
+
         test = self.root.test
 
         self.root._setObject( 'portal_types', TypesTool() )
@@ -436,6 +457,9 @@
         self.dummy=DummyContent('Dummy')
 
     def test_empty( self ):
+
+        from Products.CMFCore.PortalFolder import ContentFilter
+
         cfilter = ContentFilter()
         dummy = self.dummy
         assert cfilter( dummy )
@@ -444,6 +468,9 @@
         assert not lines
 
     def test_Type( self ):
+
+        from Products.CMFCore.PortalFolder import ContentFilter
+
         cfilter = ContentFilter( Type='foo' )
         dummy = self.dummy
         assert not cfilter( dummy )
@@ -466,6 +493,9 @@
         assert lines[0] == 'Type: Dummy Content Title, something else'
 
     def test_portal_type( self ):
+
+        from Products.CMFCore.PortalFolder import ContentFilter
+
         cfilter = ContentFilter( portal_type='some_pt' )
         dummy = self.dummy
         assert not cfilter( dummy )
@@ -483,6 +513,9 @@
         assert lines[0] == 'Portal Type: some_pt'
 
     def test_Title( self ):
+
+        from Products.CMFCore.PortalFolder import ContentFilter
+
         cfilter = ContentFilter( Title='foo' )
         dummy = self.dummy
         assert not cfilter( dummy )
@@ -498,6 +531,9 @@
         assert lines[0] == 'Title: foo'
 
     def test_Creator( self ):
+
+        from Products.CMFCore.PortalFolder import ContentFilter
+
         cfilter = ContentFilter( Creator='moe' )
         dummy = self.dummy
         self.failIf( cfilter(dummy) )
@@ -513,6 +549,9 @@
         self.assertEqual(lines[0],'Creator: moe')
 
     def test_Description( self ):
+
+        from Products.CMFCore.PortalFolder import ContentFilter
+
         cfilter = ContentFilter( Description='funny' )
         dummy = self.dummy
         assert not cfilter( dummy )
@@ -528,6 +567,9 @@
         assert lines[0] == 'Description: funny'
 
     def test_Subject( self ):
+
+        from Products.CMFCore.PortalFolder import ContentFilter
+
         cfilter = ContentFilter( Subject=('foo',) )
         dummy = self.dummy
         assert not cfilter( dummy )
@@ -544,6 +586,9 @@
 
     def test_Subject2( self ):
         # Now test with mutli-valued
+
+        from Products.CMFCore.PortalFolder import ContentFilter
+
         cfilter = ContentFilter( Subject=('foo', 'bar' ) )
         dummy = self.dummy
         assert not cfilter( dummy )
@@ -561,6 +606,9 @@
         assert lines[0] == 'Subject: foo, bar'
 
     def test_created( self ):
+
+        from Products.CMFCore.PortalFolder import ContentFilter
+
         cfilter = ContentFilter( created=DateTime( '2001/01/01' )
                                , created_usage='range:min' )
         dummy = self.dummy
@@ -577,6 +625,9 @@
         assert lines[0] == 'Created since: 2001/01/01'
 
     def test_created2( self ):
+
+        from Products.CMFCore.PortalFolder import ContentFilter
+
         cfilter = ContentFilter( created=DateTime( '2001/01/01' )
                                , created_usage='range:max' )
 
@@ -594,6 +645,9 @@
         assert lines[0] == 'Created before: 2001/01/01'
 
     def test_modified( self ):
+
+        from Products.CMFCore.PortalFolder import ContentFilter
+
         cfilter = ContentFilter( modified=DateTime( '2001/01/01' )
                                , modified_usage='range:min' )
         dummy = self.dummy
@@ -610,6 +664,9 @@
         assert lines[0] == 'Modified since: 2001/01/01'
 
     def test_modified2( self ):
+
+        from Products.CMFCore.PortalFolder import ContentFilter
+
         cfilter = ContentFilter( modified=DateTime( '2001/01/01' )
                                , modified_usage='range:max' )
         dummy = self.dummy
@@ -626,6 +683,9 @@
         assert lines[0] == 'Modified before: 2001/01/01'
 
     def test_mixed( self ):
+
+        from Products.CMFCore.PortalFolder import ContentFilter
+
         cfilter = ContentFilter( created=DateTime( '2001/01/01' )
                                , created_usage='range:max'
                                , Title='foo'
@@ -657,11 +717,344 @@
         assert 'Title: foo' in lines
 
 
+#------------------------------------------------------------------------------
+#   Tests for security-related CopySupport lifted from the Zope 2.7
+#   / head OFS.tests.testCopySupport (see Collector #259).
+#------------------------------------------------------------------------------
+ADD_IMAGES_AND_FILES = 'Add images and files'
+FILE_META_TYPES = ( { 'name'        : 'File'
+                    , 'action'      : 'manage_addFile'
+                    , 'permission'  : ADD_IMAGES_AND_FILES
+                    }
+                  ,
+                  )
+class _SensitiveSecurityPolicy:
+
+    def __init__( self, validate_lambda, checkPermission_lambda ):
+        self._lambdas = ( validate_lambda, checkPermission_lambda )
+
+    def validate( self, *args, **kw ):
+        return self._lambdas[ 0 ]( *args, **kw )
+
+    def checkPermission( self, *args, **kw ) :
+        return self._lambdas[ 1 ]( *args, **kw )
+
+class _AllowedUser( Implicit ):
+
+    def __init__( self, allowed_lambda ):
+        self._lambdas = ( allowed_lambda, )
+
+    def getId( self ):
+        return 'unit_tester'
+
+    getUserName = getId
+
+    def allowed( self, object, object_roles=None ):
+        return self._lambdas[ 0 ]( object, object_roles )
+
+class PortalFolderCopySupportTests( TestCase ):
+
+    _old_policy = None
+
+    def setUp( self ):
+        self._scrubSecurity()
+
+    def tearDown( self ):
+
+        self._scrubSecurity()
+        self._cleanApp()
+
+    def _initFolders( self ):
+        from Products.CMFCore.PortalFolder import PortalFolder
+
+        self.connection = makeConnection()
+        try:
+            r = self.connection.root()
+            a = Application()
+            r['Application'] = a
+            self.root = a
+            responseOut = self.responseOut = cStringIO.StringIO()
+            self.app = makerequest( self.root, stdout=responseOut )
+            self.app._setObject( 'folder1', PortalFolder( 'folder1' ) )
+            self.app._setObject( 'folder2', PortalFolder( 'folder2' ) )
+            folder1 = getattr( self.app, 'folder1' )
+            folder2 = getattr( self.app, 'folder2' )
+
+            manage_addFile( folder1, 'file'
+                          , file='', 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.
+            get_transaction().commit()
+        except:
+            self.connection.close()
+            raise
+        get_transaction().begin()
+
+        return self.app._getOb( 'folder1' ), self.app._getOb( 'folder2' )
+
+    def _cleanApp( self ):
+
+        get_transaction().abort()
+        self.app._p_jar.sync()
+        self.connection.close()
+        del self.app
+        del self.responseOut
+        del self.root
+        del self.connection
+
+    def _scrubSecurity( self ):
+
+        noSecurityManager()
+
+        if self._old_policy is not None:
+            SecurityManager.setSecurityPolicy( self._old_policy )
+
+    def _assertCopyErrorUnauth( self, callable, *args, **kw ):
+
+        import re
+        from zExceptions import Unauthorized
+        from OFS.CopySupport import CopyError
+
+        ce_regex = kw.get( 'ce_regex' )
+        if ce_regex is not None:
+            del kw[ 'ce_regex' ]
+
+        try:
+            callable( *args, **kw )
+
+        except CopyError, e:
+
+            if ce_regex is not None:
+                
+                pattern = re.compile( ce_regex, re.DOTALL )
+                if pattern.search( e ) is None:
+                    self.fail( "Paste failed; didn't match pattern:\n%s" % e )
+
+            else:
+                self.fail( "Paste failed; no pattern:\n%s" % e )
+
+        except Unauthorized, e:
+            pass
+
+        else:
+            self.fail( "Paste allowed unexpectedly." )
+
+    def _initPolicyAndUser( self    
+                          , a_lambda=None
+                          , v_lambda=None
+                          , c_lambda=None
+                          ):
+        def _promiscuous( *args, **kw ):
+            return 1
+
+        if a_lambda is None:
+            a_lambda = _promiscuous
+
+        if v_lambda is None:
+            v_lambda = _promiscuous
+
+        if c_lambda is None:
+            c_lambda = _promiscuous
+
+        scp = _SensitiveSecurityPolicy( v_lambda, c_lambda )
+        self._old_policy = SecurityManager.setSecurityPolicy( scp )
+
+        newSecurityManager( None
+                          , _AllowedUser( a_lambda ).__of__( self.root ) )
+
+    def test_copy_baseline( self ):
+
+        folder1, folder2 = self._initFolders()
+        folder2.all_meta_types = FILE_META_TYPES
+
+        self._initPolicyAndUser()
+
+        self.failUnless( 'file' in folder1.objectIds() )
+        self.failIf( 'file' in folder2.objectIds() )
+
+        cookie = folder1.manage_copyObjects( ids=( 'file', ) )
+        folder2.manage_pasteObjects( cookie )
+
+        self.failUnless( 'file' in folder1.objectIds() )
+        self.failUnless( 'file' in folder2.objectIds() )
+
+    def test_copy_cant_read_source( self ):
+
+        folder1, folder2 = self._initFolders()
+        folder2.all_meta_types = FILE_META_TYPES
+
+        a_file = folder1._getOb( 'file' )
+
+        def _validate( a, c, n, v, *args, **kw ):
+            return aq_base( v ) is not aq_base( a_file )
+
+        self._initPolicyAndUser( v_lambda=_validate )
+
+        cookie = folder1.manage_copyObjects( ids=( 'file', ) )
+        self._assertCopyErrorUnauth( folder2.manage_pasteObjects
+                                   , cookie
+                                   , ce_regex='Insufficient privileges'
+                                   )
+
+    def test_copy_cant_create_target_metatype_not_supported( self ):
+        
+        from OFS.CopySupport import CopyError
+
+        folder1, folder2 = self._initFolders()
+        folder2.all_meta_types = ()
+
+        self._initPolicyAndUser()
+
+        cookie = folder1.manage_copyObjects( ids=( 'file', ) )
+        self._assertCopyErrorUnauth( folder2.manage_pasteObjects
+                                   , cookie
+                                   , ce_regex='Not Supported'
+                                   )
+
+    def test_move_baseline( self ):
+
+        folder1, folder2 = self._initFolders()
+        folder2.all_meta_types = FILE_META_TYPES
+
+        self.failUnless( 'file' in folder1.objectIds() )
+        self.failIf( 'file' in folder2.objectIds() )
+
+        self._initPolicyAndUser()
+
+        cookie = folder1.manage_cutObjects( ids=( 'file', ) )
+        folder2.manage_pasteObjects( cookie )
+
+        self.failIf( 'file' in folder1.objectIds() )
+        self.failUnless( 'file' in folder2.objectIds() )
+
+    def test_move_cant_read_source( self ):
+        
+        from OFS.CopySupport import CopyError
+
+        folder1, folder2 = self._initFolders()
+        folder2.all_meta_types = FILE_META_TYPES
+
+        a_file = folder1._getOb( 'file' )
+
+        def _validate( a, c, n, v, *args, **kw ):
+            return aq_base( v ) is not aq_base( a_file )
+
+        self._initPolicyAndUser( v_lambda=_validate )
+
+        cookie = folder1.manage_cutObjects( ids=( 'file', ) )
+        self._assertCopyErrorUnauth( folder2.manage_pasteObjects
+                                   , cookie
+                                   , ce_regex='Insufficient privileges'
+                                   )
+
+    def test_move_cant_create_target_metatype_not_supported( self ):
+        
+        from OFS.CopySupport import CopyError
+
+        folder1, folder2 = self._initFolders()
+        folder2.all_meta_types = ()
+
+        self._initPolicyAndUser()
+
+        cookie = folder1.manage_cutObjects( ids=( 'file', ) )
+        self._assertCopyErrorUnauth( folder2.manage_pasteObjects
+                                   , cookie
+                                   , ce_regex='Not Supported'
+                                   )
+
+    def test_move_cant_create_target_metatype_not_allowed( self ):
+        
+        #
+        #   This test can't succeed on Zope's earlier than 2.7.3 because
+        #   of the DWIM'y behavior of 'guarded_getattr', which tries to
+        #   filter #   acquired-but-inaccessible objects, rather than raising
+        #   Unauthorized.
+        #
+        #   If you are running with such a Zope, this test will error out
+        #   with an AttributeError (instead of the expected Unauthorized).
+        #
+        from OFS.CopySupport import CopyError
+
+        folder1, folder2 = self._initFolders()
+        folder2.all_meta_types = FILE_META_TYPES
+
+        def _no_manage_addFile( a, c, n, v, *args, **kw ):
+            return n != 'manage_addFile'
+
+        self._initPolicyAndUser( v_lambda=_no_manage_addFile )
+
+        cookie = folder1.manage_cutObjects( ids=( 'file', ) )
+        self._assertCopyErrorUnauth( folder2.manage_pasteObjects
+                                   , cookie
+                                   , ce_regex='Insufficient Privileges'
+                                             + '.*%s' % ADD_IMAGES_AND_FILES
+                                   )
+
+    def test_move_cant_delete_source( self ):
+        
+        #
+        #   This test fails on Zope's earlier than 2.7.3 because of the
+        #   changes required to 'OFS.CopytSupport.manage_pasteObjects'
+        #   which must pass 'validate_src' of 2 to '_verifyObjectPaste'
+        #   to indicate that the object is being moved, rather than
+        #   simply copied.
+        #
+        #   If you are running with such a Zope, this test will fail,
+        #   because the move (which should raise Unauthorized) will be
+        #   allowed.
+        #
+        from AccessControl.Permissions import delete_objects as DeleteObjects
+        from OFS.CopySupport import CopyError
+        from Products.CMFCore.PortalFolder import PortalFolder
+        from Products.CMFCore.permissions import AddPortalFolders
+
+        folder1, folder2 = self._initFolders()
+        folder1.manage_permission( DeleteObjects, roles=(), acquire=0 )
+
+        folder1._setObject( 'sub', PortalFolder( 'sub' ) )
+        get_transaction().commit() # get a _p_jar for 'sub'
+
+        FOLDER_CTOR = 'manage_addProducts/CMFCore/manage_addPortalFolder'
+        folder2.all_meta_types = ( { 'name'        : 'CMF Core Content'
+                                   , 'action'      : FOLDER_CTOR
+                                   , 'permission'  : AddPortalFolders
+                                   }
+                                 ,
+                                 )
+
+        self.app.portal_types = DummyTypesTool()
+
+        def _no_delete_objects(permission, object, context):
+            return permission != DeleteObjects
+
+        self._initPolicyAndUser( c_lambda=_no_delete_objects )
+
+        cookie = folder1.manage_cutObjects( ids=( 'sub', ) )
+        self._assertCopyErrorUnauth( folder2.manage_pasteObjects
+                                   , cookie
+                                   , ce_regex='Insufficient Privileges'
+                                             + '.*%s' % DeleteObjects
+                                   )
+
+class DummyTypeInfo:
+
+    def allowType( self, portal_type ):
+        return True
+
+class DummyTypesTool( Implicit ):
+
+    def getTypeInfo( self, portal_type ):
+
+        return DummyTypeInfo()
+
 def test_suite():
     return TestSuite((
         makeSuite( PortalFolderFactoryTests ),
         makeSuite( PortalFolderTests ),
         makeSuite( ContentFilterTests ),
+        makeSuite( PortalFolderCopySupportTests ),
         ))
 
 if __name__ == '__main__':



More information about the CMF-checkins mailing list