Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pure-Python BTree.items() can't be iterated, C implementation allows it #20

Closed
jamadden opened this issue Apr 17, 2017 · 2 comments · Fixed by #21
Closed

Pure-Python BTree.items() can't be iterated, C implementation allows it #20

jamadden opened this issue Apr 17, 2017 · 2 comments · Fixed by #21

Comments

@jamadden
Copy link
Member

jamadden commented Apr 17, 2017

(As reported in zopefoundation/zope.proxy#16.)

Given this code:

import BTrees.OOBTree
from zope.security.proxy import Proxy
from zope.security.checker import Checker

bt = BTrees.OOBTree.OOBTree()
bt[''] = 42
items = bt.items()

checker = Checker({})
proxy = Proxy(items, checker)

iter(proxy)

It passes using the C implementation of everything (specifically, the C implementation of BTrees), but it fails using pure-python (Python implementation of BTrees):

Traceback (most recent call last):
  File "test.py", line 15, in <module>
    iter(proxy)
  File "//zope.filepypy/site-packages/zope/security/proxy.py", line 28, in _wrapper
    checker.check(wrapped, name)
  File "//zope.filepypy/site-packages/zope/security/checker.py", line 229, in check
    raise ForbiddenAttribute(name, object)
ForbiddenAttribute: ('__iter__', <BTrees._base._TreeItems object at 0x000000010da483a0>)

(Note that using CheckerPy also works with the C implementation of BTrees and fails with the Python version. Also note this happens for both empty trees and for trees that have objects in them. In the empty case, items() returns a tuple and the exception is ForbiddenAttribute: ('__iter__', ()))

The checker logic doesn't raise an exception if the object doesn't have the __iter__ attribute:

/*         if name != '__iter__' or hasattr(object, name): */
/*             __traceback_supplement__ = (TracebackSupplement, object) */
/*             raise ForbiddenAttribute, (name, object) */

      if (strcmp("__iter__", MAKE_STRING(name)) == 0
          && ! PyObject_HasAttr(object, name))
        /* We want an attr error if we're asked for __iter__ and we don't
           have it. We'll get one by allowing the access. */
        return 0;
    }

The C implementation of TreeItems does not have an __iter__ method:

Traceback (most recent call last):
  File "test.py", line 9, in <module>
    print(items.__iter__)
AttributeError: 'OOBTreeItems' object has no attribute '__iter__'

Instead, the TreeItems implements both tp_iter and tp_iternext and is its own iter (tp_iter just returns self). But the objects returned by the Python implementation all (necessarily) have an __iter__.

Thoughts on how to resolve this? Maybe this isn't a zope.security bug, per-se, but whatever sets up the permissions for zope.app should specifically allow iter on BTrees._base._TreeItems? (That smells bad to me, not least because it fails for the empty/tuple case.)

@jamadden
Copy link
Member Author

jamadden commented Apr 22, 2017

Some observations:

  1. I don't think the empty BTree case actually matters. Tuples are wrapped in a checker that permits iteration. The example above is contrived; the real tests that are failing have a BTree or Folder or something that's wrapped in a proxy to start with, not the items itself.
  2. The default list checker also allows iteration.
  3. Under Python 3, the views of dict.keys, dict.values and dict.items get no checkers (because they're immutable).
  4. There are special cases for list iterator, tuple iterator, etc, to allow iteration to actually work.
  5. Similarly, zope.app.security provides access to XXTreeIterator classes to allow iteration to work (though those are considered implementation details) as well as __getitem__, __len, similarly to the special cases for list, etc, iterator.
  6. However, none of the XXTreeItems classes have any special checkers assigned to them. Thus attempting to, for instance, index them with __getitem__ fails. iteration only works as a consequence of the implementation in C.
  7. XXTreeItems are immutable.

So, all of that leads me to this conclusion:

  • It would be safe to allow access to BTrees._base._TreeItems.__iter__ (it currently returns a generator, which is also special cased as an iterator); indeed it would be safe and possibly useful to allow access to __getitem__ and __len__ too.

This could be done in a few different ways:

  1. In zope.security.checker._default_checkers via a conditional import (easy and doesn't need to know implementation details, though it's somewhat tedious due to all the families)
  2. In zope.app.security._protectionts.zcml. That might be tricky because the C implementation doesn't expose XXTreeItems as an accessible/importable name, and it also doesn't have __iter__. I think that can be avoided if you're willing to rely directly on BTrees._base._TreeItems. Update: No, that fails. You do need to handle _TreeItems for the case that both Python and C implementations are in use, but you still also need to handle XXTreeItems from the C implementation, because there are cases that Python wants to access the __len__ of them.
  3. The BTrees Python implementation of _TreeItems can conditionally (on the importability of zope.security) define a __Security_checker__ attribute to give the desired access. This is the easiest of all since it only has to be done once in one class. Update: No, that fails for the C items for the reason given above.
  4. Alternately, that __Security_checker__ could be added in zope.security. This has the advantage of keeping zope.security out of BTrees, but the disadvantage of having BTree implementation knowledge embedded in zope.security. Update: No, that fails for the C items for the reason given above.

I've tested that the __Security_checker__ method works.

Thoughts?

@jamadden
Copy link
Member Author

jamadden commented Apr 24, 2017

Another wrinkle is that Python 3.x will attempt to call __len__ to get the length hint in some cases. That fails with both the C and Python implementations. That makes several of the possible options I thought of incorrect.

I'm working on a PR. This package seems like the most logical place to address the issue.

jamadden added a commit that referenced this issue Apr 24, 2017
Also fix ``list(proxy_btree.items())`` (or a list comprehension of the
same) in Python 3, which wants the ``__len__`` for a hint.

This is a central place to make sure these all behave consistently.

Fixes #20
jamadden added a commit that referenced this issue Apr 24, 2017
Also fix ``list(proxy_btree.items())`` (or a list comprehension of the
same) in Python 3, which wants the ``__len__`` for a hint.

This is a central place to make sure these all behave consistently.

Fixes #20

Also drop pypy3

As a 3.2 implementation, it's not supported by pip anymore. There is a
much more recent version, 3.5-beta, but it's not on Travis yet. The
3.3-alpha which is on Travis is a dead end.
jamadden added a commit that referenced this issue Apr 24, 2017
Also fix ``list(proxy_btree.items())`` (or a list comprehension of the
same) in Python 3, which wants the ``__len__`` for a hint.

This is a central place to make sure these all behave consistently.

Fixes #20

Also drop pypy3

As a 3.2 implementation, it's not supported by pip anymore. There is a
much more recent version, 3.5-beta, but it's not on Travis yet. The
3.3-alpha which is on Travis is a dead end.
jamadden added a commit that referenced this issue Apr 26, 2017
clrpackages pushed a commit to clearlinux-pkgs/zope.security that referenced this issue Apr 27, 2017
…sion 4.1.0

4.1.0 (2017-04-24)
------------------

- When testing ``PURE_PYTHON`` environments under ``tox``, avoid poisoning
  the user's global wheel cache.

- Drop support for Python 2.6 and 3.2.

- Add support for Python 3.5 and 3.6.

- Fix `issue 20 <https://github.com/zopefoundation/zope.security/issues/20>`_:
  iteration of pure-Python ``BTrees.items()``, and also creating a list from
  ``BTrees.items()`` on Python 3.
jamadden added a commit that referenced this issue May 17, 2017
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant