summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason Madden <jamadden@gmail.com>2017-05-17 07:10:48 -0500
committerJason Madden <jamadden@gmail.com>2017-05-17 07:10:48 -0500
commitcdb0161c7edf67857a7625ec666a80c9cd35c297 (patch)
treef1cde20a2bcc3f6dda7e89caf096534ca549e04f
parent2b82f83048802017a451d2abcdc2a2bae8ece182 (diff)
downloadzope-security-fix-ordered-dict-checker.tar.gz
OrderedDict, BTree and dict all iterate the same way.fix-ordered-dict-checker
Fixes #23. Also a further fix for #20 (you couldn't iterate a BTree all by itself). Refactor the test case for BTree to be a shared implementation and confirm that it works as expected for dict, using the actual dict checker. Then apply it to OrderedDict and BTree and fix the resulting failures by refactoring the fixup in checker.py to a shared implementation and applying it.
-rw-r--r--CHANGES.rst7
-rw-r--r--src/zope/security/checker.py35
-rw-r--r--src/zope/security/tests/test_checker.py52
3 files changed, 64 insertions, 30 deletions
diff --git a/CHANGES.rst b/CHANGES.rst
index 0203e6a..aea7438 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -4,7 +4,12 @@ Changes
4.1.1 (unreleased)
------------------
-- TBD
+- Fix `issue 23 <https://github.com/zopefoundation/zope.security/issues/23>`_:
+ iteration of ``collections.OrderedDict`` and its various views is
+ now allowed by default on all versions of Python.
+
+- As a further fix for issue 20, iteration of BTree itself is now
+ allowed by default.
4.1.0 (2017-04-24)
------------------
diff --git a/src/zope/security/checker.py b/src/zope/security/checker.py
index 3187afc..344428c 100644
--- a/src/zope/security/checker.py
+++ b/src/zope/security/checker.py
@@ -770,6 +770,27 @@ if PYTHON2:
_default_checkers[type({}.iterkeys())] = _iteratorChecker
_default_checkers[type({}.itervalues())] = _iteratorChecker
+def _fixup_dictlike(dict_type):
+ empty_dict = dict_type()
+ populated_dict = dict_type({1: 2})
+ for dictlike in (empty_dict, populated_dict):
+ for attr in ('__iter__', 'keys', 'items', 'values'):
+ obj = getattr(dictlike, attr)()
+ o_type = type(obj)
+ if o_type not in _default_checkers:
+ _default_checkers[o_type] = _iteratorChecker
+
+def _fixup_odict():
+ # OrderedDicts have three different implementations: Python 2 (pure
+ # python, returns generators and lists), Python <=3.4 (pure Python,
+ # uses view classes) and CPython 3.5+ (implemented in C). These should
+ # all be iterable.
+ from collections import OrderedDict
+ _fixup_dictlike(OrderedDict)
+
+_fixup_odict()
+del _fixup_odict
+
try:
import BTrees
except ImportError: # pragma: no cover
@@ -794,20 +815,14 @@ else:
for name in ('IF', 'II', 'IO', 'OI', 'OO'):
for family_name in ('family32', 'family64'):
family = getattr(BTrees, family_name)
- btree = getattr(family, name).BTree()
-
- empty_type = type(btree.items())
- if empty_type not in _default_checkers:
- _default_checkers[empty_type] = _iteratorChecker
-
- btree[1] = 1
- populated_type = type(btree.items())
- if populated_type not in _default_checkers:
- _default_checkers[populated_type] = _iteratorChecker
+ btree = getattr(family, name).BTree
+ _fixup_dictlike(btree)
_fixup_btrees()
del _fixup_btrees
+del _fixup_dictlike
+
def _clear():
_checkers.clear()
_checkers.update(_default_checkers)
diff --git a/src/zope/security/tests/test_checker.py b/src/zope/security/tests/test_checker.py
index ceda502..03a8883 100644
--- a/src/zope/security/tests/test_checker.py
+++ b/src/zope/security/tests/test_checker.py
@@ -391,34 +391,48 @@ class CheckerTestsBase(object):
finally:
_clear()
- @_skip_if_no_btrees
- def test_iteration_of_btree_items(self):
- # iteration of BTree.items() is allowed by default.
+ def _check_iteration_of_dict_like(self, dict_like):
from zope.security.proxy import Proxy
from zope.security.checker import Checker
- from zope.security.checker import CheckerPublic
- import BTrees
+ from zope.security.checker import _default_checkers
+
+ checker = _default_checkers[dict]
+
+ proxy = Proxy(dict_like, checker)
+ # empty
+ self.assertEqual([], list(proxy.items()))
+ self.assertEqual([], list(proxy.keys()))
+ self.assertEqual([], list(proxy.values()))
+ self.assertEqual([], list(proxy))
- checker = Checker({'items': CheckerPublic,
- 'keys': CheckerPublic,
- 'values': CheckerPublic})
+ # With an object
+ dict_like[1] = 2
+ self.assertEqual([(1, 2)], list(proxy.items()))
+ self.assertEqual([1], list(proxy.keys()))
+ self.assertEqual([1], list(proxy))
+ self.assertEqual([2], list(proxy.values()))
+
+ @_skip_if_no_btrees
+ def test_iteration_of_btree_items_keys_values(self):
+ # iteration of BTree.items() is allowed by default.
+ import BTrees
for name in ('IF', 'II', 'IO', 'OI', 'OO'):
for family_name in ('family32', 'family64'):
family = getattr(BTrees, family_name)
btree = getattr(family, name).BTree()
- proxy = Proxy(btree, checker)
- # empty
- self.assertEqual([], list(proxy.items()))
- self.assertEqual([], list(proxy.keys()))
- self.assertEqual([], list(proxy.values()))
-
- # With an object
- btree[1] = 2
- self.assertEqual([(1, 2)], list(proxy.items()))
- self.assertEqual([1], list(proxy.keys()))
- self.assertEqual([2], list(proxy.values()))
+ self._check_iteration_of_dict_like(btree)
+
+ def test_iteration_of_odict_items_keys_values(self):
+ # iteration of OrderedDict.items() is allowed by default.
+ from collections import OrderedDict
+
+ odict = OrderedDict()
+ self._check_iteration_of_dict_like(odict)
+ def test_iteration_of_dict_items_keys_values(self):
+ # iteration of regular dict is allowed by default
+ self._check_iteration_of_dict_like(dict())
class CheckerPyTests(unittest.TestCase, CheckerTestsBase):