summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES.rst9
-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
5 files changed, 227 insertions, 37 deletions
diff --git a/CHANGES.rst b/CHANGES.rst
index 13ea38e..1533b06 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -4,7 +4,14 @@ Changes
4.1.2 (unreleased)
------------------
-- TBD
+- 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
+ Python 2. Note that ``providedBy`` returns unproxied objects for backwards
+ compatibility.
+
+- Fix ``__length_hint__`` of proxied iterator objects. Previously it
+ was ignored.
4.1.1 (2017-05-17)
------------------
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):