############################################################################## # # Copyright (c) 2003 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Security proxy tests $Id$ """ import unittest from zope.security.proxy import getChecker, ProxyFactory, removeSecurityProxy from zope.proxy import ProxyBase as proxy class Checker(object): ok = 1 unproxied_types = str, def check_getattr(self, object, name): if name not in ("foo", "next", "__class__", "__name__", "__module__"): raise RuntimeError def check_setattr(self, object, name): if name != "foo": raise RuntimeError def check(self, object, opname): if not self.ok: raise RuntimeError def proxy(self, value): if type(value) in self.unproxied_types: return value return ProxyFactory(value, self) class Something: def __init__(self): self.foo = [1,2,3] def __getitem__(self, key): return self.foo[key] def __setitem__(self, key, value): self.foo[key] = value def __delitem__(self, key): del self.foo[key] def __call__(self, arg): return 42 def __eq__(self, other): return self is other def __hash__(self): return 42 def __iter__(self): return self def next(self): return 42 # Infinite sequence def __len__(self): return 42 def __nonzero__(self): return 1 def __getslice__(self, i, j): return [42] def __setslice__(self, i, j, value): if value != [42]: raise ValueError def __contains__(self, x): return x == 42 class ProxyTests(unittest.TestCase): def setUp(self): self.x = Something() self.c = Checker() self.p = ProxyFactory(self.x, self.c) def shouldFail(self, *args): self.c.ok = 0 self.assertRaises(RuntimeError, *args) self.c.ok = 1 def testDerivation(self): self.assert_(isinstance(self.p, proxy)) def testStr(self): self.assertEqual(str(self.p), str(self.x)) x = Something() c = Checker() c.ok = 0 p = ProxyFactory(x, c) s = str(p) self.failUnless(s.startswith( ">y", "x&y", "x|y", "x^y", ] def test_binops(self): P = self.c.proxy for expr in self.binops: first = 1 for x in [1, P(1)]: for y in [2, P(2)]: if first: z = eval(expr) first = 0 else: self.assertEqual(removeSecurityProxy(eval(expr)), z, "x=%r; y=%r; expr=%r" % (x, y, expr)) self.shouldFail(lambda x, y: eval(expr), x, y) def test_inplace(self): # TODO: should test all inplace operators... P = self.c.proxy pa = P(1) pa += 2 self.assertEqual(removeSecurityProxy(pa), 3) a = [1, 2, 3] pa = qa = P(a) pa += [4, 5, 6] self.failUnless(pa is qa) self.assertEqual(a, [1, 2, 3, 4, 5, 6]) def doit(): pa = P(1) pa += 2 self.shouldFail(doit) pa = P(2) pa **= 2 self.assertEqual(removeSecurityProxy(pa), 4) def doit(): pa = P(2) pa **= 2 self.shouldFail(doit) def test_coerce(self): P = self.c.proxy # Before 2.3, coerce() of two proxies returns them unchanged import sys fixed_coerce = sys.version_info >= (2, 3, 0) x = P(1) y = P(2) a, b = coerce(x, y) self.failUnless(a is x and b is y) x = P(1) y = P(2.1) a, b = coerce(x, y) self.failUnless(removeSecurityProxy(a) == 1.0 and b is y) if fixed_coerce: self.failUnless(type(removeSecurityProxy(a)) is float and b is y) x = P(1.1) y = P(2) a, b = coerce(x, y) self.failUnless(a is x and removeSecurityProxy(b) == 2.0) if fixed_coerce: self.failUnless(a is x and type(removeSecurityProxy(b)) is float) x = P(1) y = 2 a, b = coerce(x, y) self.failUnless(a is x and b is y) x = P(1) y = 2.1 a, b = coerce(x, y) self.failUnless(type(removeSecurityProxy(a)) is float and b is y) x = P(1.1) y = 2 a, b = coerce(x, y) self.failUnless(a is x and type(removeSecurityProxy(b)) is float) x = 1 y = P(2) a, b = coerce(x, y) self.failUnless(a is x and b is y) x = 1.1 y = P(2) a, b = coerce(x, y) self.failUnless(a is x and type(removeSecurityProxy(b)) is float) x = 1 y = P(2.1) a, b = coerce(x, y) self.failUnless(type(removeSecurityProxy(a)) is float and b is y) def test_using_mapping_slots_hack(): """The security proxy will use mapping slots, on the checker to go faster If a checker implements normally, a checkers's check and check_getattr methods are used to check operator and attribute access: >>> class Checker(object): ... def check(self, object, name): ... print 'check', name ... def check_getattr(self, object, name): ... print 'check_getattr', name ... def proxy(self, object): ... return 1 >>> def f(): ... pass >>> p = ProxyFactory(f, Checker()) >>> p.__name__ check_getattr __name__ 1 >>> p() check __call__ 1 But, if the checker has a __setitem__ method: >>> def __setitem__(self, object, name): ... print '__setitem__', name >>> Checker.__setitem__ = __setitem__ It will be used rather than either check or check_getattr: >>> p.__name__ __setitem__ __name__ 1 >>> p() __setitem__ __call__ 1 If a checker has a __getitem__ method: >>> def __getitem__(self, object): ... return 2 >>> Checker.__getitem__ = __getitem__ It will be used rather than it's proxy method: >>> p.__name__ __setitem__ __name__ 2 >>> p() __setitem__ __call__ 2 """ def test_suite(): suite = unittest.makeSuite(ProxyTests) from doctest import DocTestSuite suite.addTest(DocTestSuite()) suite.addTest(DocTestSuite('zope.security.proxy')) return suite if __name__=='__main__': from unittest import main main(defaultTest='test_suite')