def test_print_stmt__simple_prints(): glb = {'_print_': PrintCollector, '_getattr_': None} code, errors = compile_restricted_exec(ALLOWED_PRINT_STATEMENT)[:2] assert code is not None assert errors == () exec(code, glb) assert glb['_print']() == 'Hello World!\n' code, errors = compile_restricted_exec( ALLOWED_PRINT_STATEMENT_WITH_NO_NL)[:2] assert code is not None assert errors == () exec(code, glb) assert glb['_print']() == 'Hello World!' code, errors = compile_restricted_exec(ALLOWED_MULTI_PRINT_STATEMENT)[:2] assert code is not None assert errors == () exec(code, glb) assert glb['_print']() == 'Hello World! Hello Earth!\n' code, errors = compile_restricted_exec(ALLOWED_PRINT_TUPLE)[:2] assert code is not None assert errors == () exec(code, glb) assert glb['_print']() == "Hello World!\n" code, errors = compile_restricted_exec(ALLOWED_PRINT_MULTI_TUPLE)[:2] assert code is not None assert errors == () exec(code, glb) assert glb['_print']() == "('Hello World!', 'Hello Earth!')\n"
def test_RestrictingNodeTransformer__visit_Lambda__6(): """It prevents arguments starting with `_` in nested tuple unpacking.""" result = compile_restricted_exec("lambda (a, (c, (_bad, c))): None") # RestrictedPython.compile.compile_restricted_exec on Python 2 renders # the error message twice. This is necessary as otherwise *_bad and # **_bad would be allowed. assert lambda_err_msg in result.errors
def test_RestrictingNodeTransformer__visit_FunctionDef__6(): """It prevents function arguments starting with `_` in tuples.""" result = compile_restricted_exec("def foo(a, (c, (_bad, c))): pass") # RestrictedPython.compile.compile_restricted_exec on Python 2 renders # the error message twice. This is necessary as otherwise *_bad and # **_bad would be allowed. assert functiondef_err_msg in result.errors
def test_with_stmt_multi_ctx_unpack_sequence(mocker): result = compile_restricted_exec(WITH_STMT_MULTI_CTX_WITH_UNPACK_SEQUENCE) assert result.errors == () @contextlib.contextmanager def ctx1(): yield (1, (2, 3)) @contextlib.contextmanager def ctx2(): yield (4, 5), (6, 7) _getiter_ = mocker.stub() _getiter_.side_effect = lambda ob: ob glb = { '_getiter_': _getiter_, '_unpack_sequence_': guarded_unpack_sequence } exec(result.code, glb) ret = glb['call'](ctx1, ctx2) assert ret == (1, 2, 3, 4, 5, 6, 7) _getiter_.assert_has_calls([ mocker.call((1, (2, 3))), mocker.call((2, 3)), mocker.call(((4, 5), (6, 7))), mocker.call((4, 5)), mocker.call((6, 7)) ])
def run_test(program: str, *input_policies) -> bool: lcls = { f'dp{index}': get_dummy_pair(policy, index) for index, policy in enumerate(input_policies) } lcls.update(gen_module_namespace()) lcls['ret'] = ret lcls['edit'] = edit lcls['double'] = double lcls['Collection'] = Collection lcls['display'] = display lcls['test'] = gen_dummy_fn('test') lcls['filter'] = gen_dummy_fn('filter') lcls['view'] = gen_dummy_fn('view') glbls = {'__builtins__': safe_builtins} while True: try: compile_results = compile_restricted_exec(program) if compile_results.errors: raise Exception(compile_results.errors) else: exec(compile_results.code, glbls, lcls) return True except: print(traceback.format_exc()) return False
def test_RestrictingNodeTransformer__visit_Name__3(): """It denies a function name starting in `_`.""" result = compile_restricted_exec( BAD_NAME_OVERRIDE_OVERRIDE_GUARD_WITH_FUNCTION) assert result.errors == ( 'Line 2: "_getattr" is an invalid variable name because it ' 'starts with "_"', )
def test_RestrictingNodeTransformer__visit_Lambda__2(): """It prevents keyword arguments starting with `_`.""" result = compile_restricted_exec("lambda _bad=1: None") # RestrictedPython.compile.compile_restricted_exec on Python 2 renders # the error message twice. This is necessary as otherwise *_bad and **_bad # would be allowed. assert lambda_err_msg in result.errors
def test_print_stmt_conditional_print(): code, errors = compile_restricted_exec(CONDITIONAL_PRINT)[:2] glb = {'_print_': PrintCollector, '_getattr_': None} exec(code, glb) assert glb['func'](True) == '1\n' assert glb['func'](False) == ''
def test_print_stmt_no_new_scope(): code, errors = compile_restricted_exec(NO_PRINT_SCOPES)[:2] glb = {'_print_': PrintCollector, '_getattr_': None} exec(code, glb) ret = glb['class_scope']() assert ret == 'a\n'
def execute(users_secrets, program, app_id=None, app_module=None): json_output = dict() # object to interact with the program result = Result() glbls = { '__builtins__': safe_builtins, '_getitem_': default_guarded_getitem } lcls = assemble_locals(result=result, users_secrets=users_secrets, app_id=app_id, debug=True) try: c_program = compile_restricted_exec(program) exec(c_program, glbls, lcls) except: print(traceback.format_exc()) json_output = {'result': 'error', 'traceback': traceback.format_exc()} return json_output json_output['stored_items'] = result._stored_keys json_output['encrypted_data'] = result._encrypted_data json_output['data'] = result._dp_pair_data json_output['result'] = 'ok' return json_output
def test_print_stmt__with_printed_no_print(): code, errors, warnings = compile_restricted_exec(WARN_PRINTED_NO_PRINT)[:3] assert code is not None assert errors == () assert warnings == [ "Line 2: Doesn't print, but reads 'printed' variable."]
def execute(hass, filename, source, data): """Execute a script.""" from RestrictedPython import compile_restricted_exec from RestrictedPython.Guards import safe_builtins, full_write_guard compiled = compile_restricted_exec(source, filename=filename) if compiled.errors: _LOGGER.error('Error loading script %s: %s', filename, ', '.join(compiled.errors)) return if compiled.warnings: _LOGGER.warning('Warning loading script %s: %s', filename, ', '.join(compiled.warnings)) restricted_globals = { '__builtins__': safe_builtins, '_print_': StubPrinter, '_getattr_': getattr, '_write_': full_write_guard, } local = { 'hass': hass, 'data': data, 'logger': logging.getLogger('{}.{}'.format(__name__, filename)) } try: _LOGGER.info('Executing %s: %s', filename, data) # pylint: disable=exec-used exec(compiled.code, restricted_globals, local) except Exception as err: # pylint: disable=broad-except _LOGGER.exception('Error executing script %s: %s', filename, err)
def test_print_stmt__with_printed_no_print_nested(): code, errors, warnings = compile_restricted_exec( WARN_PRINTED_NO_PRINT_NESTED)[:3] assert code is not None assert errors == () assert warnings == ["Line 3: Doesn't print, but reads 'printed' variable."]
def test_call_py2_builtins(): """It should not be allowed to access global __builtins__ in Python2.""" result = compile_restricted_exec('__builtins__["getattr"]') assert result.code is None assert result.errors == ( 'Line 1: "__builtins__" is an invalid variable name because it starts with "_"', ) # NOQA: E501
def test_call_breakpoint(): """The Python3.7+ builtin function breakpoint should not be used and is forbidden in RestrictedPython. """ result = compile_restricted_exec('breakpoint()') assert result.errors == ('Line 1: "breakpoint" is a reserved name.',) assert result.code is None
def test_async_yield_from(): """`yield from` statement should be allowed.""" result = compile_restricted_exec(ASYNC_YIELD_FORM_EXAMPLE) assert result.errors == ( 'Line 4: AsyncFunctionDef statements are not allowed.', ) assert result.code is None
def execute(hass, filename, source, data=None): """Execute Python source.""" compiled = compile_restricted_exec(source, filename=filename) if compiled.errors: _LOGGER.error("Error loading script %s: %s", filename, ", ".join(compiled.errors)) return if compiled.warnings: _LOGGER.warning("Warning loading script %s: %s", filename, ", ".join(compiled.warnings)) def protected_getattr(obj, name, default=None): """Restricted method to get attributes.""" if name.startswith("async_"): raise ScriptError("Not allowed to access async methods") if (obj is hass and name not in ALLOWED_HASS or obj is hass.bus and name not in ALLOWED_EVENTBUS or obj is hass.states and name not in ALLOWED_STATEMACHINE or obj is hass.services and name not in ALLOWED_SERVICEREGISTRY or obj is dt_util and name not in ALLOWED_DT_UTIL or obj is datetime and name not in ALLOWED_DATETIME or isinstance(obj, TimeWrapper) and name not in ALLOWED_TIME): raise ScriptError( f"Not allowed to access {obj.__class__.__name__}.{name}") return getattr(obj, name, default) builtins = safe_builtins.copy() builtins.update(utility_builtins) builtins["datetime"] = datetime builtins["sorted"] = sorted builtins["time"] = TimeWrapper() builtins["dt_util"] = dt_util logger = logging.getLogger(f"{__name__}.{filename}") restricted_globals = { "__builtins__": builtins, "_print_": StubPrinter, "_getattr_": protected_getattr, "_write_": full_write_guard, "_getiter_": iter, "_getitem_": default_guarded_getitem, "_iter_unpack_sequence_": guarded_iter_unpack_sequence, "_unpack_sequence_": guarded_unpack_sequence, "hass": hass, "data": data or {}, "logger": logger, } try: _LOGGER.info("Executing %s: %s", filename, data) # pylint: disable=exec-used exec(compiled.code, restricted_globals) except ScriptError as err: logger.error("Error executing script: %s", err) except Exception as err: # pylint: disable=broad-except logger.exception("Error executing script: %s", err)
def test_RestrictingNodeTransformer__visit_Call__1(): """It compiles a function call successfully and returns the used name.""" result = compile_restricted_exec('a = max([1, 2, 3])') assert result.errors == () loc = {} exec(result.code, {}, loc) assert loc['a'] == 3 assert result.used_names == {'max': True}
def test_RestrictingNodeTransformer__visit_ClassDef__4(): """It does not allow to pass a metaclass to class definitions.""" result = compile_restricted_exec(EXPLICIT_METACLASS) assert result.errors == ( 'Line 2: The keyword argument "metaclass" is not allowed.',) assert result.code is None
def test_import_py3_builtins(): """It should not be allowed to access global builtins in Python3.""" result = compile_restricted_exec(BUILTINS_EXAMPLE) assert result.code is None assert result.errors == ( 'Line 2: "builtins" is a reserved name.', 'Line 4: "builtins" is a reserved name.' )
def test_import_py2_as_builtins(): """It should not be allowed to access global __builtins__ in Python2.""" result = compile_restricted_exec(__BUILTINS_EXAMPLE) assert result.code is None assert result.errors == ( 'Line 2: "__builtins__" is an invalid variable name because it starts with "_"', # NOQA: E501 'Line 4: "__builtins__" is an invalid variable name because it starts with "_"' # NOQA: E501 )
def test_RestrictingNodeTransformer__visit_ClassDef__4(): """It does not allow to pass a metaclass to class definitions.""" result = compile_restricted_exec(EXPLICIT_METACLASS) assert result.errors == ( 'Line 2: The keyword argument "metaclass" is not allowed.', ) assert result.code is None
def test_print_stmt__with_print_no_printed(): code, errors, warnings = compile_restricted_exec(WARN_PRINT_NO_PRINTED)[:3] assert code is not None assert errors == () assert warnings == [ "Line 2: Prints, but never reads 'printed' variable." ]
def test_print_stmt__nested_print_collector(mocker): code, errors = compile_restricted_exec(INJECT_PRINT_COLLECTOR_NESTED)[:2] glb = {"_print_": PrintCollector, '_getattr_': None} exec(code, glb) ret = glb['main']() assert ret == 'inner\nf1\nf2main\n'
def test_iterate_over_dict_items_safe(): glb = safe_globals.copy() glb['_getiter_'] = default_guarded_getiter glb['_iter_unpack_sequence_'] = guarded_iter_unpack_sequence result = compile_restricted_exec(ITERATE_OVER_DICT_ITEMS) assert result.code is not None assert result.errors == () exec(result.code, glb, None)
def test_iterate_over_dict_items_plain(): glb = {} result = compile_restricted_exec(ITERATE_OVER_DICT_ITEMS) assert result.code is not None assert result.errors == () with pytest.raises(NameError) as excinfo: exec(result.code, glb, None) assert "name '_iter_unpack_sequence_' is not defined" in str(excinfo.value)
def test_compile__compile_restricted_exec__4(): """It does not return code on a SyntaxError.""" result = compile_restricted_exec('asdf|') assert result.code is None assert result.warnings == [] assert result.used_names == {} assert result.errors == ( "Line 1: SyntaxError: invalid syntax at statement: 'asdf|'", )
def test_transform(): """It compiles a function call successfully and returns the used name.""" result = compile_restricted_exec('a = f"{max([1, 2, 3])}"') assert result.errors == () loc = {} exec(result.code, {}, loc) assert loc['a'] == '3' assert result.used_names == {'max': True}
def test_compile__compile_restricted_exec__2(): """It compiles without restrictions if there is no policy.""" result = compile_restricted_exec('_a = 42', policy=None) assert result.errors == () assert result.warnings == [] assert result.used_names == {} glob = {} exec(result.code, glob) assert glob['_a'] == 42
def execute(hass, filename, source, data=None): """Execute Python source.""" from RestrictedPython import compile_restricted_exec from RestrictedPython.Guards import safe_builtins, full_write_guard, \ guarded_iter_unpack_sequence, guarded_unpack_sequence from RestrictedPython.Utilities import utility_builtins from RestrictedPython.Eval import default_guarded_getitem compiled = compile_restricted_exec(source, filename=filename) if compiled.errors: _LOGGER.error('Error loading script %s: %s', filename, ', '.join(compiled.errors)) return if compiled.warnings: _LOGGER.warning('Warning loading script %s: %s', filename, ', '.join(compiled.warnings)) def protected_getattr(obj, name, default=None): """Restricted method to get attributes.""" # pylint: disable=too-many-boolean-expressions if name.startswith('async_'): raise ScriptError('Not allowed to access async methods') elif (obj is hass and name not in ALLOWED_HASS or obj is hass.bus and name not in ALLOWED_EVENTBUS or obj is hass.states and name not in ALLOWED_STATEMACHINE or obj is hass.services and name not in ALLOWED_SERVICEREGISTRY): raise ScriptError('Not allowed to access {}.{}'.format( obj.__class__.__name__, name)) return getattr(obj, name, default) builtins = safe_builtins.copy() builtins.update(utility_builtins) builtins['datetime'] = datetime restricted_globals = { '__builtins__': builtins, '_print_': StubPrinter, '_getattr_': protected_getattr, '_write_': full_write_guard, '_getiter_': iter, '_getitem_': default_guarded_getitem, '_iter_unpack_sequence_': guarded_iter_unpack_sequence, '_unpack_sequence_': guarded_unpack_sequence, } logger = logging.getLogger('{}.{}'.format(__name__, filename)) local = {'hass': hass, 'data': data or {}, 'logger': logger} try: _LOGGER.info('Executing %s: %s', filename, data) # pylint: disable=exec-used exec(compiled.code, restricted_globals, local) except ScriptError as err: logger.error('Error executing script: %s', err) except Exception as err: # pylint: disable=broad-except logger.exception('Error executing script: %s', err)
def execute(hass, filename, source, data=None): """Execute Python source.""" from RestrictedPython import compile_restricted_exec from RestrictedPython.Guards import safe_builtins, full_write_guard from RestrictedPython.Utilities import utility_builtins from RestrictedPython.Eval import default_guarded_getitem compiled = compile_restricted_exec(source, filename=filename) if compiled.errors: _LOGGER.error('Error loading script %s: %s', filename, ', '.join(compiled.errors)) return if compiled.warnings: _LOGGER.warning('Warning loading script %s: %s', filename, ', '.join(compiled.warnings)) def protected_getattr(obj, name, default=None): """Restricted method to get attributes.""" # pylint: disable=too-many-boolean-expressions if name.startswith('async_'): raise ScriptError('Not allowed to access async methods') elif (obj is hass and name not in ALLOWED_HASS or obj is hass.bus and name not in ALLOWED_EVENTBUS or obj is hass.states and name not in ALLOWED_STATEMACHINE or obj is hass.services and name not in ALLOWED_SERVICEREGISTRY): raise ScriptError('Not allowed to access {}.{}'.format( obj.__class__.__name__, name)) return getattr(obj, name, default) builtins = safe_builtins.copy() builtins.update(utility_builtins) restricted_globals = { '__builtins__': builtins, '_print_': StubPrinter, '_getattr_': protected_getattr, '_write_': full_write_guard, '_getiter_': iter, '_getitem_': default_guarded_getitem } logger = logging.getLogger('{}.{}'.format(__name__, filename)) local = { 'hass': hass, 'data': data or {}, 'logger': logger } try: _LOGGER.info('Executing %s: %s', filename, data) # pylint: disable=exec-used exec(compiled.code, restricted_globals, local) except ScriptError as err: logger.error('Error executing script: %s', err) except Exception as err: # pylint: disable=broad-except logger.exception('Error executing script: %s', err)
def test_visit_invalid_variable_name(): """Accessing private attributes is forbidden. This is just a smoke test to validate that restricted exec is used in the run-time evaluation of f-strings. """ result = compile_restricted_exec('f"{__init__}"') assert result.errors == ( 'Line 1: "__init__" is an invalid variable name because it starts with "_"', # NOQA: E501 )
def test_f_string_self_documenting_expressions(): """Checks if f-string self-documenting expressions is checked.""" result = compile_restricted_exec( f_string_self_documenting_expressions_example, ) assert result.errors == () glb = {'_print_': PrintCollector, '_getattr_': None} exec(result.code, glb) assert glb['_print']() == "user='******' member_since=datetime.date(1975, 7, 31)\n" # NOQA: E501
def test_compile__compile_restricted_exec__1(): """It returns a CompileResult on success.""" result = compile_restricted_exec('a = 42') assert result.__class__ == CompileResult assert result.errors == () assert result.warnings == [] assert result.used_names == {} glob = {} exec(result.code, glob) assert glob['a'] == 42
def test_yield(): """`yield` statement should be allowed.""" result = compile_restricted_exec(YIELD_EXAMPLE) assert result.errors == () assert result.code is not None local = {} exec(result.code, {}, local) test_generator = local['test_generator'] exec_result = list(test_generator()) assert exec_result == [42]
def test_RestrictingNodeTransformer__module_func_def_name_call(): """It forbids definition and usage of magic methods as functions ... ... at module level. """ result = compile_restricted_exec(BLACKLISTED_FUNC_NAMES_CALL_TEST) # assert result.errors == ('Line 1: ') assert result.errors == ( 'Line 2: "__init__" is an invalid variable name because it starts with "_"', # NOQA: E501 'Line 5: "__init__" is an invalid variable name because it starts with "_"', # NOQA: E501 )
def test_safer_getattr__underscore_name(): """It prevents accessing an attribute which starts with an underscore.""" result = compile_restricted_exec(GETATTR_UNDERSCORE_NAME) assert result.errors == () assert result.warnings == [] glb = safe_globals.copy() glb['getattr'] = safer_getattr with pytest.raises(AttributeError) as err: exec(result.code, glb, {}) assert ( '"__class__" is an invalid attribute name because it starts with "_"' == str(err.value))
def test_print_stmt__fail_with_none_target(mocker): code, errors = compile_restricted_exec('print >> None, "test"')[:2] assert code is not None assert errors == () glb = {'_getattr_': getattr, '_print_': PrintCollector} with pytest.raises(AttributeError) as excinfo: exec(code, glb) assert "'NoneType' object has no attribute 'write'" in str(excinfo.value)
def test_yield_from(): """`yield from` statement should be allowed.""" result = compile_restricted_exec(YIELD_FORM_EXAMPLE) assert result.errors == () assert result.code is not None def my_external_generator(): my_list = [1, 2, 3, 4, 5] for elem in my_list: yield(elem) local = {} exec(result.code, {}, local) reader_wapper = local['reader_wapper'] exec_result = list(reader_wapper(my_external_generator())) assert exec_result == [1, 2, 3, 4, 5]
def test_print_stmt__protect_chevron_print(mocker): code, errors = compile_restricted_exec( PROTECT_PRINT_STATEMENT_WITH_CHEVRON)[:2] _getattr_ = mocker.stub() _getattr_.side_effect = getattr glb = {'_getattr_': _getattr_, '_print_': PrintCollector} exec(code, glb) stream = mocker.stub() stream.write = mocker.stub() glb['print_into_stream'](stream) stream.write.assert_has_calls([ mocker.call('Hello World!'), mocker.call('\n') ]) _getattr_.assert_called_once_with(stream, 'write')
def test_Nonlocal(): result = compile_restricted_exec(NONLOCAL_EXAMPLE) assert result.errors == ('Line 5: Nonlocal statements are not allowed.',) assert result.code is None
def test_RestrictingNodeTransformer__visit_Lambda__8(): """It prevents arguments starting with `_` in weird lambdas.""" result = compile_restricted_exec(BAD_ARG_IN_LAMBDA) # On Python 2 the first error message is contained twice: assert lambda_err_msg in result.errors
def test_RestrictingNodeTransformer__visit_Lambda__7(): """It prevents arguments starting with `_` together with a single `*`.""" result = compile_restricted_exec("lambda good, *, _bad: None") assert result.errors == (lambda_err_msg,)
def test_RestrictingNodeTransformer__visit_Lambda__4(): """It prevents ** arguments starting with `_`.""" result = compile_restricted_exec("lambda **_bad: None") assert result.errors == (lambda_err_msg,)
def test_RestrictingNodeTransformer__visit_Attribute__2(): """It is an error if a bad attribute name is used.""" result = compile_restricted_exec(BAD_ATTR_ROLES) assert result.errors == ( 'Line 3: "abc__roles__" is an invalid attribute name because it ' 'ends with "__roles__".',)
def test_RestrictingNodeTransformer__visit_Attribute__4(): """It allows `_` as attribute name.""" result = compile_restricted_exec(ALLOW_UNDERSCORE_ONLY) assert result.errors == ()