summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJason Madden <jamadden@gmail.com>2017-08-30 08:50:21 -0500
committerJason Madden <jamadden@gmail.com>2017-08-30 09:05:37 -0500
commit6f8de039836b62cf8c24c5ea23dfa0e6f0e629b6 (patch)
treed33ba4f9db75c32c75e9a1ee01c196e6d0572925 /src
parent4e08d85f4ad2d9fb1f1acbe1fad004c81b6d33c0 (diff)
downloadzope-security-6f8de039836b62cf8c24c5ea23dfa0e6f0e629b6.tar.gz
Fix proxying of providedBy on Python 3 and fix __length_hint__
Fixes #27. Add special cases to defaultCheckers for the two types of objects that can be returned from zope.interface.providedBy. On Python 2, these were never proxied, but on Python 3 they were. Now it's consistent (they're never proxied). (Using an _iteratorChecker for them would be a breaking change because the results of iterating them would be security proxied interface objects that don't compare equally.) Also fix `__length_hint__` while we're at it. Previously it was ignored because it is looked up on the type of the object, and proxy didn't implement that. So implement it, and add it to the list of names allowed for iterators.
Diffstat (limited to 'src')
-rw-r--r--src/zope/security/_proxy.c92
-rw-r--r--src/zope/security/checker.py39
-rw-r--r--src/zope/security/proxy.py5
-rw-r--r--src/zope/security/tests/test_checker.py119
4 files changed, 219 insertions, 36 deletions
diff --git a/src/zope/security/_proxy.c b/src/zope/security/_proxy.c
index 27edbb3..5108065 100644
--- a/src/zope/security/_proxy.c
+++ b/src/zope/security/_proxy.c
@@ -177,8 +177,8 @@ check(SecurityProxy *self, PyObject *meth, PyObject *name)
return self->proxy_checker->ob_type->tp_as_mapping->
mp_ass_subscript(self->proxy_checker, self->proxy.proxy_object, name);
- r = PyObject_CallMethodObjArgs(self->proxy_checker, meth,
- self->proxy.proxy_object, name,
+ r = PyObject_CallMethodObjArgs(self->proxy_checker, meth,
+ self->proxy.proxy_object, name,
NULL);
if (r == NULL)
return -1;
@@ -227,26 +227,26 @@ check2(PyObject *self, PyObject *other,
{
PyObject *result = NULL;
- if (Proxy_Check(self))
+ if (Proxy_Check(self))
{
if (check((SecurityProxy*)self, str_check, opname) >= 0)
{
- result = operation(((SecurityProxy*)self)->proxy.proxy_object,
+ result = operation(((SecurityProxy*)self)->proxy.proxy_object,
other);
PROXY_RESULT(((SecurityProxy*)self), result);
}
}
- else if (Proxy_Check(other))
+ else if (Proxy_Check(other))
{
if (check((SecurityProxy*)other, str_check, ropname) >= 0)
{
- result = operation(self,
+ result = operation(self,
((SecurityProxy*)other)->proxy.proxy_object);
-
+
PROXY_RESULT(((SecurityProxy*)other), result);
}
}
- else
+ else
{
Py_INCREF(Py_NotImplemented);
return Py_NotImplemented;
@@ -261,10 +261,10 @@ check2i(SecurityProxy *self, PyObject *other,
{
PyObject *result = NULL;
- if (check(self, str_check, opname) >= 0)
+ if (check(self, str_check, opname) >= 0)
{
result = operation(self->proxy.proxy_object, other);
- if (result == self->proxy.proxy_object)
+ if (result == self->proxy.proxy_object)
{
/* If the operation was really carried out inplace,
don't create a new proxy, but use the old one. */
@@ -272,7 +272,7 @@ check2i(SecurityProxy *self, PyObject *other,
Py_INCREF((PyObject *)self);
result = (PyObject *)self;
}
- else
+ else
PROXY_RESULT(self, result);
}
return result;
@@ -372,7 +372,7 @@ proxy_iter(SecurityProxy *self)
{
PyObject *result = NULL;
- if (check(self, str_check, str___iter__) >= 0)
+ if (check(self, str_check, str___iter__) >= 0)
{
result = PyObject_GetIter(self->proxy.proxy_object);
PROXY_RESULT(self, result);
@@ -385,7 +385,7 @@ proxy_iternext(SecurityProxy *self)
{
PyObject *result = NULL;
- if (check(self, str_check_getattr, str_next) >= 0)
+ if (check(self, str_check_getattr, str_next) >= 0)
{
result = PyIter_Next(self->proxy.proxy_object);
PROXY_RESULT(self, result);
@@ -398,7 +398,7 @@ proxy_getattro(SecurityProxy *self, PyObject *name)
{
PyObject *result = NULL;
- if (check(self, str_check_getattr, name) >= 0)
+ if (check(self, str_check_getattr, name) >= 0)
{
result = PyObject_GetAttr(self->proxy.proxy_object, name);
PROXY_RESULT(self, result);
@@ -449,7 +449,7 @@ default_repr(PyObject *object)
Py_DECREF(klass);
Py_XDECREF(name);
Py_XDECREF(module);
-
+
return result;
}
@@ -458,11 +458,11 @@ proxy_str(SecurityProxy *self)
{
PyObject *result = NULL;
- if (check(self, str_check, str___str__) >= 0)
+ if (check(self, str_check, str___str__) >= 0)
{
result = PyObject_Str(self->proxy.proxy_object);
}
- else
+ else
{
PyErr_Clear();
result = default_repr(self->proxy.proxy_object);
@@ -504,7 +504,7 @@ proxy_call(SecurityProxy *self, PyObject *args, PyObject *kwds)
{
PyObject *result = NULL;
- if (check(self, str_check, str___call__) >= 0)
+ if (check(self, str_check, str___call__) >= 0)
{
result = PyObject_Call(self->proxy.proxy_object, args, kwds);
PROXY_RESULT(self, result);
@@ -558,7 +558,7 @@ proxy_pow(PyObject *self, PyObject *other, PyObject *modulus)
{
PyObject *result = NULL;
- if (Proxy_Check(self))
+ if (Proxy_Check(self))
{
if (check((SecurityProxy*)self, str_check, str___pow__) >= 0)
{
@@ -567,21 +567,21 @@ proxy_pow(PyObject *self, PyObject *other, PyObject *modulus)
PROXY_RESULT(((SecurityProxy*)self), result);
}
}
- else if (Proxy_Check(other))
+ else if (Proxy_Check(other))
{
if (check((SecurityProxy*)other, str_check, str___rpow__) >= 0)
- {
- result = PyNumber_Power(self,
- ((SecurityProxy*)other)->proxy.proxy_object,
+ {
+ result = PyNumber_Power(self,
+ ((SecurityProxy*)other)->proxy.proxy_object,
modulus);
PROXY_RESULT(((SecurityProxy*)other), result);
}
}
- else if (modulus != NULL && Proxy_Check(modulus))
+ else if (modulus != NULL && Proxy_Check(modulus))
{
if (check((SecurityProxy*)modulus, str_check, str___3pow__) >= 0)
- {
- result = PyNumber_Power(self, other,
+ {
+ result = PyNumber_Power(self, other,
((SecurityProxy*)modulus)->proxy.proxy_object);
PROXY_RESULT(((SecurityProxy*)modulus), result);
}
@@ -608,7 +608,7 @@ proxy_coerce(PyObject **p_self, PyObject **p_other)
assert(Proxy_Check(self));
- if (check((SecurityProxy*)self, str_check, str___coerce__) >= 0)
+ if (check((SecurityProxy*)self, str_check, str___coerce__) >= 0)
{
PyObject *left = ((SecurityProxy*)self)->proxy.proxy_object;
PyObject *right = other;
@@ -697,6 +697,21 @@ proxy_length(SecurityProxy *self)
return -1;
}
+static PyObject *
+proxy_length_hint(SecurityProxy *self)
+{
+ PyObject *result = NULL;
+ result = PyObject_CallMethod(self->proxy.proxy_object, "__length_hint__", NULL);
+ if (result == NULL) {
+ if (PyErr_ExceptionMatches(PyExc_AttributeError)) {
+ PyErr_Clear();
+ result = Py_NotImplemented;
+ Py_INCREF(result);
+ }
+ }
+ return result;
+}
+
/* sq_item and sq_ass_item may be called by PySequece_{Get,Set}Item(). */
static PyObject *proxy_getitem(SecurityProxy *, PyObject *);
static int proxy_setitem(SecurityProxy *, PyObject *, PyObject *);
@@ -765,7 +780,7 @@ proxy_getitem(SecurityProxy *self, PyObject *key)
{
PyObject *result = NULL;
- if (check(self, str_check, str___getitem__) >= 0)
+ if (check(self, str_check, str___getitem__) >= 0)
{
result = PyObject_GetItem(self->proxy.proxy_object, key);
PROXY_RESULT(self, result);
@@ -884,6 +899,13 @@ disallowed. The proxy method should return a proxy for the object\n\
if one is needed, otherwise the object itself.\n\
";
+static PyMethodDef proxy_methods[] = {
+ {"__length_hint__", (PyCFunction)proxy_length_hint, METH_NOARGS,
+ "Guess the length of the object"},
+ {NULL, NULL} /* sentinel */
+};
+
+
static PyTypeObject SecurityProxyType = {
PyVarObject_HEAD_INIT(NULL, 0)
"zope.security._proxy._Proxy",
@@ -925,7 +947,7 @@ static PyTypeObject SecurityProxyType = {
0, /* tp_weaklistoffset */
(getiterfunc)proxy_iter, /* tp_iter */
(iternextfunc)proxy_iternext, /* tp_iternext */
- 0, /* tp_methods */
+ proxy_methods, /* tp_methods */
0, /* tp_members */
0, /* tp_getset */
0, /* tp_base */
@@ -963,7 +985,7 @@ module_getObject(PyObject *self, PyObject *arg)
result = arg;
else
result = ((SecurityProxy*)arg)->proxy.proxy_object;
-
+
Py_INCREF(result);
return result;
}
@@ -974,7 +996,7 @@ module___doc__[] = "Security proxy implementation.";
static PyMethodDef
module_functions[] = {
{"getChecker", module_getChecker, METH_O, "get checker from proxy"},
- {"getObject", module_getObject, METH_O,
+ {"getObject", module_getObject, METH_O,
"Get the proxied object\n\nReturn the original object if not proxied."},
{NULL}
};
@@ -1069,19 +1091,19 @@ if((str_op_##S = INTERN("__" #S "__")) == NULL) return MOD_ERROR_VAL
INIT_STRING(__setitem__);
INIT_STRING(__setslice__);
INIT_STRING(__str__);
-
+
__class__str = FROM_STRING("__class__");
if (! __class__str)
return MOD_ERROR_VAL;
-
+
__name__str = FROM_STRING("__name__");
if (! __name__str)
return MOD_ERROR_VAL;
-
+
__module__str = FROM_STRING("__module__");
if (! __module__str) return MOD_ERROR_VAL;
-
+
SecurityProxyType.tp_alloc = PyType_GenericAlloc;
SecurityProxyType.tp_free = PyObject_GC_Del;
SecurityProxyType.tp_base = &ProxyType;
diff --git a/src/zope/security/checker.py b/src/zope/security/checker.py
index 344428c..f870a13 100644
--- a/src/zope/security/checker.py
+++ b/src/zope/security/checker.py
@@ -604,7 +604,8 @@ _typeChecker = NamesChecker(
'__implemented__'])
_namedChecker = NamesChecker(['__name__'])
-_iteratorChecker = NamesChecker(['next', '__next__', '__iter__', '__len__'])
+_iteratorChecker = NamesChecker(['next', '__next__', '__iter__', '__len__',
+ '__length_hint__',])
_setChecker = NamesChecker(['__iter__', '__len__', '__str__', '__contains__',
'copy', 'difference', 'intersection', 'issubset',
@@ -823,6 +824,42 @@ else:
del _fixup_dictlike
+def _fixup_zope_interface():
+ # Make sure the provided and implementedBy objects
+ # can be iterated.
+ # Note that we DO NOT use the _iteratorChecker, but instead
+ # we use NoProxy to be sure that the results (of iteration or not) are not
+ # proxied. On Python 2, these objects are builtin and don't go through the
+ # checking process at all, much like BTrees, so NoProxy is necessary for
+ # compatibility. On Python 3, prior to this, iteration was simply not allowed.
+ from zope.interface import providedBy
+ from zope.interface import alsoProvides
+
+ class I1(Interface):
+ pass
+
+ class I2(Interface):
+ pass
+
+ @implementer(I1)
+ class O(object):
+ pass
+
+ o = O()
+
+
+ # This will be athe zope.interface.implementedBy from the class
+ # a zope.interface.declarations.Implements
+ _default_checkers[type(providedBy(o))] = NoProxy
+
+ alsoProvides(o, I2)
+ # This will be the zope.interface.Provides from the instance
+ _default_checkers[type(providedBy(o))] = NoProxy
+
+_fixup_zope_interface()
+del _fixup_zope_interface
+
+
def _clear():
_checkers.clear()
_checkers.update(_default_checkers)
diff --git a/src/zope/security/proxy.py b/src/zope/security/proxy.py
index d7f12e2..77a82d4 100644
--- a/src/zope/security/proxy.py
+++ b/src/zope/security/proxy.py
@@ -199,6 +199,11 @@ class ProxyPy(PyProxyBase):
return bool(wrapped)
__bool__ = __nonzero__
+ def __length_hint__(self):
+ # no check
+ wrapped = super(PyProxyBase, self).__getattribute__('_wrapped')
+ return wrapped.__length_hint__()
+
def __coerce__(self, other):
# For some reason _check_name does not work for coerce()
wrapped = super(PyProxyBase, self).__getattribute__('_wrapped')
diff --git a/src/zope/security/tests/test_checker.py b/src/zope/security/tests/test_checker.py
index 03a8883..b82799f 100644
--- a/src/zope/security/tests/test_checker.py
+++ b/src/zope/security/tests/test_checker.py
@@ -434,6 +434,125 @@ class CheckerTestsBase(object):
# iteration of regular dict is allowed by default
self._check_iteration_of_dict_like(dict())
+ def test_iteration_of_interface_implementedBy(self):
+ # Iteration of implementedBy is allowed by default
+ # See https://github.com/zopefoundation/zope.security/issues/27
+ from zope.security.proxy import Proxy
+ from zope.security.checker import Checker
+
+ from zope.interface import providedBy
+ from zope.interface import implementer
+ from zope.interface import Interface
+
+ class I1(Interface):
+ pass
+
+ @implementer(I1)
+ class O(object):
+ pass
+
+ o = O()
+
+ checker = Checker({})
+
+ proxy = Proxy(o, checker)
+
+ # Since the object itself doesn't have any interfaces,
+ # the providedBy will return the implementedBy of the class
+ l = list(providedBy(proxy))
+
+ self.assertEqual(l, [I1])
+
+ def test_iteration_of_interface_providesBy(self):
+ # Iteration of zope.interface.Provides is allowed by default
+ # See https://github.com/zopefoundation/zope.security/issues/27
+ from zope.security.proxy import Proxy
+ from zope.security.checker import Checker
+
+ from zope.interface import providedBy
+ from zope.interface import alsoProvides
+ from zope.interface import implementer
+ from zope.interface import Interface
+
+ class I1(Interface):
+ pass
+
+ class I2(Interface):
+ pass
+
+ @implementer(I1)
+ class O(object):
+ pass
+
+ o = O()
+ alsoProvides(o, I2)
+
+ checker = Checker({})
+
+ proxy = Proxy(o, checker)
+
+ # Since the object has its own interfaces, provided
+ # by will return a zope.interface.Provides object
+ l = list(providedBy(proxy))
+
+ self.assertEqual(l, [I2, I1])
+
+ def test_iteration_with_length_hint(self):
+ # PEP 424 implemented officially in Python 3.4 and
+ # unofficially before in cPython and PyPy that allows for a
+ # __length_hint__ method to be defined on iterators. It should
+ # be allowed by default. See
+ # https://github.com/zopefoundation/zope.security/issues/27
+ from zope.security.proxy import Proxy
+ from zope.security.checker import _iteratorChecker
+ from zope.security.checker import Checker
+
+ class Iter(object):
+ __Security_checker__ = _iteratorChecker
+
+ items = (0, 1, 2)
+ index = 0
+ hint = len(items)
+ hint_called = False
+
+ def __iter__(self):
+ return self
+
+ def __next__(self):
+ try:
+ return self.items[self.index]
+ except IndexError:
+ raise StopIteration()
+ finally:
+ self.index += 1
+
+ next = __next__
+
+ def __length_hint__(self):
+ self.hint_called = True
+ return self.hint
+
+ # The hint is called on raw objects
+ i = Iter()
+ list(i)
+ self.assertTrue(i.hint_called, "__length_hint__ should be called")
+
+ # The hint is called when we proxy the root object
+ i = Iter()
+ proxy = Proxy(i, _iteratorChecker)
+ l = list(proxy)
+ self.assertEqual(l, [0, 1, 2])
+ self.assertTrue(i.hint_called, "__length_hint__ should be called")
+
+ # The hint is called when we proxy its iterator
+ i = Iter()
+ it = iter(i)
+ proxy = Proxy(it, _iteratorChecker)
+ l = list(proxy)
+ self.assertEqual(l, [0, 1, 2])
+ self.assertTrue(i.hint_called, "__length_hint__ should be called")
+
+
class CheckerPyTests(unittest.TestCase, CheckerTestsBase):
def _getTargetClass(self):