##// END OF EJS Templates
Guard against custom properties
krassowski -
Show More
@@ -3,6 +3,7 b' from typing import ('
3 Callable,
3 Callable,
4 Dict,
4 Dict,
5 Set,
5 Set,
6 Sequence,
6 Tuple,
7 Tuple,
7 NamedTuple,
8 NamedTuple,
8 Type,
9 Type,
@@ -113,18 +114,30 b' class EvaluationPolicy:'
113 return True
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 def _has_original_dunder_external(
130 def _has_original_dunder_external(
117 value,
131 value,
118 module_name,
132 module_name: str,
119 access_path,
133 access_path: Sequence[str],
120 method_name,
134 method_name: str,
121 ):
135 ):
136 if module_name not in sys.modules:
137 # LBYLB as it is faster
138 return False
122 try:
139 try:
123 if module_name not in sys.modules:
140 member_type = _get_external(module_name, access_path)
124 return False
125 member_type = sys.modules[module_name]
126 for attr in access_path:
127 member_type = getattr(member_type, attr)
128 value_type = type(value)
141 value_type = type(value)
129 if type(value) == member_type:
142 if type(value) == member_type:
130 return True
143 return True
@@ -199,12 +212,42 b' class SelectivePolicy(EvaluationPolicy):'
199 method_name="__getattr__",
212 method_name="__getattr__",
200 )
213 )
201
214
215 accept = False
216
202 # Many objects do not have `__getattr__`, this is fine
217 # Many objects do not have `__getattr__`, this is fine
203 if has_original_attr is None and has_original_attribute:
218 if has_original_attr is None and has_original_attribute:
204 return True
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.
205
226
206 # Accept objects without modifications to `__getattr__` and `__getattribute__`
227 value_class = type(value)
207 return has_original_attr and has_original_attribute
228 if not hasattr(value_class, attr):
229 return True
230
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 def can_get_item(self, value, item):
252 def can_get_item(self, value, item):
210 """Allow accessing `__getiitem__` of allow-listed instances unless it was not modified."""
253 """Allow accessing `__getiitem__` of allow-listed instances unless it was not modified."""
@@ -1,3 +1,4 b''
1 from contextlib import contextmanager
1 from typing import NamedTuple
2 from typing import NamedTuple
2 from functools import partial
3 from functools import partial
3 from IPython.core.guarded_eval import (
4 from IPython.core.guarded_eval import (
@@ -25,6 +26,21 b' LIMITED_OR_HIGHER = [limited, unsafe, dangerous]'
25 MINIMAL_OR_HIGHER = [minimal, *LIMITED_OR_HIGHER]
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 @dec.skip_without("pandas")
44 @dec.skip_without("pandas")
29 def test_pandas_series_iloc():
45 def test_pandas_series_iloc():
30 import pandas as pd
46 import pandas as pd
@@ -34,6 +50,32 b' def test_pandas_series_iloc():'
34 assert guarded_eval("data.iloc[0]", context) == 1
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 @dec.skip_without("pandas")
79 @dec.skip_without("pandas")
38 def test_pandas_series():
80 def test_pandas_series():
39 import pandas as pd
81 import pandas as pd
@@ -472,9 +514,12 b' def test_assumption_instance_attr_do_not_matter():'
472 def __getattr__(self, k):
514 def __getattr__(self, k):
473 return "a"
515 return "a"
474
516
517 def f(self):
518 return "b"
519
475 t = T()
520 t = T()
476 t.__getitem__ = lambda f: "b"
521 t.__getitem__ = f
477 t.__getattr__ = lambda f: "b"
522 t.__getattr__ = f
478 assert t[1] == "a"
523 assert t[1] == "a"
479 assert t[1] == "a"
524 assert t[1] == "a"
480
525
General Comments 0
You need to be logged in to leave comments. Login now