[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