############################################################################## # # Copyright (c) 2001, 2002 Zope Foundation 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. # ############################################################################## """Helper functions for Proxies. """ import functools import sys from zope.proxy import PyProxyBase from zope.security._compat import PYPY from zope.security.interfaces import ForbiddenAttribute def _check_name(meth, wrap_result=True): name = meth.__name__ def _wrapper(self, *args, **kw): wrapped = super(PyProxyBase, self).__getattribute__('_wrapped') checker = super(PyProxyBase, self).__getattribute__('_checker') checker.check(wrapped, name) res = meth(self, *args, **kw) if not wrap_result: return res return checker.proxy(res) return functools.update_wrapper(_wrapper, meth) def _check_name_inplace(meth): name = meth.__name__ def _wrapper(self, *args, **kw): wrapped = super(PyProxyBase, self).__getattribute__('_wrapped') checker = super(PyProxyBase, self).__getattribute__('_checker') checker.check(wrapped, name) w_meth = getattr(wrapped, name, None) if w_meth is not None: # The proxy object cannot change; we are modifying in place. self._wrapped = w_meth(*args, **kw) return self x_name = '__%s__' % name[3:-2] return ProxyPy(getattr(wrapped, x_name)(*args, **kw), checker) return functools.update_wrapper(_wrapper, meth) def _fmt_address(obj): # Try to replicate PyString_FromString("%p", obj), which actually uses # the platform sprintf(buf, "%p", obj), which we cannot access from Python # directly (and ctypes seems like overkill). if sys.platform != 'win32': return '0x%0x' % id(obj) elif sys.maxsize < 2**32: return '0x%08X' % id(obj) else: return '0x%016X' % id(obj) class ProxyPy(PyProxyBase): __slots__ = ('_wrapped', '_checker') def __new__(cls, value, checker): inst = super(PyProxyBase, cls).__new__(cls) inst._wrapped = value inst._checker = checker return inst def __init__(self, value, checker): if checker is None: raise ValueError('checker may now be None') self._wrapped = value self._checker = checker # Attribute protocol def __getattribute__(self, name): if name in ('_wrapped', '_checker'): # Only allow _wrapped and _checker to be accessed from inside. if sys._getframe(1).f_locals.get('self') is not self: raise AttributeError(name) wrapped = super(PyProxyBase, self).__getattribute__('_wrapped') if name == '_wrapped': return wrapped checker = super(PyProxyBase, self).__getattribute__('_checker') if name == '_checker': return checker if name not in ('__cmp__', '__hash__', '__bool__', '__nonzero__', '__lt__', '__le__', '__eq__', '__ne__', '__ge__', '__gt__'): checker.check_getattr(wrapped, name) if name in ('__reduce__', '__reduce_ex__'): # The superclass specifically denies access to __reduce__ # and __reduce__ex__, not letting proxies be pickled. But # for backwards compatibility, we need to be able to # pickle proxies. See checker:Global for an example. val = getattr(wrapped, name) elif name == '__module__': # The superclass deals with descriptors found in the type # of this object just like the Python language spec states, letting # them have precedence over things found in the instance. This # normally makes us a better proxy implementation. However, the # C version of this code in _proxy doesn't take that same care and instead # uses the generic object attribute access methods directly on # the wrapped object. This is a behaviour difference; so far, it's # only been noticed for the __module__ attribute, which checker:Global # wants to override but couldn't because this object's type's __module__ would # get in the way. That broke pickling, and checker:Global can't return # anything more sophisticated than a str (a tuple) because it gets proxied # and breaks pickling again. Our solution is to match the C version for this # one attribute. val = getattr(wrapped, name) else: val = super(ProxyPy, self).__getattribute__(name) return checker.proxy(val) def __getattr__(self, name): wrapped = super(PyProxyBase, self).__getattribute__('_wrapped') checker = super(PyProxyBase, self).__getattribute__('_checker') checker.check_getattr(wrapped, name) return checker.proxy(getattr(wrapped, name)) def __setattr__(self, name, value): if name in ('_wrapped', '_checker'): return super(PyProxyBase, self).__setattr__(name, value) wrapped = super(PyProxyBase, self).__getattribute__('_wrapped') checker = super(PyProxyBase, self).__getattribute__('_checker') checker.check_setattr(wrapped, name) setattr(wrapped, name, value) def __delattr__(self, name): if name in ('_wrapped', '_checker'): raise AttributeError() wrapped = super(PyProxyBase, self).__getattribute__('_wrapped') checker = super(PyProxyBase, self).__getattribute__('_checker') checker.check_setattr(wrapped, name) delattr(wrapped, name) @_check_name def __getslice__(self, start, end): try: return self._wrapped.__getslice__(start, end) except: getitem = PyProxyBase.__getattribute__(self, '__getitem__') return getitem(slice(start, end)) @_check_name def __setslice__(self, i, j, value): try: return self._wrapped.__setslice__(i, j, value) except: setitem = PyProxyBase.__getattribute__(self, '__setitem__') return setitem(slice(i, j), value) def __cmp__(self, other): # no check wrapped = super(PyProxyBase, self).__getattribute__('_wrapped') return cmp(wrapped, other) def __lt__(self, other): # no check wrapped = super(PyProxyBase, self).__getattribute__('_wrapped') return wrapped < other def __le__(self, other): # no check wrapped = super(PyProxyBase, self).__getattribute__('_wrapped') return wrapped <= other def __eq__(self, other): # no check wrapped = super(PyProxyBase, self).__getattribute__('_wrapped') return wrapped == other def __ne__(self, other): # no check wrapped = super(PyProxyBase, self).__getattribute__('_wrapped') return wrapped != other def __ge__(self, other): # no check wrapped = super(PyProxyBase, self).__getattribute__('_wrapped') return wrapped >= other def __gt__(self, other): # no check wrapped = super(PyProxyBase, self).__getattribute__('_wrapped') return wrapped > other def __hash__(self): # no check wrapped = super(PyProxyBase, self).__getattribute__('_wrapped') return hash(wrapped) def __nonzero__(self): # no check wrapped = super(PyProxyBase, self).__getattribute__('_wrapped') return bool(wrapped) __bool__ = __nonzero__ def __length_hint__(self): # no check wrapped = super(PyProxyBase, self).__getattribute__('_wrapped') try: hint = wrapped.__length_hint__ except AttributeError: return NotImplemented else: return hint() def __coerce__(self, other): # For some reason _check_name does not work for coerce() wrapped = super(PyProxyBase, self).__getattribute__('_wrapped') checker = super(PyProxyBase, self).__getattribute__('_checker') checker.check(wrapped, '__coerce__') return super(ProxyPy, self).__coerce__(other) def __str__(self): try: return _check_name(PyProxyBase.__str__)(self) except: # The C implementation catches all exceptions. wrapped = super(PyProxyBase, self).__getattribute__('_wrapped') return '' %( wrapped.__class__.__module__, wrapped.__class__.__name__, _fmt_address(wrapped)) def __repr__(self): try: return _check_name(PyProxyBase.__repr__)(self) except: # The C implementation catches all exceptions. wrapped = super(PyProxyBase, self).__getattribute__('_wrapped') return '' %( wrapped.__class__.__module__, wrapped.__class__.__name__, _fmt_address(wrapped)) for name in ['__call__', #'__repr__', #'__str__', '__unicode__', '__reduce__', '__reduce_ex__', #'__lt__', # Unchecked in C proxy (rich coparison) #'__le__', # Unchecked in C proxy (rich coparison) #'__eq__', # Unchecked in C proxy (rich coparison) #'__ne__', # Unchecked in C proxy (rich coparison) #'__ge__', # Unchecked in C proxy (rich coparison) #'__gt__', # Unchecked in C proxy (rich coparison) #'__nonzero__', # Unchecked in C proxy (rich coparison) #'__bool__', # Unchecked in C proxy (rich coparison) #'__hash__', # Unchecked in C proxy (rich coparison) #'__cmp__', # Unchecked in C proxy '__getitem__', '__setitem__', '__delitem__', '__iter__', '__next__', 'next', '__contains__', '__neg__', '__pos__', '__abs__', '__invert__', '__complex__', '__int__', '__float__', '__long__', '__oct__', '__hex__', '__index__', '__add__', '__sub__', '__mul__', '__div__', '__truediv__', '__floordiv__', '__mod__', '__divmod__', '__pow__', '__radd__', '__rsub__', '__rmul__', '__rdiv__', '__rtruediv__', '__rfloordiv__', '__rmod__', '__rdivmod__', '__rpow__', '__lshift__', '__rshift__', '__and__', '__xor__', '__or__', '__rlshift__', '__rrshift__', '__rand__', '__rxor__', '__ror__', ]: meth = getattr(PyProxyBase, name) setattr(ProxyPy, name, _check_name(meth)) for name in ['__len__', ]: meth = getattr(PyProxyBase, name) setattr(ProxyPy, name, _check_name(meth, False)) for name in ['__iadd__', '__isub__', '__imul__', '__idiv__', '__itruediv__', '__ifloordiv__', '__imod__', '__ilshift__', '__irshift__', '__iand__', '__ixor__', '__ior__', '__ipow__', ]: meth = getattr(PyProxyBase, name) setattr(ProxyPy, name, _check_name_inplace(meth)) def getCheckerPy(proxy): return super(PyProxyBase, proxy).__getattribute__('_checker') if PYPY: _builtin_isinstance = __builtins__.isinstance else: _builtin_isinstance = __builtins__['isinstance'] def getObjectPy(proxy): if not _builtin_isinstance(proxy, ProxyPy): return proxy return super(PyProxyBase, proxy).__getattribute__('_wrapped') try: from zope.security._proxy import _Proxy except (ImportError, AttributeError): #pragma NO COVER PyPy / PURE_PYTHON getChecker = getCheckerPy getObject = getObjectPy Proxy = ProxyPy else: #pragma NO COVER CPython from zope.security._proxy import getChecker from zope.security._proxy import getObject Proxy = _Proxy removeSecurityProxy = getObject def getTestProxyItems(proxy): """Return a sorted sequence of checker names and permissions for testing """ checker = getChecker(proxy) return sorted(checker.get_permissions.items()) def isinstance(object, cls): """Test whether an object is an instance of a type. This works even if the object is security proxied. """ # The removeSecurityProxy call is OK here because it is *only* # being used for isinstance return _builtin_isinstance(removeSecurityProxy(object), cls)