Skip to content

Commit

Permalink
Check marking schemes for attachments to prevent leakage
Browse files Browse the repository at this point in the history
  • Loading branch information
tuncbkose committed Jun 16, 2023
1 parent 05bb458 commit ca07516
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 2 deletions.
27 changes: 25 additions & 2 deletions nbgrader/preprocessors/clearmarkingscheme.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ class ClearMarkScheme(NbGraderPreprocessor):

begin_mark_scheme_delimeter = Unicode(
"BEGIN MARK SCHEME",
help="The delimiter marking the beginning of hidden tests cases"
help="The delimiter marking the beginning of a marking scheme region"
).tag(config=True)

end_mark_scheme_delimeter = Unicode(
"END MARK SCHEME",
help="The delimiter marking the end of hidden tests cases"
help="The delimiter marking the end of a marking scheme region"
).tag(config=True)

enforce_metadata = Bool(
Expand All @@ -35,6 +35,16 @@ class ClearMarkScheme(NbGraderPreprocessor):
)
).tag(config=True)

check_attachment_leakage = Bool(
True,
help=dedent(
"""
Whether or not to check if a marking scheme region contains an attachment,
in order to prevent leakage to student version of notebooks.
"""
)
).tag(config=True)

def _remove_mark_scheme_region(self, cell: NotebookNode) -> bool:
"""Find a region in the cell that is delimeted by
`self.begin_mark_scheme_delimeter` and `self.end_mark_scheme_delimeter` (e.g. ###
Expand All @@ -50,6 +60,7 @@ def _remove_mark_scheme_region(self, cell: NotebookNode) -> bool:
new_lines = []
in_ms = False
removed_ms = False
attachment_regex = r"!\[.*\]\(attachment:.+?\)"

for line in lines:
# begin the test area
Expand All @@ -67,6 +78,18 @@ def _remove_mark_scheme_region(self, cell: NotebookNode) -> bool:
elif self.end_mark_scheme_delimeter in line:
in_ms = False

elif self.check_attachment_leakage and in_ms and re.search(attachment_regex, line):
raise RuntimeError(
"""
Encountered an attachment in a marking scheme.
This can leak to student notebooks.
You might want to embed your image instead, like here:
https://github.com/jupyter/nbgrader/issues/1782#issuecomment-1541493629.
You can suppress this check with ClearMarkScheme.check_attachment_leakage=False.
For more info: https://github.com/jupyter/nbgrader/issues/1782
"""
)

# add lines as long as it's not in the hidden tests region
elif not in_ms:
new_lines.append(line)
Expand Down
62 changes: 62 additions & 0 deletions nbgrader/tests/preprocessors/test_clearmarkscheme.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,65 @@ def test_preprocess_notebook(self, preprocessor):
"""Is the test notebook processed without error?"""
nb = self._read_nb(os.path.join("files", "test_taskcell.ipynb"))
preprocessor.preprocess(nb, {})

# attachment detection tests
def test_attachment_in_mark_scheme(self, preprocessor):
"""Is an error raised when there is an attachment in the marking scheme?"""
source = dedent(
"""
assert True
### BEGIN MARK SCHEME
![](attachment:image.png)
### END MARK SCHEME
"""
).strip()
cell = create_task_cell(source, 'markdown', 'some-task-id', 2)
with pytest.raises(RuntimeError):
preprocessor._remove_mark_scheme_region(cell)

source = dedent(
"""
assert True
### BEGIN MARK SCHEME
Text text text text text.
![](attachment:image1.jpg)
Grade grade grade.
![](attachment:image2.png)
Mark mark mark mark.
### END MARK SCHEME
"""
).strip()
cell = create_task_cell(source, 'markdown', 'some-task-id', 2)
with pytest.raises(RuntimeError):
preprocessor._remove_mark_scheme_region(cell)

def test_attachment_suppress_error(self, preprocessor):
"""Can the error be suppressed?"""
source = dedent(
"""
assert True
### BEGIN MARK SCHEME
![](attachment:image.png)
### END MARK SCHEME
"""
).strip()
cell = create_task_cell(source, 'markdown', 'some-task-id', 2)
preprocessor.check_attachment_leakage = False
removed_test = preprocessor._remove_mark_scheme_region(cell)
assert removed_test
assert cell.source == "assert True"

def test_attachment_not_in_mark_scheme(self, preprocessor):
"""Attachments outside of marking schemes shouldn't be touched"""
source = dedent(
"""
![](attachment:image.png)
### BEGIN MARK SCHEME
assert True
### END MARK SCHEME
"""
).strip()
cell = create_task_cell(source, 'markdown', 'some-task-id', 2)
removed_test = preprocessor._remove_mark_scheme_region(cell)
assert removed_test
assert cell.source == "![](attachment:image.png)"

0 comments on commit ca07516

Please sign in to comment.