Back to index

plone3  3.1.7
testSecurityDeclarations.py
Go to the documentation of this file.
00001 #
00002 # Tests the security declarations Plone makes on resources
00003 # for access by restricted code (aka PythonScripts)
00004 #
00005 
00006 from Testing import ZopeTestCase
00007 from Products.CMFPlone.tests import PloneTestCase
00008 from Products.CMFPlone.tests import dummy
00009 
00010 from AccessControl import Unauthorized
00011 from ZODB.POSException import ConflictError
00012 from Products.ZCTextIndex.ParseTree import ParseError
00013 from OFS.CopySupport import CopyError
00014 from Products.CMFDefault.DiscussionTool import DiscussionNotAllowed
00015 from Products.CMFPlone.PloneTool import AllowSendto
00016 
00017 
00018 class RestrictedPythonTest(ZopeTestCase.ZopeTestCase):
00019 
00020     def addPS(self, id, params='', body=''):
00021         factory = self.folder.manage_addProduct['PythonScripts']
00022         factory.manage_addPythonScript(id)
00023         self.folder[id].ZPythonScript_edit(params, body)
00024 
00025     def check(self, psbody):
00026         self.addPS('ps', body=psbody)
00027         try:
00028             self.folder.ps()
00029         except (ImportError, Unauthorized), e:
00030             self.fail(e)
00031 
00032     def checkUnauthorized(self, psbody):
00033         self.addPS('ps', body=psbody)
00034         try:
00035             self.folder.ps()
00036         except (AttributeError, ImportError, Unauthorized):
00037             pass
00038 
00039 
00040 class TestSecurityDeclarations(RestrictedPythonTest):
00041 
00042     # NOTE: This test case is a bit hairy. Security declarations
00043     # "stick" once a module has been imported into the restricted
00044     # environment. Therefore the tests are not truly independent.
00045     # Be careful when adding new tests to this class.
00046 
00047     def testImport_log(self):
00048         self.check('from Products.CMFPlone.utils import log')
00049 
00050     def testAccess_log(self):
00051         self.check('from Products.CMFPlone import utils;'
00052                    'print utils.log')
00053 
00054     def testImport_log_deprecated(self):
00055         self.check('from Products.CMFPlone.utils import log_deprecated')
00056 
00057     def testAccess_log_deprecated(self):
00058         self.check('from Products.CMFPlone import utils;'
00059                    'print utils.log_deprecated')
00060 
00061     def testImport_log_exc(self):
00062         self.check('from Products.CMFPlone.utils import log_exc')
00063 
00064     def testAccess_log_exc(self):
00065         self.check('from Products.CMFPlone import utils;'
00066                    'print utils.log_exc')
00067 
00068     def testImport_getLogger(self):
00069         self.check('from logging import getLogger')
00070 
00071     def testAccess_getLogger(self):
00072         self.check('from logging import getLogger;'
00073                    'log = getLogger("testlog");'
00074                    'log.debug("test")')
00075 
00076     def testImport_IndexIterator(self):
00077         self.check('from Products.CMFPlone import IndexIterator')
00078 
00079     def testAccess_IndexIterator(self):
00080         self.check('from Products import CMFPlone;'
00081                    'print CMFPlone.IndexIterator')
00082 
00083     def testUse_IndexIterator(self):
00084         self.check('from Products.CMFPlone import IndexIterator;'
00085                    'print IndexIterator().next')
00086 
00087     def testImport_ObjectMoved(self):
00088         self.check('from Products.CMFCore.WorkflowCore import ObjectMoved')
00089 
00090     def testAccess_ObjectMoved(self):
00091         self.check('from Products.CMFCore import WorkflowCore;'
00092                    'print WorkflowCore.ObjectMoved')
00093 
00094     def testUse_ObjectMoved(self):
00095         self.check('from Products.CMFCore.WorkflowCore import ObjectMoved;'
00096                    'print ObjectMoved("foo").getResult')
00097 
00098     def testImport_ObjectDeleted(self):
00099         self.check('from Products.CMFCore.WorkflowCore import ObjectDeleted')
00100 
00101     def testAccess_ObjectDeleted(self):
00102         self.check('from Products.CMFCore import WorkflowCore;'
00103                    'print WorkflowCore.ObjectDeleted')
00104 
00105     def testUse_ObjectDeleted(self):
00106         self.check('from Products.CMFCore.WorkflowCore import ObjectDeleted;'
00107                    'print ObjectDeleted().getResult')
00108 
00109     def testImport_WorkflowException(self):
00110         self.check('from Products.CMFCore.WorkflowCore import WorkflowException')
00111 
00112     def testAccess_WorkflowException(self):
00113         self.check('from Products.CMFCore import WorkflowCore;'
00114                    'print WorkflowCore.WorkflowException')
00115 
00116     def testUse_WorkflowException(self):
00117         self.check('from Products.CMFCore.WorkflowCore import WorkflowException;'
00118                    'print WorkflowException().args')
00119 
00120     def testImport_Batch(self):
00121         self.check('from Products.CMFPlone import Batch')
00122 
00123     def testAccess_Batch(self):
00124         self.check('from Products import CMFPlone;'
00125                    'print CMFPlone.Batch')
00126 
00127     def testUse_Batch(self):
00128         self.check('from Products.CMFPlone import Batch;'
00129                    'print Batch([], 0).nexturls')
00130 
00131     # utils
00132 
00133     def testImport_transaction_note(self):
00134         self.check('from Products.CMFPlone.utils import transaction_note')
00135 
00136     def testAccess_transaction_note(self):
00137         self.check('import Products.CMFPlone.utils;'
00138                    'print Products.CMFPlone.utils.transaction_note')
00139 
00140     def testImport_base_hasattr(self):
00141         self.check('from Products.CMFPlone.utils import base_hasattr')
00142 
00143     def testAccess_base_hasattr(self):
00144         self.check('import Products.CMFPlone.utils;'
00145                    'print Products.CMFPlone.utils.base_hasattr')
00146 
00147     def testImport_safe_hasattr(self):
00148         self.check('from Products.CMFPlone.utils import safe_hasattr')
00149 
00150     def testAccess_safe_hasattr(self):
00151         self.check('import Products.CMFPlone.utils;'
00152                    'print Products.CMFPlone.utils.safe_hasattr')
00153 
00154     def testImport_safe_callable(self):
00155         self.check('from Products.CMFPlone.utils import safe_callable')
00156 
00157     def testAccess_safe_callable(self):
00158         self.check('import Products.CMFPlone.utils;'
00159                    'print Products.CMFPlone.utils.safe_callable')
00160 
00161     # Exceptions
00162 
00163     def testImport_Unauthorized(self):
00164         self.check('from AccessControl import Unauthorized')
00165 
00166     def testAccess_Unauthorized(self):
00167         self.check('import AccessControl;'
00168                    'print AccessControl.Unauthorized')
00169 
00170     def testImport_zExceptionsUnauthorized(self):
00171         # TODO: Note that this is not allowed
00172         self.checkUnauthorized('from zExceptions import Unauthorized')
00173 
00174     def testImport_ConflictError(self):
00175         self.check('from ZODB.POSException import ConflictError')
00176 
00177     def testAccess_ConflictError(self):
00178         self.check('import ZODB.POSException;'
00179                    'print ZODB.POSException.ConflictError')
00180 
00181     def testRaise_ConflictError(self):
00182         self.assertRaises(ConflictError,
00183             self.check, 'from ZODB.POSException import ConflictError;'
00184                         'raise ConflictError')
00185 
00186     def testCatch_ConflictErrorRaisedByRestrictedCode(self):
00187         try:
00188             self.check('''
00189 from ZODB.POSException import ConflictError
00190 try: raise ConflictError
00191 except ConflictError: pass
00192 ''')
00193         except Exception, e:
00194             self.fail('Failed to catch: %s %s (module %s)' %
00195                       (e.__class__.__name__, e, e.__module__))
00196 
00197     def testCatch_ConflictErrorRaisedByPythonModule(self):
00198         self.folder._setObject('raiseConflictError', dummy.Raiser(ConflictError))
00199         try:
00200             self.check('''
00201 from ZODB.POSException import ConflictError
00202 try: context.raiseConflictError()
00203 except ConflictError: pass
00204 ''')
00205         except Exception, e:
00206             self.fail('Failed to catch: %s %s (module %s)' %
00207                       (e.__class__.__name__, e, e.__module__))
00208 
00209     def testImport_ParseError(self):
00210         self.check('from Products.ZCTextIndex.ParseTree import ParseError')
00211 
00212     def testAccess_ParseError(self):
00213         self.check('import Products.ZCTextIndex.ParseTree;'
00214                    'print Products.ZCTextIndex.ParseTree.ParseError')
00215 
00216     def testCatch_ParseErrorRaisedByPythonModule(self):
00217         self.folder._setObject('raiseParseError', dummy.Raiser(ParseError))
00218         try:
00219             self.check('''
00220 from Products.ZCTextIndex.ParseTree import ParseError
00221 try: context.raiseParseError()
00222 except ParseError: pass
00223 ''')
00224         except Exception, e:
00225             self.fail('Failed to catch: %s %s (module %s)' %
00226                       (e.__class__.__name__, e, e.__module__))
00227 
00228     from DateTime.DateTime import DateTimeError
00229 
00230     def testImport_DateTimeError(self):
00231         self.check('from DateTime.DateTime import DateTimeError')
00232 
00233     def testAccess_DateTimeError(self):
00234         self.check('import DateTime.DateTime;'
00235                    'print DateTime.DateTime.DateTimeError')
00236 
00237     def testCatch_DateTimeErrorRaisedByPythonModule(self):
00238         self.folder._setObject('raiseDateTimeError', dummy.Raiser(self.DateTimeError))
00239         try:
00240             self.check('''
00241 from DateTime.DateTime import DateTimeError
00242 try: context.raiseDateTimeError()
00243 except DateTimeError: pass
00244 ''')
00245         except Exception, e:
00246             self.fail('Failed to catch: %s %s (module %s)' %
00247                       (e.__class__.__name__, e, e.__module__))
00248 
00249     from DateTime.DateTime import SyntaxError
00250 
00251     def testImport_SyntaxError(self):
00252         self.check('from DateTime.DateTime import SyntaxError')
00253 
00254     def testAccess_SyntaxError(self):
00255         self.check('import DateTime.DateTime;'
00256                    'print DateTime.DateTime.SyntaxError')
00257 
00258     def testCatch_SyntaxErrorRaisedByPythonModule(self):
00259         self.folder._setObject('raiseSyntaxError', dummy.Raiser(self.SyntaxError))
00260         try:
00261             self.check('''
00262 from DateTime.DateTime import SyntaxError
00263 try: context.raiseSyntaxError()
00264 except SyntaxError: pass
00265 ''')
00266         except Exception, e:
00267             self.fail('Failed to catch: %s %s (module %s)' %
00268                       (e.__class__.__name__, e, e.__module__))
00269 
00270     def testImport_CopyError(self):
00271         self.check('from OFS.CopySupport import CopyError')
00272 
00273     def testAccess_CopyError(self):
00274         self.check('import OFS.CopySupport;'
00275                    'print OFS.CopySupport.CopyError')
00276 
00277     def testCatch_CopyErrorRaisedByPythonModule(self):
00278         self.folder._setObject('raiseCopyError', dummy.Raiser(CopyError))
00279         try:
00280             self.check('''
00281 from OFS.CopySupport import CopyError
00282 try: context.raiseCopyError()
00283 except CopyError: pass
00284 ''')
00285         except Exception, e:
00286             self.fail('Failed to catch: %s %s (module %s)' %
00287                       (e.__class__.__name__, e, e.__module__))
00288 
00289     def testImport_DiscussionNotAllowed(self):
00290         self.check('from Products.CMFDefault.DiscussionTool '
00291                    'import DiscussionNotAllowed')
00292 
00293     def testAccess_DiscussionNotAllowed(self):
00294         self.check('import Products.CMFDefault.DiscussionTool;'
00295                    'print Products.CMFDefault.DiscussionTool.DiscussionNotAllowed')
00296 
00297     def testCatch_DiscussionNotAllowedRaisedByPythonModule(self):
00298         self.folder._setObject('raiseDiscussionNotAllowed',
00299                                dummy.Raiser(DiscussionNotAllowed))
00300         try:
00301             self.check('''
00302 from Products.CMFDefault.DiscussionTool import DiscussionNotAllowed
00303 try: context.raiseDiscussionNotAllowed()
00304 except DiscussionNotAllowed: pass
00305 ''')
00306         except Exception, e:
00307             self.fail('Failed to catch: %s %s (module %s)' %
00308                       (e.__class__.__name__, e, e.__module__))
00309 
00310     # isRTL
00311 
00312     try:
00313         from Products import PlacelessTranslationService
00314     except ImportError:
00315         pass
00316     else:
00317         def testImport_PTS(self):
00318             self.check('from Products import PlacelessTranslationService')
00319 
00320         def testImport_isRTL(self):
00321             self.check('from Products.PlacelessTranslationService import isRTL')
00322 
00323         def testAccess_isRTL(self):
00324             self.check('import Products.PlacelessTranslationService;'
00325                        'print Products.PlacelessTranslationService.isRTL')
00326 
00327     # getToolByName
00328 
00329     def testImport_getToolByName(self):
00330         self.check('from Products.CMFCore.utils import getToolByName')
00331 
00332     def testAccess_getToolByName(self):
00333         # TODO: Note that this is NOT allowed!
00334         self.checkUnauthorized('from Products.CMFCore import utils;'
00335                                'print utils.getToolByName')
00336 
00337     def testUse_getToolByName(self):
00338         self.app.manage_addFolder('portal_membership') # Fake a portal tool
00339         self.check('from Products.CMFCore.utils import getToolByName;'
00340                    'print getToolByName(context, "portal_membership")')
00341 
00342     # transaction
00343 
00344     def testImport_transaction(self):
00345         self.checkUnauthorized('import transaction')
00346 
00347     def testUse_transaction(self):
00348         self.checkUnauthorized('import transaction;'
00349                                'transaction.get()')
00350 
00351     # allow sendto
00352 
00353     def testImport_AllowSendto(self):
00354         self.check('from Products.CMFPlone.PloneTool import AllowSendto')
00355 
00356     def testAccess_AllowSendto(self):
00357         # TODO: Note that this is NOT allowed!
00358         self.checkUnauthorized('from Products.CMFPlone import PloneTool;'
00359                                'print PloneTool.AllowSendto')
00360 
00361     # ZCatalog
00362 
00363     def testImport_mergeResults(self):
00364         self.check('from Products.ZCatalog.Catalog import mergeResults')
00365 
00366 
00367 
00368 class TestAcquisitionMethods(RestrictedPythonTest):
00369 
00370     def test_aq_explicit(self):
00371         self.check('print context.aq_explicit')
00372 
00373     def test_aq_parent(self):
00374         self.check('print context.aq_parent')
00375 
00376     def test_aq_inner(self):
00377         self.check('print context.aq_inner')
00378 
00379     def test_aq_inner_aq_parent(self):
00380         self.check('print context.aq_inner.aq_parent')
00381 
00382     def test_aq_self(self):
00383         self.checkUnauthorized('print context.aq_self')
00384 
00385     def test_aq_base(self):
00386         self.checkUnauthorized('print context.aq_base')
00387 
00388     def test_aq_acquire(self):
00389         self.checkUnauthorized('print context.aq_acquire')
00390 
00391 
00392 class TestAllowSendtoSecurity(PloneTestCase.PloneTestCase):
00393 
00394     def test_AllowSendto(self):
00395         portal = self.portal
00396         mtool = self.portal.portal_membership
00397         checkPermission = mtool.checkPermission
00398 
00399         # should be allowed as Member
00400         self.failUnless(checkPermission(AllowSendto, portal))
00401         # should be allowed as Manager
00402         self.setRoles(['Manager'])
00403         self.failUnless(checkPermission(AllowSendto, portal))
00404         # should be allowed as anonymous
00405         self.logout()
00406         self.failUnless(checkPermission(AllowSendto, portal))
00407 
00408     def test_allowsendto_changed(self):
00409         mtool = self.portal.portal_membership
00410         checkPermission = mtool.checkPermission
00411 
00412         self.setRoles(['Manager'])
00413         self.portal.manage_permission(AllowSendto, roles=('Manager',),
00414                                       acquire=False)
00415         self.setRoles(['Member'])
00416 
00417         self.failIf(checkPermission(AllowSendto, self.portal))
00418 
00419     def test_sendto_script_failes(self):
00420         # set permission to Manager only
00421         self.setRoles(['Manager'])
00422         self.portal.manage_permission(AllowSendto, roles=('Manager',),
00423                                       acquire=False)
00424         self.setRoles(['Member'])
00425         # get sendto script in context of folder
00426         sendto = self.folder.sendto
00427         # should faile with the not allowed msg check if the msg
00428         # contains the string
00429         msg = sendto()
00430         errormsg = "You%20are%20not%20allowed%20to%20send%20this%20link"
00431         self.failIf(str(msg).find(errormsg) != -1, str(msg))
00432 
00433 
00434 class TestSkinSecurity(PloneTestCase.PloneTestCase):
00435 
00436     def test_OwnerCanViewConstrainTypesForm(self):
00437         try:
00438             self.folder.restrictedTraverse('folder_constraintypes_form')
00439         except Unauthorized:
00440             self.fail("Owner could not access folder_constraintypes_form")
00441 
00442 
00443 class TestNavtreeSecurity(PloneTestCase.PloneTestCase, RestrictedPythonTest):
00444 
00445     def testNavtreeStrategyBase(self):
00446         self.check('from Products.CMFPlone.browser.navtree import NavtreeStrategyBase;'
00447                     'n=NavtreeStrategyBase();'
00448                     'n.nodeFilter({});'
00449                     'n.subtreeFilter({});'
00450                     'n.decoratorFactory({});')
00451 
00452     def testNavtreeStrategyBase(self):
00453         self.check('from Products.CMFPlone.browser.navtree import NavtreeStrategyBase;'
00454                     'n=NavtreeStrategyBase();'
00455                     'n.nodeFilter({});'
00456                     'n.subtreeFilter({});'
00457                     'n.decoratorFactory({});')
00458 
00459     def testSitemapNavtreeStrategy(self):
00460         # We don't test the decorator factory because that requres an
00461         # actual brain in item
00462         self.check('from Products.CMFPlone.browser.navtree import SitemapNavtreeStrategy;'
00463                     'n=SitemapNavtreeStrategy(context);'
00464                     'n.nodeFilter({"item":1});'
00465                     'n.subtreeFilter({"item":1});')
00466 
00467     def testDefaultNavtreeStrategy(self):
00468         # We don't test the decorator factory because that requres an
00469         # actual brain in item
00470         self.check('from Products.CMFPlone.browser.navtree import DefaultNavtreeStrategy;'
00471                     'n=DefaultNavtreeStrategy(context);'
00472                     'n.nodeFilter({"item":1});'
00473                     'n.subtreeFilter({"item":1});')
00474 
00475     def testNavtreeQueryBuilder(self):
00476         self.check('from Products.CMFPlone.browser.navtree import NavtreeQueryBuilder;'
00477                     'n=NavtreeQueryBuilder(context);'
00478                     'n();')
00479 
00480     def testSitemapQueryBuilder(self):
00481         self.check('from Products.CMFPlone.browser.navtree import SitemapQueryBuilder;'
00482                     'n=SitemapQueryBuilder(context);'
00483                     'n();')
00484 
00485     def testGetNavigationRoot(self):
00486         self.check('from Products.CMFPlone.browser.navtree import getNavigationRoot')
00487 
00488     def testBuildFolderTree(self):
00489         self.check('from Products.CMFPlone.browser.navtree import buildFolderTree')
00490 
00491 def test_suite():
00492     from unittest import TestSuite, makeSuite
00493     suite = TestSuite()
00494     suite.addTest(makeSuite(TestSecurityDeclarations))
00495     suite.addTest(makeSuite(TestAcquisitionMethods))
00496     suite.addTest(makeSuite(TestAllowSendtoSecurity))
00497     suite.addTest(makeSuite(TestSkinSecurity))
00498     suite.addTest(makeSuite(TestNavtreeSecurity))
00499     return suite