Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ For changes before version 3.0, see ``HISTORY.rst``.
7.4 (unreleased)
----------------

- Fix a parity defect between the Python and C implementations of
``guarded_getattr``: the pure-Python implementation keyed the container
assertion on ``type(value.__self__)`` instead of ``type(inst)``, so under
``PURE_PYTHON`` a bound method of an allow-listed simple type (e.g.
``tuple``/``bytes``/``range``) was returned without calling ``validate()``.
The Python implementation now keys on ``type(inst)``, matching the C
implementation, and a regression test guards the behaviour.

- Add preliminary support for Python 3.15 as of 3.15b1.

- Add support for automatically building and publishing Windows/ARM64 wheels.
Expand Down
14 changes: 8 additions & 6 deletions src/AccessControl/ImplPython.py
Original file line number Diff line number Diff line change
Expand Up @@ -733,12 +733,14 @@ def guarded_getattr(inst, name, default=_marker):
return default
raise

try:
container = v.__self__
except AttributeError:
container = aq_parent(aq_inner(v)) or inst

assertion = Containers(type(container))
# The container assertion must be keyed on the type of the accessed
# object, to match the C implementation (see ``cAccessControl.c``, which
# uses ``Containers(type(inst))``). Keying on ``type(v.__self__)``
# instead let a bound method whose ``__self__`` is an allow-listed simple
# type (e.g. ``tuple``, ``bytes``, ``range``) take the truthy-assertion
# short-circuit and be returned without calling ``validate()`` -- a
# divergence from the C engine.
assertion = Containers(type(inst))

if isinstance(assertion, dict):
# We got a table that lets us reason about individual
Expand Down
19 changes: 19 additions & 0 deletions src/AccessControl/tests/testZopeGuards.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,25 @@ def test_calls_validate_for_unknown_type(self):
guarded_getattr(self, 'test_calls_validate_for_unknown_type')
self.assertEqual(len(self.__sm.calls), 1)

def test_validates_bound_method_of_allow_listed_type(self):
# A bound method whose ``__self__`` is an allow-listed simple type
# (here a ``tuple``), accessed as an attribute of an object that has
# no container assertion of its own, must still be routed through
# ``validate()``. The assertion lookup keys on the type of the
# accessed object (``type(inst)``), not on ``type(value.__self__)`` --
# otherwise the pure-Python engine short-circuits and diverges from
# the C engine, returning the method without a permission check.
from AccessControl import Unauthorized
from AccessControl.ZopeGuards import guarded_getattr

class Protected:
feed = (1, 2, 3).count # bound method of an allow-listed tuple

obj = Protected()
self.__sm.reject = True
self.assertRaises(Unauthorized, guarded_getattr, obj, 'feed')
self.assertEqual(len(self.__sm.calls), 1)

def test_attr_handler_table(self):
from AccessControl import Unauthorized
from AccessControl.SimpleObjectPolicies import ContainerAssertions
Expand Down
Loading