##// END OF EJS Templates
Guard against custom properties
krassowski -
Show More
@@ -3,6 +3,7 b' from typing import ('
3 3 Callable,
4 4 Dict,
5 5 Set,
6 Sequence,
6 7 Tuple,
7 8 NamedTuple,
8 9 Type,
@@ -113,18 +114,30 b' class EvaluationPolicy:'
113 114 return True
114 115
115 116
117 def _get_external(module_name: str, access_path: Sequence[str]):
118 """Get value from external module given a dotted access path.
119
120 Raises:
121 * `KeyError` if module is removed not found, and
122 * `AttributeError` if acess path does not match an exported object
123 """
124 member_type = sys.modules[module_name]
125 for attr in access_path:
126 member_type = getattr(member_type, attr)
127 return member_type
128
129
116 130 def _has_original_dunder_external(
117 131 value,
118 module_name,
119 access_path,
120 method_name,
132 module_name: str,
133 access_path: Sequence[str],
134 method_name: str,
121 135 ):
122 try:
123 136 if module_name not in sys.modules:
137 # LBYLB as it is faster
124 138 return False
125 member_type = sys.modules[module_name]
126 for attr in access_path:
127 member_type = getattr(member_type, attr)
139 try:
140 member_type = _get_external(module_name, access_path)
128 141 value_type = type(value)
129 142 if type(value) == member_type:
130 143 return True
@@ -199,12 +212,42 b' class SelectivePolicy(EvaluationPolicy):'
199 212 method_name="__getattr__",
200 213 )
201 214
215 accept = False
216
202 217 # Many objects do not have `__getattr__`, this is fine
203 218 if has_original_attr is None and has_original_attribute:
219 accept = True
220 else:
221 # Accept objects without modifications to `__getattr__` and `__getattribute__`
222 accept = has_original_attr and has_original_attribute
223
224 if accept:
225 # We still need to check for overriden properties.
226
227 value_class = type(value)
228 if not hasattr(value_class, attr):
204 229 return True
205 230
206 # Accept objects without modifications to `__getattr__` and `__getattribute__`
207 return has_original_attr and has_original_attribute
231 class_attr_val = getattr(value_class, attr)
232 is_property = isinstance(class_attr_val, property)
233
234 if not is_property:
235 return True
236
237 # Properties in allowed types are ok
238 if type(value) in self.allowed_getattr:
239 return True
240
241 # Properties in subclasses of allowed types may be ok if not changed
242 for module_name, *access_path in self.allowed_getattr_external:
243 try:
244 external_class = _get_external(module_name, access_path)
245 external_class_attr_val = getattr(external_class, attr)
246 except (KeyError, AttributeError):
247 return False # pragma: no cover
248 return class_attr_val == external_class_attr_val
249
250 return False
208 251
209 252 def can_get_item(self, value, item):
210 253 """Allow accessing `__getiitem__` of allow-listed instances unless it was not modified."""
@@ -1,3 +1,4 b''
1 from contextlib import contextmanager
1 2 from typing import NamedTuple
2 3 from functools import partial
3 4 from IPython.core.guarded_eval import (
@@ -25,6 +26,21 b' LIMITED_OR_HIGHER = [limited, unsafe, dangerous]'
25 26 MINIMAL_OR_HIGHER = [minimal, *LIMITED_OR_HIGHER]
26 27
27 28
29 @contextmanager
30 def module_not_installed(module: str):
31 import sys
32
33 try:
34 to_restore = sys.modules[module]
35 del sys.modules[module]
36 except KeyError:
37 to_restore = None
38 try:
39 yield
40 finally:
41 sys.modules[module] = to_restore
42
43
28 44 @dec.skip_without("pandas")
29 45 def test_pandas_series_iloc():
30 46 import pandas as pd
@@ -34,6 +50,32 b' def test_pandas_series_iloc():'
34 50 assert guarded_eval("data.iloc[0]", context) == 1
35 51
36 52
53 def test_rejects_custom_properties():
54 class BadProperty:
55 @property
56 def iloc(self):
57 return [None]
58
59 series = BadProperty()
60 context = limited(data=series)
61
62 with pytest.raises(GuardRejection):
63 guarded_eval("data.iloc[0]", context)
64
65
66 @dec.skip_without("pandas")
67 def test_accepts_non_overriden_properties():
68 import pandas as pd
69
70 class GoodProperty(pd.Series):
71 pass
72
73 series = GoodProperty([1], index=["a"])
74 context = limited(data=series)
75
76 assert guarded_eval("data.iloc[0]", context) == 1
77
78
37 79 @dec.skip_without("pandas")
38 80 def test_pandas_series():
39 81 import pandas as pd
@@ -472,9 +514,12 b' def test_assumption_instance_attr_do_not_matter():'
472 514 def __getattr__(self, k):
473 515 return "a"
474 516
517 def f(self):
518 return "b"
519
475 520 t = T()
476 t.__getitem__ = lambda f: "b"
477 t.__getattr__ = lambda f: "b"
521 t.__getitem__ = f
522 t.__getattr__ = f
478 523 assert t[1] == "a"
479 524 assert t[1] == "a"
480 525
General Comments 0
You need to be logged in to leave comments. Login now