Python-evaluated problems were failing to render because they were trying to invoke `course_id.make_asset_key` in order to obtain the asset key of the custom Python ZIP file. Learning Core assets work differently (no asset keys, etc.), and, furthermore, we haven't really thought though how and whether we want to support custom Python ZIPs in libraries. So, this fix punts on supporting Python ZIP files in libraries for now, but enables rendering of advanced CAPA problems which don't rely on a ZIP. This brings us to parity with Legacy Libraries, which didn't support assets at all and thus didn't support Python ZIPs either. Fixes: https://github.com/openedx/edx-platform/issues/37447
82 lines
3.0 KiB
Python
82 lines
3.0 KiB
Python
# lint-amnesty, pylint: disable=missing-module-docstring
|
|
|
|
import re
|
|
|
|
from django.conf import settings
|
|
from opaque_keys.edx.keys import CourseKey, LearningContextKey
|
|
|
|
DEFAULT_PYTHON_LIB_FILENAME = 'python_lib.zip'
|
|
|
|
|
|
def course_code_library_asset_name():
|
|
"""
|
|
Return the asset name to use for course code libraries, defaulting to python_lib.zip.
|
|
"""
|
|
return getattr(settings, 'PYTHON_LIB_FILENAME', DEFAULT_PYTHON_LIB_FILENAME)
|
|
|
|
|
|
def can_execute_unsafe_code(course_id):
|
|
"""
|
|
Determine if this course is allowed to run unsafe code.
|
|
|
|
For use from the ModuleStore. Checks the `course_id` against a list of whitelisted
|
|
regexes.
|
|
|
|
Returns a boolean, true if the course can run outside the sandbox.
|
|
|
|
"""
|
|
# To decide if we can run unsafe code, we check the course id against
|
|
# a list of regexes configured on the server.
|
|
# If this is not defined in the environment variables then default to the most restrictive, which
|
|
# is 'no unsafe courses'
|
|
# TODO: This should be a database configuration, where we can mark individual courses as being
|
|
# safe/unsafe. Someone in the future should switch us over to that rather than using regexes
|
|
# in a settings file
|
|
# To others using this: the code as-is is brittle and likely to be changed in the future,
|
|
# as per the TODO, so please consider carefully before adding more values to COURSES_WITH_UNSAFE_CODE
|
|
for regex in getattr(settings, 'COURSES_WITH_UNSAFE_CODE', []):
|
|
if re.match(regex, str(course_id)):
|
|
return True
|
|
return False
|
|
|
|
|
|
def get_python_lib_zip(contentstore, context_key: LearningContextKey):
|
|
"""Return the bytes of the course code library file, if it exists."""
|
|
if not isinstance(context_key, CourseKey):
|
|
# Although Content Libraries V2 does support python-evaluated capa problems,
|
|
# it doesn't yet support supplementary python zip files.
|
|
return None
|
|
python_lib_filename = course_code_library_asset_name()
|
|
asset_key = context_key.make_asset_key("asset", python_lib_filename)
|
|
zip_lib = contentstore().find(asset_key, throw_on_not_found=False)
|
|
if zip_lib is not None:
|
|
return zip_lib.data
|
|
else:
|
|
return None
|
|
|
|
|
|
class SandboxService:
|
|
"""
|
|
A service which provides utilities for executing sandboxed Python code, for example, inside custom Python questions.
|
|
|
|
Args:
|
|
contentstore(function): function which creates an instance of xmodule.content.ContentStore
|
|
course_id(string or CourseLocator): identifier for the course
|
|
"""
|
|
def __init__(self, contentstore, course_id, **kwargs):
|
|
super().__init__(**kwargs)
|
|
self.contentstore = contentstore
|
|
self.course_id = course_id
|
|
|
|
def can_execute_unsafe_code(self):
|
|
"""
|
|
Returns a boolean, true if the course can run outside the sandbox.
|
|
"""
|
|
return can_execute_unsafe_code(self.course_id)
|
|
|
|
def get_python_lib_zip(self):
|
|
"""
|
|
Return the bytes of the course code library file, if it exists.
|
|
"""
|
|
return get_python_lib_zip(self.contentstore, self.course_id)
|