Skip to content
4 changes: 2 additions & 2 deletions lms/djangoapps/courseware/tests/test_submitting_problems.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
OptionResponseXMLFactory,
SchematicResponseXMLFactory
)
from xmodule.capa.tests.test_util import use_unsafe_codejail
from xmodule.capa.tests.test_util import UseUnsafeCodejail
from xmodule.capa.xqueue_interface import XQueueInterface
from common.djangoapps.course_modes.models import CourseMode
from lms.djangoapps.courseware.models import BaseStudentModuleHistory, StudentModule
Expand Down Expand Up @@ -811,7 +811,7 @@ def test_three_files(self, mock_xqueue_post):
self.assertEqual(list(kwargs['files'].keys()), filenames.split())


@use_unsafe_codejail()
@UseUnsafeCodejail()
class TestPythonGradedResponse(TestSubmittingProblems):
"""
Check that we can submit a schematic and custom response, and it answers properly.
Expand Down
4 changes: 2 additions & 2 deletions lms/djangoapps/instructor_task/tests/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

from xmodule.capa.responsetypes import StudentInputError
from xmodule.capa.tests.response_xml_factory import CodeResponseXMLFactory, CustomResponseXMLFactory
from xmodule.capa.tests.test_util import use_unsafe_codejail
from xmodule.capa.tests.test_util import UseUnsafeCodejail
from lms.djangoapps.courseware.model_data import StudentModule
from lms.djangoapps.grades.api import CourseGradeFactory
from lms.djangoapps.instructor_task.api import (
Expand Down Expand Up @@ -73,7 +73,7 @@ def _assert_task_failure(self, entry_id, task_type, problem_url_name, expected_m

@ddt.ddt
@override_settings(RATELIMIT_ENABLE=False)
@use_unsafe_codejail()
@UseUnsafeCodejail()
class TestRescoringTask(TestIntegrationTask):
"""
Integration-style tests for rescoring problems in a background task.
Expand Down
5 changes: 5 additions & 0 deletions openedx/core/djangolib/tests/test_markup.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,13 @@ class FormatHtmlTest(unittest.TestCase):
("<a>нтмℓ-єѕ¢αρє∂</a>", "&lt;a&gt;нтмℓ-єѕ¢αρє∂&lt;/a&gt;"),
)
def test_simple(self, before_after):
"""Verify that plain text is safely HTML-escaped."""
(before, after) = before_after
assert str(Text(_(before))) == after # pylint: disable=translation-of-non-string
assert str(Text(before)) == after

def test_formatting(self):
"""Ensure Text.format correctly mixes escaped text with raw HTML."""
# The whole point of this function is to make sure this works:
out = Text(_("Point & click {start}here{end}!")).format(
start=HTML("<a href='http://edx.org'>"),
Expand All @@ -39,6 +41,7 @@ def test_formatting(self):
assert str(out) == "Point &amp; click <a href='http://edx.org'>here</a>!"

def test_nested_formatting(self):
"""Validate nested formatting where HTML contains formatted text."""
# Sometimes, you have plain text, with html inserted, and the html has
# plain text inserted. It gets twisty...
out = Text(_("Send {start}email{end}")).format(
Expand All @@ -48,6 +51,7 @@ def test_nested_formatting(self):
assert str(out) == "Send <a href='mailto:A&amp;B'>email</a>"

def test_mako(self):
"""Confirm Mako templates format Text/HTML objects with expected filters."""
# The default_filters used here have to match the ones in edxmako.
template = Template(
"""
Expand All @@ -64,6 +68,7 @@ def test_mako(self):
assert out.strip() == "A &amp; B & C"

def test_ungettext(self):
"""Check that ngettext output is properly formatted and HTML-escaped."""
for i in [1, 2]:
out = Text(ngettext("1 & {}", "2 & {}", i)).format(HTML("<>"))
assert out == f"{i} &amp; <>"
Expand Down
65 changes: 33 additions & 32 deletions openedx/core/lib/safe_lxml/xmlparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,45 +22,46 @@ class RestrictedElement(_etree.ElementBase):
__slots__ = ()
blacklist = (_etree._Entity, _etree._ProcessingInstruction, _etree._Comment) # pylint: disable=protected-access

def _filter(self, iterator): # pylint: disable=missing-function-docstring
def _filter(self, iterator):
"""Yield only elements not in the blacklist from the given iterator."""
blacklist = self.blacklist
for child in iterator:
if isinstance(child, blacklist):
continue
yield child

def __iter__(self):
iterator = super(RestrictedElement, self).__iter__() # pylint: disable=super-with-arguments
iterator = super().__iter__()
return self._filter(iterator)

def iterchildren(self, tag=None, reversed=False): # pylint: disable=redefined-builtin
iterator = super(RestrictedElement, self).iterchildren( # pylint: disable=super-with-arguments
tag=tag, reversed=reversed
)
"""Iterate over child elements while excluding blacklisted nodes."""
iterator = super().iterchildren(tag=tag, reversed=reversed)
return self._filter(iterator)

def iter(self, tag=None, *tags): # pylint: disable=keyword-arg-before-vararg
iterator = super(RestrictedElement, self).iter(tag=tag, *tags) # pylint: disable=super-with-arguments
"""Iterate over the element tree excluding blacklisted nodes."""
iterator = super().iter(tag=tag, *tags)
return self._filter(iterator)

def iterdescendants(self, tag=None, *tags): # pylint: disable=keyword-arg-before-vararg
iterator = super(RestrictedElement, self).iterdescendants( # pylint: disable=super-with-arguments
tag=tag, *tags
)
"""Iterate over descendants while filtering out blacklisted nodes."""
iterator = super().iterdescendants(tag=tag, *tags)
return self._filter(iterator)

def itersiblings(self, tag=None, preceding=False):
iterator = super(RestrictedElement, self).itersiblings( # pylint: disable=super-with-arguments
tag=tag, preceding=preceding
)
"""Iterate over siblings excluding blacklisted node types."""
iterator = super().itersiblings(tag=tag, preceding=preceding)
return self._filter(iterator)

def getchildren(self):
iterator = super(RestrictedElement, self).__iter__() # pylint: disable=super-with-arguments
"""Return a list of non-blacklisted child elements."""
iterator = super().__iter__()
return list(self._filter(iterator))

def getiterator(self, tag=None):
iterator = super(RestrictedElement, self).getiterator(tag) # pylint: disable=super-with-arguments
"""Iterate over the tree with blacklisted nodes filtered out."""
iterator = super().getiterator(tag)
return self._filter(iterator)


Expand All @@ -73,27 +74,30 @@ class GlobalParserTLS(threading.local):

element_class = RestrictedElement

def createDefaultParser(self): # pylint: disable=missing-function-docstring
def create_default_parser(self):
"""Create a secure XMLParser using the restricted element class."""
parser = _etree.XMLParser(**self.parser_config)
element_class = self.element_class
if self.element_class is not None:
lookup = _etree.ElementDefaultClassLookup(element=element_class)
parser.set_element_class_lookup(lookup)
return parser

def setDefaultParser(self, parser):
def set_default_parser(self, parser):
"""Store a thread-local default XML parser instance."""
self._default_parser = parser # pylint: disable=attribute-defined-outside-init

def getDefaultParser(self): # pylint: disable=missing-function-docstring
def get_default_parser(self):
"""Return the thread-local default parser, creating it if missing."""
parser = getattr(self, "_default_parser", None)
if parser is None:
parser = self.createDefaultParser()
self.setDefaultParser(parser)
parser = self.create_default_parser()
self.set_default_parser(parser)
return parser


_parser_tls = GlobalParserTLS()
getDefaultParser = _parser_tls.getDefaultParser
get_default_parser = _parser_tls.get_default_parser


def check_docinfo(elementtree, forbid_dtd=False, forbid_entities=True):
Expand All @@ -107,9 +111,7 @@ def check_docinfo(elementtree, forbid_dtd=False, forbid_entities=True):
raise DTDForbidden(docinfo.doctype, docinfo.system_url, docinfo.public_id)
if forbid_entities and not LXML3:
# lxml < 3 has no iterentities()
raise NotSupportedError(
"Unable to check for entity declarations in lxml 2.x"
) # pylint: disable=implicit-str-concat
raise NotSupportedError("Unable to check for entity declarations in lxml 2.x")

if forbid_entities:
for dtd in docinfo.internalDTD, docinfo.externalDTD:
Expand All @@ -119,29 +121,28 @@ def check_docinfo(elementtree, forbid_dtd=False, forbid_entities=True):
raise EntitiesForbidden(entity.name, entity.content, None, None, None, None)


def parse(
source, parser=None, base_url=None, forbid_dtd=False, forbid_entities=True
): # pylint: disable=missing-function-docstring
def parse(source, parser=None, base_url=None, forbid_dtd=False, forbid_entities=True):
"""Securely parse XML from a source and enforce DTD/entity restrictions."""
if parser is None:
parser = getDefaultParser()
parser = get_default_parser()
elementtree = _etree.parse(source, parser, base_url=base_url)
check_docinfo(elementtree, forbid_dtd, forbid_entities)
return elementtree


def fromstring(
text, parser=None, base_url=None, forbid_dtd=False, forbid_entities=True
): # pylint: disable=missing-function-docstring
def fromstring(text, parser=None, base_url=None, forbid_dtd=False, forbid_entities=True):
"""Securely parse XML from a string and validate docinfo."""
if parser is None:
parser = getDefaultParser()
parser = get_default_parser()
rootelement = _etree.fromstring(text, parser, base_url=base_url)
elementtree = rootelement.getroottree()
check_docinfo(elementtree, forbid_dtd, forbid_entities)
return rootelement


XML = fromstring
XML = fromstring # pylint: disable=invalid-name


def iterparse(*args, **kwargs):
"""Disabled XML iterparse function that always raises NotSupportedError."""
raise NotSupportedError("iterparse not available")
Loading
Loading