diff --git a/CHANGES.rst b/CHANGES.rst index 10fad5c..a724f49 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -7,6 +7,13 @@ Changes - Allow to use the package with Python 3.13 -- Caution: No security audit has been done so far. +- Provide new function ``RestrictedPython.Guards.safer_getattr_raise``. + It is similar to ``safer_getattr`` but handles its parameter + ``default`` like ``getattr``, i.e. it raises ``AttributeError`` + if the attribute lookup fails and this parameter is not provided, + fixes `#287 `_. + + 7.3 (2024-09-30) ---------------- diff --git a/src/RestrictedPython/Guards.py b/src/RestrictedPython/Guards.py index 5aa720b..ce092f0 100644 --- a/src/RestrictedPython/Guards.py +++ b/src/RestrictedPython/Guards.py @@ -240,6 +240,9 @@ def guarded_delattr(object, name): safe_builtins['delattr'] = guarded_delattr +raise_ = object() + + def safer_getattr(object, name, default=None, getattr=getattr): """Getattr implementation which prevents using format on string objects. @@ -263,12 +266,18 @@ def safer_getattr(object, name, default=None, getattr=getattr): '"{name}" is an invalid attribute name because it ' 'starts with "_"'.format(name=name) ) - return getattr(object, name, default) + args = (object, name) + (() if default is raise_ else (default,)) + return getattr(*args) safe_builtins['_getattr_'] = safer_getattr +def safer_getattr_raise(object, name, default=raise_): + """like ``safer_getattr`` but raising ``AttributeError`` if failing.""" + return safer_getattr(object, name, default) + + def guarded_iter_unpack_sequence(it, spec, _getiter_): """Protect sequence unpacking of targets in a 'for loop'. diff --git a/tests/test_Guards.py b/tests/test_Guards.py index f3db19b..18347d1 100644 --- a/tests/test_Guards.py +++ b/tests/test_Guards.py @@ -329,6 +329,18 @@ def test_Guards__safer_getattr__5(): ) == str(err.value) +def test_Guards__safer_getattr_raise(): + from types import SimpleNamespace + + from RestrictedPython.Guards import safer_getattr_raise as ga + + o = SimpleNamespace(a="a") + assert ga(o, "a") == "a" + assert ga(o, "b", None) is None + with pytest.raises(AttributeError): + ga(o, "b") + + def test_call_py3_builtins(): """It should not be allowed to access global builtins in Python3.""" result = compile_restricted_exec('builtins["getattr"]')