feat: Upgrade to codejail 4.0.0 (#36916)
This brings an important security improvement -- codejail won't default to running in unsafe mode, which can happen if certain configuration errors are present. Properly configured installations shouldn't be affected. We just need to adjust some unit tests to opt into unsafe mode. Changes: - Update `edx-codejail` dependency to [version 4.0.0](https://github.com/openedx/codejail/blob/master/CHANGELOG.rst#400---2025-06-13) - Define a `use_unsafe_codejail` decorator that allows running a unit test (or entire TestCase class) in unsafe mode - Use that decorator as needed, based on which tests started failing
This commit is contained in:
@@ -15,6 +15,7 @@ from markupsafe import Markup
|
||||
from xmodule.capa.correctmap import CorrectMap
|
||||
from xmodule.capa.responsetypes import LoncapaProblemError
|
||||
from xmodule.capa.tests.helpers import new_loncapa_problem
|
||||
from xmodule.capa.tests.test_util import use_unsafe_codejail
|
||||
from openedx.core.djangolib.markup import HTML
|
||||
|
||||
|
||||
@@ -23,6 +24,7 @@ FEATURES_WITH_GRADING_METHOD_IN_PROBLEMS['ENABLE_GRADING_METHOD_IN_PROBLEMS'] =
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
@use_unsafe_codejail()
|
||||
class CAPAProblemTest(unittest.TestCase):
|
||||
""" CAPA problem related tests"""
|
||||
|
||||
@@ -424,6 +426,7 @@ class CAPAProblemTest(unittest.TestCase):
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
@use_unsafe_codejail()
|
||||
class CAPAMultiInputProblemTest(unittest.TestCase):
|
||||
""" TestCase for CAPA problems with multiple inputtypes """
|
||||
|
||||
|
||||
@@ -11,12 +11,14 @@ from unittest import mock
|
||||
import ddt
|
||||
from lxml import etree
|
||||
from xmodule.capa.tests.helpers import new_loncapa_problem, mock_capa_system
|
||||
from xmodule.capa.tests.test_util import use_unsafe_codejail
|
||||
from openedx.core.djangolib.markup import HTML
|
||||
|
||||
from .response_xml_factory import CustomResponseXMLFactory, StringResponseXMLFactory
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
@use_unsafe_codejail()
|
||||
class CapaHtmlRenderTest(unittest.TestCase):
|
||||
"""
|
||||
CAPA HTML rendering tests class.
|
||||
|
||||
@@ -37,6 +37,7 @@ from xmodule.capa.tests.response_xml_factory import (
|
||||
SymbolicResponseXMLFactory,
|
||||
TrueFalseResponseXMLFactory
|
||||
)
|
||||
from xmodule.capa.tests.test_util import use_unsafe_codejail
|
||||
from xmodule.capa.util import convert_files_to_filenames
|
||||
from xmodule.capa.xqueue_interface import dateformat
|
||||
|
||||
@@ -108,6 +109,7 @@ class ResponseTest(unittest.TestCase):
|
||||
return str(rand.randint(0, 1e9))
|
||||
|
||||
|
||||
@use_unsafe_codejail()
|
||||
class MultiChoiceResponseTest(ResponseTest): # pylint: disable=missing-class-docstring
|
||||
xml_factory_class = MultipleChoiceResponseXMLFactory
|
||||
|
||||
@@ -375,6 +377,7 @@ class SymbolicResponseTest(ResponseTest): # pylint: disable=missing-class-docst
|
||||
assert correct_map.get_correctness('1_2_1') == expected_correctness
|
||||
|
||||
|
||||
@use_unsafe_codejail()
|
||||
class OptionResponseTest(ResponseTest): # pylint: disable=missing-class-docstring
|
||||
xml_factory_class = OptionResponseXMLFactory
|
||||
|
||||
@@ -422,6 +425,7 @@ class OptionResponseTest(ResponseTest): # pylint: disable=missing-class-docstri
|
||||
assert correct_map.get_property('1_2_1', 'answervariable') == '$a'
|
||||
|
||||
|
||||
@use_unsafe_codejail()
|
||||
class FormulaResponseTest(ResponseTest):
|
||||
"""
|
||||
Test the FormulaResponse class
|
||||
@@ -571,6 +575,7 @@ class FormulaResponseTest(ResponseTest):
|
||||
assert not list(problem.responders.values())[0].validate_answer('3*y+2*x')
|
||||
|
||||
|
||||
@use_unsafe_codejail()
|
||||
class StringResponseTest(ResponseTest): # pylint: disable=missing-class-docstring
|
||||
xml_factory_class = StringResponseXMLFactory
|
||||
|
||||
@@ -1124,6 +1129,7 @@ class CodeResponseTest(ResponseTest): # pylint: disable=missing-class-docstring
|
||||
assert output[answer_id]['msg'] == 'Invalid grader reply. Please contact the course staff.'
|
||||
|
||||
|
||||
@use_unsafe_codejail()
|
||||
class ChoiceResponseTest(ResponseTest): # pylint: disable=missing-class-docstring
|
||||
xml_factory_class = ChoiceResponseXMLFactory
|
||||
|
||||
@@ -1292,6 +1298,7 @@ class ChoiceResponseTest(ResponseTest): # pylint: disable=missing-class-docstri
|
||||
self.assert_grade(problem, ['choice_1', 'choice_3'], 'incorrect')
|
||||
|
||||
|
||||
@use_unsafe_codejail()
|
||||
class NumericalResponseTest(ResponseTest): # pylint: disable=missing-class-docstring
|
||||
xml_factory_class = NumericalResponseXMLFactory
|
||||
|
||||
@@ -1680,6 +1687,7 @@ class NumericalResponseTest(ResponseTest): # pylint: disable=missing-class-docs
|
||||
assert not responder.validate_answer('fish')
|
||||
|
||||
|
||||
@use_unsafe_codejail()
|
||||
class CustomResponseTest(ResponseTest): # pylint: disable=missing-class-docstring
|
||||
xml_factory_class = CustomResponseXMLFactory
|
||||
|
||||
@@ -2399,6 +2407,7 @@ class CustomResponseTest(ResponseTest): # pylint: disable=missing-class-docstri
|
||||
assert correct_map.get_msg('1_2_11') == '11'
|
||||
|
||||
|
||||
@use_unsafe_codejail()
|
||||
class SchematicResponseTest(ResponseTest):
|
||||
"""
|
||||
Class containing setup and tests for Schematic responsetype.
|
||||
@@ -2488,6 +2497,7 @@ class AnnotationResponseTest(ResponseTest): # lint-amnesty, pylint: disable=mis
|
||||
assert expected_points == actual_points, ('%s should have %d points' % (answer_id, expected_points))
|
||||
|
||||
|
||||
@use_unsafe_codejail()
|
||||
class ChoiceTextResponseTest(ResponseTest):
|
||||
"""
|
||||
Class containing setup and tests for ChoiceText responsetype.
|
||||
|
||||
@@ -6,7 +6,9 @@ Tests capa util
|
||||
|
||||
import unittest
|
||||
|
||||
import codejail.safe_exec
|
||||
import ddt
|
||||
from django.test.utils import TestContextDecorator
|
||||
from lxml import etree
|
||||
|
||||
from xmodule.capa.tests.helpers import mock_capa_system
|
||||
@@ -167,3 +169,28 @@ class UtilTest(unittest.TestCase):
|
||||
expected_text = '$あなたあなたあなたあなた あなたhi'
|
||||
contextual_text = contextualize_text(text, context)
|
||||
assert expected_text == contextual_text
|
||||
|
||||
|
||||
class use_unsafe_codejail(TestContextDecorator):
|
||||
"""
|
||||
Tell codejail to run in unsafe mode for the scope of the decorator.
|
||||
Use this as a decorator on Django TestCase classes or methods.
|
||||
|
||||
This is needed because codejail has significant OS-level setup requirements
|
||||
which we don't even attempt to fulfill for unit testing purposes. Running
|
||||
tests in unsafe mode (that is, running code executions in-process, with no
|
||||
sandboxing) is only safe because we control the contents of the unit tests.
|
||||
It's not a perfect replica of how safe mode operates but it's generally good
|
||||
enough for testing the integration and overall behavior.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.old_be_unsafe = None
|
||||
super().__init__()
|
||||
|
||||
def enable(self):
|
||||
self.old_be_unsafe = codejail.safe_exec.ALWAYS_BE_UNSAFE
|
||||
codejail.safe_exec.ALWAYS_BE_UNSAFE = True
|
||||
|
||||
def disable(self):
|
||||
codejail.safe_exec.ALWAYS_BE_UNSAFE = self.old_be_unsafe
|
||||
|
||||
Reference in New Issue
Block a user