refactor: deprecates ModuleSystem properties for code sandboxing and cache

* Deprecates ModuleSystem can_execute_unsafe_code, get_python_lib_zip and cache properties
* Adds a new CacheService and SandboxService to provide the deprecated property
* Adds tests for the added CacheService and SandboxService
* Updates the ModuleSystemShim tests in Lms and Studio
This commit is contained in:
Jillian Vogel
2021-12-06 12:03:55 +10:30
parent 3eea5d9337
commit 2173a98ef8
11 changed files with 374 additions and 119 deletions

View File

@@ -8,6 +8,7 @@ import functools
import os
from contextlib import contextmanager
from enum import Enum
from mimetypes import guess_type
from unittest.mock import patch
from django.conf import settings
@@ -16,6 +17,12 @@ from django.db import connections, transaction
from django.test import TestCase
from django.test.utils import override_settings
from xmodule.contentstore.content import StaticContent
from xmodule.contentstore.django import _CONTENTSTORE
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.django import SignalHandler, clear_existing_modulestores, modulestore
from xmodule.modulestore.tests.factories import XMODULE_FACTORY_LOCK
from xmodule.modulestore.tests.mongo_connection import MONGO_HOST, MONGO_PORT_NUM
from lms.djangoapps.courseware.field_overrides import OverrideFieldData
from openedx.core.djangolib.testing.utils import CacheIsolationMixin, CacheIsolationTestCase, FilteredQueryCountMixin
from openedx.core.lib.tempdir import mkdtemp_clean
@@ -23,11 +30,6 @@ from common.djangoapps.split_modulestore_django.models import SplitModulestoreCo
from common.djangoapps.student.models import CourseEnrollment
from common.djangoapps.student.tests.factories import AdminFactory, UserFactory, InstructorFactory
from common.djangoapps.student.tests.factories import StaffFactory
from xmodule.contentstore.django import _CONTENTSTORE
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.django import SignalHandler, clear_existing_modulestores, modulestore
from xmodule.modulestore.tests.factories import XMODULE_FACTORY_LOCK
from xmodule.modulestore.tests.mongo_connection import MONGO_HOST, MONGO_PORT_NUM
class CourseUserType(Enum):
@@ -604,3 +606,16 @@ class ModuleStoreTestCase(
self.store.update_item(course, user_id)
updated_course = self.store.get_course(course.id)
return updated_course
def upload_file_to_course(course_key, contentstore, source_file, target_filename):
'''
Uploads the given source file to the given course, and returns the content of the file.
'''
asset_key = course_key.make_asset_key('asset', target_filename)
with open(source_file, "rb") as f:
file_contents = f.read()
mimetype = guess_type(source_file)[0]
content = StaticContent(asset_key, target_filename, mimetype, file_contents, locked=False)
contentstore.save(content)
return file_contents

View File

@@ -41,3 +41,29 @@ def get_python_lib_zip(contentstore, course_id):
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)

View File

@@ -34,13 +34,13 @@ from xblock.fields import (
)
from xblock.runtime import IdGenerator, IdReader, Runtime
from openedx.core.djangolib.markup import HTML
from xmodule import block_metadata_utils
from xmodule.errortracker import exc_info_to_str
from xmodule.exceptions import UndefinedContext
from xmodule.fields import RelativeTime
from xmodule.modulestore.exceptions import ItemNotFoundError
from xmodule.util.xmodule_django import add_webpack_to_fragment
from openedx.core.djangolib.markup import HTML
from common.djangoapps.xblock_django.constants import (
ATTR_KEY_ANONYMOUS_USER_ID,
@@ -1906,6 +1906,59 @@ class ModuleSystemShim:
}
return None
@property
def can_execute_unsafe_code(self):
"""
Returns a function which returns a boolean, indicating whether or not to allow the execution of unsafe,
unsandboxed code.
Deprecated in favor of the sandbox service.
"""
warnings.warn(
'runtime.can_execute_unsafe_code is deprecated. Please use the sandbox service instead.',
DeprecationWarning, stacklevel=3,
)
sandbox_service = self._services.get('sandbox')
if sandbox_service:
return sandbox_service.can_execute_unsafe_code
# Default to saying "no unsafe code".
return lambda: False
@property
def get_python_lib_zip(self):
"""
Returns a function returning a bytestring or None.
The bytestring is the contents of a zip file that should be importable by other Python code running in the
module.
Deprecated in favor of the sandbox service.
"""
warnings.warn(
'runtime.get_python_lib_zip is deprecated. Please use the sandbox service instead.',
DeprecationWarning, stacklevel=3,
)
sandbox_service = self._services.get('sandbox')
if sandbox_service:
return sandbox_service.get_python_lib_zip
# Default to saying "no lib data"
return lambda: None
@property
def cache(self):
"""
Returns a cache object with two methods:
* .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.
Deprecated in favor of the cache service.
"""
warnings.warn(
'runtime.cache is deprecated. Please use the cache service instead.',
DeprecationWarning, stacklevel=3,
)
return self._services.get('cache') or DoNothingCache()
class ModuleSystem(MetricsMixin, ConfigurableFragmentWrapper, ModuleSystemShim, Runtime):
"""
@@ -1925,10 +1978,10 @@ class ModuleSystem(MetricsMixin, ConfigurableFragmentWrapper, ModuleSystemShim,
replace_urls, descriptor_runtime, filestore=None,
debug=False, hostname="", publish=None, node_path="",
course_id=None,
cache=None, can_execute_unsafe_code=None, replace_course_urls=None,
replace_course_urls=None,
replace_jump_to_id_urls=None, error_descriptor_class=None,
field_data=None, rebind_noauth_module_to_user=None,
get_python_lib_zip=None, **kwargs):
**kwargs):
"""
Create a closure around the system environment.
@@ -1956,17 +2009,6 @@ class ModuleSystem(MetricsMixin, ConfigurableFragmentWrapper, ModuleSystemShim,
publish(event) - A function that allows XModules to publish events (such as grade changes)
cache - A cache object with two methods:
.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 function returning a boolean, whether or
not to allow the execution of unsafe, unsandboxed code.
get_python_lib_zip - A function returning a bytestring or None. The
bytestring is the contents of a zip file that should be importable
by other Python code running in the module.
error_descriptor_class - The class to use to render XModules with errors
field_data - the `FieldData` to use for backing XBlock storage.
@@ -1993,10 +2035,6 @@ class ModuleSystem(MetricsMixin, ConfigurableFragmentWrapper, ModuleSystemShim,
if publish:
self.publish = publish
self.cache = cache or DoNothingCache()
self.can_execute_unsafe_code = can_execute_unsafe_code or (lambda: False)
self.get_python_lib_zip = get_python_lib_zip or (lambda: None)
self.replace_course_urls = replace_course_urls
self.replace_jump_to_id_urls = replace_jump_to_id_urls
self.error_descriptor_class = error_descriptor_class