Source code for zope.security.proxy

##############################################################################
#
# 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.

.. seealso:: :ref:`proxy-known-issues`
"""
import functools
import sys

from zope.proxy import PyProxyBase

from zope.security._compat import PURE_PYTHON


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)
    if sys.maxsize < 2**32:  # pragma: no cover
        return '0x%08X' % id(obj)
    return '0x%016X' % id(obj)  # pragma: no cover


[docs] class ProxyPy(PyProxyBase): """ The pure-Python reference implementation of a security proxy. This should normally not be created directly, instead use the :func:`~.ProxyFactory`. You can choose to use this implementation instead of the C implementation by default by setting the ``PURE_PYTHON`` environment variable before :mod:`zope.security` is imported. """ __slots__ = ('_wrapped', '_checker') def __new__(cls, value, checker): inst = super().__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().__getattribute__('_wrapped') if name == '_wrapped': return wrapped checker = super().__getattribute__('_checker') if name == '_checker': return checker if name not in ('__cmp__', '__hash__', '__bool__', '__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().__getattribute__(name) return checker.proxy(val) def __getattr__(self, name): # We only get here if __getattribute__ has already raised an # AttributeError (we have to implement this because the super # class does). We expect that we will also raise that same # error, one way or another---either it will be forbidden by # the checker or it won't exist. However, if the underlying # object is playing games in *its* # __getattribute__/__getattr__, and we call getattr() on it, # (maybe there are threads involved), we might actually # succeed this time. # The C implementation *does not* do two checks; it only does # one check, and raises either the ForbiddenAttribute or the # underlying AttributeError, *without* invoking any defined # __getattribute__/__getattr__ more than once. So we # explicitly do the same. The consequence is that we lose a # good stack trace if the object implemented its own methods # but we're consistent. We would provide a better error # message or even subclass of AttributeError, but that's liable to # break (doc)tests. wrapped = super().__getattribute__('_wrapped') checker = super().__getattribute__('_checker') checker.check_getattr(wrapped, name) raise AttributeError(name) def __setattr__(self, name, value): if name in ('_wrapped', '_checker'): return super().__setattr__(name, value) wrapped = super().__getattribute__('_wrapped') checker = super().__getattribute__('_checker') checker.check_setattr(wrapped, name) setattr(wrapped, name, value) def __delattr__(self, name): if name in ('_wrapped', '_checker'): raise AttributeError() wrapped = super().__getattribute__('_wrapped') checker = super().__getattribute__('_checker') checker.check_setattr(wrapped, name) delattr(wrapped, name) def __lt__(self, other): # no check wrapped = super().__getattribute__('_wrapped') return wrapped < other def __le__(self, other): # no check wrapped = super().__getattribute__('_wrapped') return wrapped <= other def __eq__(self, other): # no check wrapped = super().__getattribute__('_wrapped') return wrapped == other def __ne__(self, other): # no check wrapped = super().__getattribute__('_wrapped') return wrapped != other def __ge__(self, other): # no check wrapped = super().__getattribute__('_wrapped') return wrapped >= other def __gt__(self, other): # no check wrapped = super().__getattribute__('_wrapped') return wrapped > other def __hash__(self): # no check wrapped = super().__getattribute__('_wrapped') return hash(wrapped) def __bool__(self): # no check wrapped = super().__getattribute__('_wrapped') return bool(wrapped) def __length_hint__(self): # no check wrapped = super().__getattribute__('_wrapped') try: hint = wrapped.__length_hint__ except AttributeError: return NotImplemented else: return hint() 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: # noqa: E722 do not use bare 'except' # The C implementation catches all exceptions. wrapped = super().__getattribute__('_wrapped') return '<security proxied {}.{} instance at {}>'.format( wrapped.__class__.__module__, wrapped.__class__.__name__, _fmt_address(wrapped)) 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: # noqa: E722 do not use bare 'except' wrapped = super().__getattribute__('_wrapped') return '<security proxied {}.{} instance at {}>'.format( wrapped.__class__.__module__, wrapped.__class__.__name__, _fmt_address(wrapped))
for name in ['__call__', # '__repr__', # '__str__', # '__unicode__', # Unchecked in C proxy '__reduce__', '__reduce_ex__', # '__lt__', # Unchecked in C proxy (rich comparison) # '__le__', # Unchecked in C proxy (rich comparison) # '__eq__', # Unchecked in C proxy (rich comparison) # '__ne__', # Unchecked in C proxy (rich comparison) # '__ge__', # Unchecked in C proxy (rich comparison) # '__gt__', # Unchecked in C proxy (rich comparison) # '__bool__', # Unchecked in C proxy (rich comparison) # '__hash__', # Unchecked in C proxy (rich comparison) # '__cmp__', # Unchecked in C proxy '__getitem__', '__setitem__', '__delitem__', '__iter__', '__next__', '__contains__', '__neg__', '__pos__', '__abs__', '__invert__', '__complex__', '__int__', '__float__', '__index__', '__add__', '__sub__', '__mul__', '__truediv__', '__floordiv__', '__mod__', '__divmod__', '__pow__', '__radd__', '__rsub__', '__rmul__', '__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__', '__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(ProxyPy, proxy).__getattribute__('_checker') _builtin_isinstance = sys.modules['builtins'].isinstance def getObjectPy(proxy): if not _builtin_isinstance(proxy, ProxyPy): return proxy return super(ProxyPy, proxy).__getattribute__('_wrapped') _c_available = not PURE_PYTHON if _c_available: # pragma: no cover try: from zope.security._proxy import _Proxy except (ImportError, AttributeError): _c_available = False getChecker = getCheckerPy getObject = getObjectPy Proxy = ProxyPy if _c_available: # pragma: no cover from zope.security._proxy import getChecker from zope.security._proxy import getObject Proxy = _Proxy removeSecurityProxy = getObject
[docs] def getTestProxyItems(proxy): """Return a sorted sequence of checker names and permissions for testing """ checker = getChecker(proxy) return sorted(checker.get_permissions.items())
[docs] 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)