Skip to content

Commit

Permalink
Fix TypeError handling for ProxyPy.
Browse files Browse the repository at this point in the history
Also add additional tests clarifying how the str-to-repr fallthrough
works.

Fixes #7
  • Loading branch information
jamadden committed Sep 7, 2017
1 parent f2de462 commit e5a571d
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 31 deletions.
5 changes: 5 additions & 0 deletions CHANGES.rst
Expand Up @@ -4,6 +4,11 @@ Changes
4.1.2 (unreleased)
------------------

- Fix `issue 7
<https://github.com/zopefoundation/zope.security/issues/7`_: The
pure-Python proxy didn't propagate ``TypeError`` from ``__repr__``
and ``__str__`` like the C implementation did.

- Fix `issue 27 <https://github.com/zopefoundation/zope.security/issues/27>`_:
iteration of ``zope.interface.providedBy()`` is now allowed by
default on all versions of Python. Previously it only worked on
Expand Down
11 changes: 10 additions & 1 deletion src/zope/security/proxy.py
Expand Up @@ -219,6 +219,11 @@ def __coerce__(self, other):
def __str__(self):
try:
return _check_name(PyProxyBase.__str__)(self)
# The C implementation catches almost all exceptions; the
# exception is a TypeError that's raised when the repr returns
# the wrong type of object.
except TypeError:
raise
except:
# The C implementation catches all exceptions.
wrapped = super(PyProxyBase, self).__getattribute__('_wrapped')
Expand All @@ -229,8 +234,12 @@ def __str__(self):
def __repr__(self):
try:
return _check_name(PyProxyBase.__repr__)(self)
# The C implementation catches almost all exceptions; the
# exception is a TypeError that's raised when the repr returns
# the wrong type of object.
except TypeError:
raise
except:
# The C implementation catches all exceptions.
wrapped = super(PyProxyBase, self).__getattribute__('_wrapped')
return '<security proxied %s.%s instance at %s>' %(
wrapped.__class__.__module__, wrapped.__class__.__name__,
Expand Down
99 changes: 69 additions & 30 deletions src/zope/security/tests/test_proxy.py
Expand Up @@ -19,31 +19,10 @@
from zope.security._compat import PYTHON2, PYPY, PURE_PYTHON

def _skip_if_not_Py2(testfunc):
from functools import update_wrapper
if not PYTHON2:
def dummy(self):
pass
update_wrapper(dummy, testfunc)
return dummy
return testfunc
return unittest.skipUnless(PYTHON2, "Only on Py2")(testfunc)

def _skip_if_Py2(testfunc):
from functools import update_wrapper
if PYTHON2:
def dummy(self):
pass
update_wrapper(dummy, testfunc)
return dummy
return testfunc

def _skip_if_pypy250(testfunc):
from functools import update_wrapper
if PYPY and sys.pypy_version_info[:3] == (2,5,0):
def dummy(self):
pass
update_wrapper(dummy, testfunc)
return dummy
return testfunc
return unittest.skipIf(PYTHON2, "Only on Py3")(testfunc)

class ProxyTestBase(object):

Expand Down Expand Up @@ -168,6 +147,20 @@ def test___str___checker_forbids_str(self):
'<security proxied %s.object '
'instance at %s>' % (_BUILTINS, address))

def test__str__fails_return(self):
from zope.security.interfaces import ForbiddenAttribute
class CustomStr(object):
def __str__(self):
"<CustomStr>" # Docstring, not a return

target = CustomStr()
checker = DummyChecker(ForbiddenAttribute, allowed=('__str__'))
proxy = self._makeOne(target, checker)
with self.assertRaises(TypeError):
str(target)
with self.assertRaises(TypeError):
str(proxy)

def test___repr___checker_allows_str(self):
target = object()
checker = DummyChecker()
Expand All @@ -186,6 +179,59 @@ def test___repr___checker_forbids_str(self):
'<security proxied %s.object '
'instance at %s>' % (_BUILTINS, address))

def test__str__falls_through_to_repr_when_both_allowed(self):
from zope.security.interfaces import ForbiddenAttribute
class CustomRepr(object):
def __repr__(self):
return "<CustomRepr>"

target = CustomRepr()
checker = DummyChecker(ForbiddenAttribute, allowed=("__str__", '__repr__'))
proxy = self._makeOne(target, checker)
self.assertEqual(repr(proxy), "<CustomRepr>")
self.assertEqual(str(target), "<CustomRepr>")
self.assertEqual(str(proxy), str(target))

def test__str__doesnot_fall_through_to_repr_when_str_not_allowed(self):
from zope.security.interfaces import ForbiddenAttribute
class CustomRepr(object):
def __repr__(self):
return "<CustomRepr>"

target = CustomRepr()
checker = DummyChecker(ForbiddenAttribute, allowed=('__repr__'))
proxy = self._makeOne(target, checker)
self.assertEqual(repr(proxy), "<CustomRepr>")
self.assertEqual(str(target), "<CustomRepr>")
self.assertIn("<security proxied zope.security", str(proxy))

def test__str__doesnot_fall_through_to_repr_when_repr_not_allowed(self):
from zope.security.interfaces import ForbiddenAttribute
class CustomRepr(object):
def __repr__(self):
return "<CustomRepr>"

target = CustomRepr()
checker = DummyChecker(ForbiddenAttribute, allowed=('__str__'))
proxy = self._makeOne(target, checker)
self.assertEqual(str(target), "<CustomRepr>")
self.assertEqual(str(proxy), str(target))
self.assertIn("<security proxied zope.security", repr(proxy))

def test__str__falls_through_to_repr_but_repr_fails_return(self):
from zope.security.interfaces import ForbiddenAttribute
class CustomRepr(object):
def __repr__(self):
"<CustomRepr>" # Docstring, not a return

target = CustomRepr()
checker = DummyChecker(ForbiddenAttribute, allowed=('__repr__'))
proxy = self._makeOne(target, checker)
with self.assertRaises(TypeError):
repr(target)
with self.assertRaises(TypeError):
repr(proxy)

@_skip_if_not_Py2
def test___cmp___w_self(self):
target = object()
Expand Down Expand Up @@ -1713,12 +1759,6 @@ def testGetChecker(self):
self.assertEqual(self.c, getChecker(self.p))


# XXX: PyPy 2.5.0 has a bug where proxys around types
# aren't correctly hashable, which breaks this part of the
# test. This is fixed in 2.5.1+, but as of 2015-05-28,
# TravisCI still uses 2.5.0.

@_skip_if_pypy250
def testProxiedClassicClassAsDictKey(self):
from zope.security.proxy import ProxyFactory
class C(object):
Expand All @@ -1727,7 +1767,6 @@ class C(object):
pC = ProxyFactory(C, self.c)
self.assertEqual(d[pC], d[C])

@_skip_if_pypy250
def testProxiedNewClassAsDictKey(self):
from zope.security.proxy import ProxyFactory
class C(object):
Expand Down

0 comments on commit e5a571d

Please sign in to comment.