Skip to content

Commit

Permalink
Update tests & readme etc for new allowed_attrs feature
Browse files Browse the repository at this point in the history
  • Loading branch information
danthedeckie committed Oct 28, 2024
1 parent 2087499 commit 8b9364a
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 5 deletions.
49 changes: 45 additions & 4 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -432,10 +432,8 @@ version that disallows method invocation on objects:
and then use ``EvalNoMethods`` instead of the ``SimpleEval`` class.

Other...
--------

The library supports Python 3.9 and higher.
Limiting Attribute Access
-------------------------

Object attributes that start with ``_`` or ``func_`` are disallowed by default.
If you really need that (BE CAREFUL!), then modify the module global
Expand All @@ -445,6 +443,49 @@ A few builtin functions are listed in ``simpleeval.DISALLOW_FUNCTIONS``. ``type
If you need to give access to this kind of functionality to your expressions, then be very
careful. You'd be better wrapping the functions in your own safe wrappers.

There is an additional layer of protection you can add in by passing in ``allowed_attrs``, which
makes all attribute access based opt-in rather than opt-out - which is a lot safer design:

.. code-block:: pycon
>>> simpleeval("' hello '.strip()", allowed_attrs={})
will throw FeatureNotAvailable - as we've now disabled all attribute access. You can enable some
reasonably sensible defaults with BASIC_ALLOWED_ATTRS:

.. code-block:: pycon
>>> from simpleeval import simpleeval, BASIC_ALLOWED_ATTRS
>>> simpleeval("' hello '.strip()", allowed_attrs=BASIC_ALLOWED_ATTRS)
is fine - ``strip()`` should be safe on strings.

It is recommended to add ``allowed_attrs=BASIC_ALLOWED_ATTRS`` whenever possible, and it will
be the default for 2.x.

You can add your own classes & limit access to attrs:

.. code-block:: pycon
>>> from simpleeval import simpleeval, BASIC_ALLOWED_ATTRS
>>> class Foo:
>>> bar = 42
>>> hidden = "secret"
>>>
>>> our_attributes = BASIC_ALLOWED_ATTRS.copy()
>>> our_attributes[Foo] = {'bar'}
>>> simpleeval("foo.bar", names={"foo": Foo()}, allowed_attrs=our_attributes)
42
>>> simpleeval("foo.hidden", names={"foo": Foo()}, allowed_attrs=our_attributes)
simpleeval.FeatureNotAvailable: Sorry, 'hidden' access not allowed on 'Foo'
will now allow access to `foo.bar` but not allow anything else.


Other...
--------

The library supports Python 3.9 and higher.

The initial idea came from J.F. Sebastian on Stack Overflow
( http://stackoverflow.com/a/9558001/1973500 ) with modifications and many improvements,
see the head of the main file for contributors list.
Expand Down
2 changes: 1 addition & 1 deletion simpleeval.py
Original file line number Diff line number Diff line change
Expand Up @@ -757,7 +757,7 @@ def _eval_attribute(self, node):
)
if node.attr not in allowed_attrs:
raise FeatureNotAvailable(
f"Sorry, '{node.attr}' access not allowed on '{type_to_check}'"
f"Sorry, '.{node.attr}' access not allowed on '{type_to_check}'"
)

# Maybe the base object is an actual object, not just a dict
Expand Down
18 changes: 18 additions & 0 deletions test_simpleeval.py
Original file line number Diff line number Diff line change
Expand Up @@ -1379,6 +1379,24 @@ def bar(self):

simple_eval("foo.bar()", names={"foo": Foo()}, allowed_attrs=extended_attrs)

def test_disallowed_extra_attr(self):
class Foo:
bar = 42
hidden = 100

assert Foo().bar == 42

extended_attrs = BASIC_ALLOWED_ATTRS.copy()
extended_attrs[Foo] = {"bar"}

self.assertEqual(
simple_eval("foo.bar", names={"foo": Foo()}, allowed_attrs=extended_attrs), 42
)
with self.assertRaisesRegex(FeatureNotAvailable, r".*'\.hidden' access not allowed.*"):
self.assertEqual(
simple_eval("foo.hidden", names={"foo": Foo()}, allowed_attrs=extended_attrs), 42
)

def test_breakout_via_generator(self):
# Thanks decorator-factory
class Foo:
Expand Down

0 comments on commit 8b9364a

Please sign in to comment.