diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index de73dcda30..f7d285887e 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -502,6 +502,10 @@ class JavascriptResponse(LoncapaResponse): return tmp_env def call_node(self, args): + # Node.js code is un-sandboxed. If the XModuleSystem says we aren't + # allowed to run unsafe code, then stop now. + if not self.system.can_execute_unsafe_code: + raise LoncapaProblemError("Execution of unsafe Javascript code is not allowed.") subprocess_args = ["node"] subprocess_args.extend(args) diff --git a/common/lib/capa/capa/tests/__init__.py b/common/lib/capa/capa/tests/__init__.py index d989405951..da3a3524f4 100644 --- a/common/lib/capa/capa/tests/__init__.py +++ b/common/lib/capa/capa/tests/__init__.py @@ -40,6 +40,7 @@ def test_system(): node_path=os.environ.get("NODE_PATH", "/usr/local/lib/node_modules"), anonymous_student_id='student', cache=None, + can_execute_unsafe_code=False, ) return the_system diff --git a/common/lib/capa/capa/tests/test_responsetypes.py b/common/lib/capa/capa/tests/test_responsetypes.py index 9cafef23d6..af6bdf823c 100644 --- a/common/lib/capa/capa/tests/test_responsetypes.py +++ b/common/lib/capa/capa/tests/test_responsetypes.py @@ -12,7 +12,7 @@ import textwrap import mock import textwrap -from . import new_loncapa_problem +from . import new_loncapa_problem, test_system from capa.responsetypes import LoncapaProblemError, \ StudentInputError, ResponseError @@ -30,9 +30,9 @@ class ResponseTest(unittest.TestCase): if self.xml_factory_class: self.xml_factory = self.xml_factory_class() - def build_problem(self, **kwargs): + def build_problem(self, system=None, **kwargs): xml = self.xml_factory.build_xml(**kwargs) - return new_loncapa_problem(xml) + return new_loncapa_problem(xml, system=system) def assert_grade(self, problem, submission, expected_correctness, msg=None): input_dict = {'1_2_1': submission} @@ -746,16 +746,37 @@ class JavascriptResponseTest(ResponseTest): coffee_file_path = os.path.dirname(__file__) + "/test_files/js/*.coffee" os.system("coffee -c %s" % (coffee_file_path)) - problem = self.build_problem(generator_src="test_problem_generator.js", - grader_src="test_problem_grader.js", - display_class="TestProblemDisplay", - display_src="test_problem_display.js", - param_dict={'value': '4'}) + system = test_system() + system.can_execute_unsafe_code = True + problem = self.build_problem( + system=system, + generator_src="test_problem_generator.js", + grader_src="test_problem_grader.js", + display_class="TestProblemDisplay", + display_src="test_problem_display.js", + param_dict={'value': '4'}, + ) # Test that we get graded correctly self.assert_grade(problem, json.dumps({0: 4}), "correct") self.assert_grade(problem, json.dumps({0: 5}), "incorrect") + def test_cant_execute_javascript(self): + # If the system says to disallow unsafe code execution, then making + # this problem will raise an exception. + system = test_system() + system.can_execute_unsafe_code = False + + with self.assertRaises(LoncapaProblemError): + problem = self.build_problem( + system=system, + generator_src="test_problem_generator.js", + grader_src="test_problem_grader.js", + display_class="TestProblemDisplay", + display_src="test_problem_display.js", + param_dict={'value': '4'}, + ) + class NumericalResponseTest(ResponseTest): from response_xml_factory import NumericalResponseXMLFactory diff --git a/common/lib/xmodule/xmodule/x_module.py b/common/lib/xmodule/xmodule/x_module.py index 31a32eb6dc..268eb4b40d 100644 --- a/common/lib/xmodule/xmodule/x_module.py +++ b/common/lib/xmodule/xmodule/x_module.py @@ -702,6 +702,7 @@ class ModuleSystem(object): open_ended_grading_interface=None, s3_interface=None, cache=None, + can_execute_unsafe_code=False, ): ''' Create a closure around the system environment. @@ -749,6 +750,9 @@ class ModuleSystem(object): .get(key) returns an object from the cache or None. .set(key, value, timeout_secs=None) stores a value in the cache with a timeout. + can_execute_unsafe_code - A boolean, whether or not to allow the execution + of unsafe, unsandboxed code. + ''' self.ajax_url = ajax_url self.xqueue = xqueue @@ -774,6 +778,7 @@ class ModuleSystem(object): self.s3_interface = s3_interface self.cache = cache or DoNothingCache() + self.can_execute_unsafe_code = can_execute_unsafe_code def get(self, attr): ''' provide uniform access to attributes (like etree).'''