Hint functions are now run in the sandbox.
This commit is contained in:
@@ -22,6 +22,7 @@ import random
|
||||
import re
|
||||
import requests
|
||||
import subprocess
|
||||
import textwrap
|
||||
import traceback
|
||||
import xml.sax.saxutils as saxutils
|
||||
|
||||
@@ -30,7 +31,7 @@ from shapely.geometry import Point, MultiPoint
|
||||
|
||||
# specific library imports
|
||||
from calc import evaluator, UndefinedVariable
|
||||
from .correctmap import CorrectMap
|
||||
from . import correctmap
|
||||
from datetime import datetime
|
||||
from .util import *
|
||||
from lxml import etree
|
||||
@@ -42,6 +43,10 @@ import safe_exec
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
CorrectMap = correctmap.CorrectMap
|
||||
CORRECTMAP_PY = None
|
||||
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Exceptions
|
||||
|
||||
@@ -253,20 +258,41 @@ class LoncapaResponse(object):
|
||||
|
||||
# We may extend this in the future to add another argument which provides a
|
||||
# callback procedure to a social hint generation system.
|
||||
if not hintfn in self.context:
|
||||
msg = 'missing specified hint function %s in script context' % hintfn
|
||||
msg += "\nSee XML source line %s" % getattr(
|
||||
self.xml, 'sourceline', '<unavailable>')
|
||||
raise LoncapaProblemError(msg)
|
||||
|
||||
global CORRECTMAP_PY
|
||||
if CORRECTMAP_PY is None:
|
||||
# We need the CorrectMap code for hint functions. No, this is not great.
|
||||
CORRECTMAP_PY = inspect.getsource(correctmap)
|
||||
|
||||
code = (
|
||||
CORRECTMAP_PY + "\n" +
|
||||
self.context['script_code'] + "\n" +
|
||||
textwrap.dedent("""
|
||||
new_cmap = CorrectMap()
|
||||
new_cmap.set_dict(new_cmap_dict)
|
||||
old_cmap = CorrectMap()
|
||||
old_cmap.set_dict(old_cmap_dict)
|
||||
{hintfn}(answer_ids, student_answers, new_cmap, old_cmap)
|
||||
new_cmap_dict.update(new_cmap.get_dict())
|
||||
old_cmap_dict.update(old_cmap.get_dict())
|
||||
""").format(hintfn=hintfn)
|
||||
)
|
||||
globals_dict = {
|
||||
'answer_ids': self.answer_ids,
|
||||
'student_answers': student_answers,
|
||||
'new_cmap_dict': new_cmap.get_dict(),
|
||||
'old_cmap_dict': old_cmap.get_dict(),
|
||||
}
|
||||
|
||||
try:
|
||||
self.context[hintfn](
|
||||
self.answer_ids, student_answers, new_cmap, old_cmap)
|
||||
safe_exec.safe_exec(code, globals_dict)
|
||||
except Exception as err:
|
||||
msg = 'Error %s in evaluating hint function %s' % (err, hintfn)
|
||||
msg += "\nSee XML source line %s" % getattr(
|
||||
self.xml, 'sourceline', '<unavailable>')
|
||||
raise ResponseError(msg)
|
||||
|
||||
new_cmap.set_dict(globals_dict['new_cmap_dict'])
|
||||
return
|
||||
|
||||
# hint specified by conditions and text dependent on conditions (a-la Loncapa design)
|
||||
|
||||
@@ -667,12 +667,16 @@ class StringResponseXMLFactory(ResponseXMLFactory):
|
||||
Where *hint_prompt* is the string for which we show the hint,
|
||||
*hint_name* is an internal identifier for the hint,
|
||||
and *hint_text* is the text we show for the hint.
|
||||
|
||||
*hintfn*: The name of a function in the script to use for hints.
|
||||
|
||||
"""
|
||||
# Retrieve the **kwargs
|
||||
answer = kwargs.get("answer", None)
|
||||
case_sensitive = kwargs.get("case_sensitive", True)
|
||||
hint_list = kwargs.get('hints', None)
|
||||
assert(answer)
|
||||
hint_fn = kwargs.get('hintfn', None)
|
||||
assert answer
|
||||
|
||||
# Create the <stringresponse> element
|
||||
response_element = etree.Element("stringresponse")
|
||||
@@ -684,18 +688,24 @@ class StringResponseXMLFactory(ResponseXMLFactory):
|
||||
response_element.set("type", "cs" if case_sensitive else "ci")
|
||||
|
||||
# Add the hints if specified
|
||||
if hint_list:
|
||||
if hint_list or hint_fn:
|
||||
hintgroup_element = etree.SubElement(response_element, "hintgroup")
|
||||
for (hint_prompt, hint_name, hint_text) in hint_list:
|
||||
stringhint_element = etree.SubElement(hintgroup_element, "stringhint")
|
||||
stringhint_element.set("answer", str(hint_prompt))
|
||||
stringhint_element.set("name", str(hint_name))
|
||||
if hint_list:
|
||||
assert not hint_fn
|
||||
for (hint_prompt, hint_name, hint_text) in hint_list:
|
||||
stringhint_element = etree.SubElement(hintgroup_element, "stringhint")
|
||||
stringhint_element.set("answer", str(hint_prompt))
|
||||
stringhint_element.set("name", str(hint_name))
|
||||
|
||||
hintpart_element = etree.SubElement(hintgroup_element, "hintpart")
|
||||
hintpart_element.set("on", str(hint_name))
|
||||
hintpart_element = etree.SubElement(hintgroup_element, "hintpart")
|
||||
hintpart_element.set("on", str(hint_name))
|
||||
|
||||
hint_text_element = etree.SubElement(hintpart_element, "text")
|
||||
hint_text_element.text = str(hint_text)
|
||||
hint_text_element = etree.SubElement(hintpart_element, "text")
|
||||
hint_text_element.text = str(hint_text)
|
||||
|
||||
if hint_fn:
|
||||
assert not hint_list
|
||||
hintgroup_element.set("hintfn", hint_fn)
|
||||
|
||||
return response_element
|
||||
|
||||
|
||||
@@ -552,6 +552,22 @@ class StringResponseTest(ResponseTest):
|
||||
correct_map = problem.grade_answers(input_dict)
|
||||
self.assertEquals(correct_map.get_hint('1_2_1'), "")
|
||||
|
||||
def test_computed_hints(self):
|
||||
problem = self.build_problem(
|
||||
answer="Michigan",
|
||||
hintfn="gimme_a_hint",
|
||||
script = textwrap.dedent("""
|
||||
def gimme_a_hint(answer_ids, student_answers, new_cmap, old_cmap):
|
||||
aid = answer_ids[0]
|
||||
answer = student_answers[aid]
|
||||
new_cmap.set_hint_and_mode(aid, answer+"??", "always")
|
||||
""")
|
||||
)
|
||||
|
||||
input_dict = {'1_2_1': 'Hello'}
|
||||
correct_map = problem.grade_answers(input_dict)
|
||||
self.assertEquals(correct_map.get_hint('1_2_1'), "Hello??")
|
||||
|
||||
|
||||
class CodeResponseTest(ResponseTest):
|
||||
from response_xml_factory import CodeResponseXMLFactory
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""Safe execution of untrusted Python code."""
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os.path
|
||||
import shutil
|
||||
import sys
|
||||
@@ -9,6 +10,8 @@ import textwrap
|
||||
from codejail import jailpy
|
||||
from codejail.util import temp_directory, change_directory
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def safe_exec(code, globals_dict, files=None, python_path=None):
|
||||
"""Execute code as "exec" does, but safely.
|
||||
@@ -78,10 +81,9 @@ def safe_exec(code, globals_dict, files=None, python_path=None):
|
||||
|
||||
# Turn this on to see what's being executed.
|
||||
if 0:
|
||||
print "--{:-<40}".format(" jailed ")
|
||||
print jailed_code
|
||||
print "--{:-<40}".format(" exec ")
|
||||
print code
|
||||
log.debug("Jailed code: %s", jailed_code)
|
||||
log.debug("Exec: %s", code)
|
||||
log.debug("Stdin: %s", stdin)
|
||||
|
||||
res = jailpy.jailpy(jailed_code, stdin=stdin, files=files)
|
||||
if res.status != 0:
|
||||
|
||||
Reference in New Issue
Block a user