Show More
@@ -275,7 +275,7 b' class FileNotebookManager(NotebookManager):' | |||||
275 | nb = current.read(f, u'json') |
|
275 | nb = current.read(f, u'json') | |
276 | except Exception as e: |
|
276 | except Exception as e: | |
277 | raise web.HTTPError(400, u"Unreadable Notebook: %s %s" % (os_path, e)) |
|
277 | raise web.HTTPError(400, u"Unreadable Notebook: %s %s" % (os_path, e)) | |
278 |
self.mark_trusted_cells(nb, |
|
278 | self.mark_trusted_cells(nb, name, path) | |
279 | model['content'] = nb |
|
279 | model['content'] = nb | |
280 | return model |
|
280 | return model | |
281 |
|
281 | |||
@@ -300,7 +300,7 b' class FileNotebookManager(NotebookManager):' | |||||
300 | os_path = self._get_os_path(new_name, new_path) |
|
300 | os_path = self._get_os_path(new_name, new_path) | |
301 | nb = current.to_notebook_json(model['content']) |
|
301 | nb = current.to_notebook_json(model['content']) | |
302 |
|
302 | |||
303 |
self.check_and_sign(nb, new_ |
|
303 | self.check_and_sign(nb, new_name, new_path) | |
304 |
|
304 | |||
305 | if 'name' in nb['metadata']: |
|
305 | if 'name' in nb['metadata']: | |
306 | nb['metadata']['name'] = u'' |
|
306 | nb['metadata']['name'] = u'' |
@@ -222,6 +222,17 b' class NotebookHandler(IPythonHandler):' | |||||
222 | self.finish() |
|
222 | self.finish() | |
223 |
|
223 | |||
224 |
|
224 | |||
|
225 | class NotebookTrustHandler(IPythonHandler): | |||
|
226 | ||||
|
227 | SUPPORTED_METHODS = ('POST') | |||
|
228 | ||||
|
229 | @web.authenticated | |||
|
230 | def post(self, path='', name=None): | |||
|
231 | """trust the specified notebook""" | |||
|
232 | self.notebook_manager.trust_notebook(name, path) | |||
|
233 | self.set_status(200) | |||
|
234 | ||||
|
235 | ||||
225 | class NotebookCheckpointsHandler(IPythonHandler): |
|
236 | class NotebookCheckpointsHandler(IPythonHandler): | |
226 |
|
237 | |||
227 | SUPPORTED_METHODS = ('GET', 'POST') |
|
238 | SUPPORTED_METHODS = ('GET', 'POST') | |
@@ -279,6 +290,7 b' class ModifyNotebookCheckpointsHandler(IPythonHandler):' | |||||
279 | _checkpoint_id_regex = r"(?P<checkpoint_id>[\w-]+)" |
|
290 | _checkpoint_id_regex = r"(?P<checkpoint_id>[\w-]+)" | |
280 |
|
291 | |||
281 | default_handlers = [ |
|
292 | default_handlers = [ | |
|
293 | (r"/api/notebooks%s/trust" % notebook_path_regex, NotebookTrustHandler), | |||
282 | (r"/api/notebooks%s/checkpoints" % notebook_path_regex, NotebookCheckpointsHandler), |
|
294 | (r"/api/notebooks%s/checkpoints" % notebook_path_regex, NotebookCheckpointsHandler), | |
283 | (r"/api/notebooks%s/checkpoints/%s" % (notebook_path_regex, _checkpoint_id_regex), |
|
295 | (r"/api/notebooks%s/checkpoints/%s" % (notebook_path_regex, _checkpoint_id_regex), | |
284 | ModifyNotebookCheckpointsHandler), |
|
296 | ModifyNotebookCheckpointsHandler), | |
@@ -286,5 +298,3 b' default_handlers = [' | |||||
286 | (r"/api/notebooks%s" % path_regex, NotebookHandler), |
|
298 | (r"/api/notebooks%s" % path_regex, NotebookHandler), | |
287 | ] |
|
299 | ] | |
288 |
|
300 | |||
289 |
|
||||
290 |
|
@@ -224,9 +224,18 b' class NotebookManager(LoggingConfigurable):' | |||||
224 | def log_info(self): |
|
224 | def log_info(self): | |
225 | self.log.info(self.info_string()) |
|
225 | self.log.info(self.info_string()) | |
226 |
|
226 | |||
227 | # NotebookManager methods provided for use in subclasses. |
|
227 | def trust_notebook(self, name, path=''): | |
228 |
|
228 | """Check for trusted cells, and sign the notebook. | ||
229 | def check_and_sign(self, nb, path, name): |
|
229 | ||
|
230 | Called as a part of saving notebooks. | |||
|
231 | """ | |||
|
232 | model = self.get_notebook(name, path) | |||
|
233 | nb = model['content'] | |||
|
234 | self.log.warn("Trusting notebook %s/%s", path, name) | |||
|
235 | self.notary.mark_cells(nb, True) | |||
|
236 | self.save_notebook(model, name, path) | |||
|
237 | ||||
|
238 | def check_and_sign(self, nb, name, path=''): | |||
230 | """Check for trusted cells, and sign the notebook. |
|
239 | """Check for trusted cells, and sign the notebook. | |
231 |
|
240 | |||
232 | Called as a part of saving notebooks. |
|
241 | Called as a part of saving notebooks. | |
@@ -236,7 +245,7 b' class NotebookManager(LoggingConfigurable):' | |||||
236 | else: |
|
245 | else: | |
237 | self.log.warn("Saving untrusted notebook %s/%s", path, name) |
|
246 | self.log.warn("Saving untrusted notebook %s/%s", path, name) | |
238 |
|
247 | |||
239 |
def mark_trusted_cells(self, nb, |
|
248 | def mark_trusted_cells(self, nb, name, path=''): | |
240 | """Mark cells as trusted if the notebook signature matches. |
|
249 | """Mark cells as trusted if the notebook signature matches. | |
241 |
|
250 | |||
242 | Called as a part of loading notebooks. |
|
251 | Called as a part of loading notebooks. |
@@ -2,12 +2,15 b'' | |||||
2 | """Tests for the notebook manager.""" |
|
2 | """Tests for the notebook manager.""" | |
3 | from __future__ import print_function |
|
3 | from __future__ import print_function | |
4 |
|
4 | |||
|
5 | import logging | |||
5 | import os |
|
6 | import os | |
6 |
|
7 | |||
7 | from tornado.web import HTTPError |
|
8 | from tornado.web import HTTPError | |
8 | from unittest import TestCase |
|
9 | from unittest import TestCase | |
9 | from tempfile import NamedTemporaryFile |
|
10 | from tempfile import NamedTemporaryFile | |
10 |
|
11 | |||
|
12 | from IPython.nbformat import current | |||
|
13 | ||||
11 | from IPython.utils.tempdir import TemporaryDirectory |
|
14 | from IPython.utils.tempdir import TemporaryDirectory | |
12 | from IPython.utils.traitlets import TraitError |
|
15 | from IPython.utils.traitlets import TraitError | |
13 | from IPython.html.utils import url_path_join |
|
16 | from IPython.html.utils import url_path_join | |
@@ -55,6 +58,14 b' class TestFileNotebookManager(TestCase):' | |||||
55 |
|
58 | |||
56 | class TestNotebookManager(TestCase): |
|
59 | class TestNotebookManager(TestCase): | |
57 |
|
60 | |||
|
61 | def setUp(self): | |||
|
62 | self._temp_dir = TemporaryDirectory() | |||
|
63 | self.td = self._temp_dir.name | |||
|
64 | self.nbm = FileNotebookManager(notebook_dir=self.td, log=logging.getLogger()) | |||
|
65 | ||||
|
66 | def tearDown(self): | |||
|
67 | self._temp_dir.cleanup() | |||
|
68 | ||||
58 | def make_dir(self, abs_path, rel_path): |
|
69 | def make_dir(self, abs_path, rel_path): | |
59 | """make subdirectory, rel_path is the relative path |
|
70 | """make subdirectory, rel_path is the relative path | |
60 | to that directory from the location where the server started""" |
|
71 | to that directory from the location where the server started""" | |
@@ -63,11 +74,33 b' class TestNotebookManager(TestCase):' | |||||
63 | os.makedirs(os_path) |
|
74 | os.makedirs(os_path) | |
64 | except OSError: |
|
75 | except OSError: | |
65 | print("Directory already exists: %r" % os_path) |
|
76 | print("Directory already exists: %r" % os_path) | |
66 |
|
77 | |||
|
78 | def add_code_cell(self, nb): | |||
|
79 | output = current.new_output("display_data", output_javascript="alert('hi');") | |||
|
80 | cell = current.new_code_cell("print('hi')", outputs=[output]) | |||
|
81 | if not nb.worksheets: | |||
|
82 | nb.worksheets.append(current.new_worksheet()) | |||
|
83 | nb.worksheets[0].cells.append(cell) | |||
|
84 | ||||
|
85 | def new_notebook(self): | |||
|
86 | nbm = self.nbm | |||
|
87 | model = nbm.create_notebook() | |||
|
88 | name = model['name'] | |||
|
89 | path = model['path'] | |||
|
90 | ||||
|
91 | full_model = nbm.get_notebook(name, path) | |||
|
92 | nb = full_model['content'] | |||
|
93 | self.add_code_cell(nb) | |||
|
94 | ||||
|
95 | nbm.save_notebook(full_model, name, path) | |||
|
96 | return nb, name, path | |||
|
97 | ||||
67 | def test_create_notebook(self): |
|
98 | def test_create_notebook(self): | |
68 | with TemporaryDirectory() as td: |
|
99 | with TemporaryDirectory() as td: | |
69 | # Test in root directory |
|
100 | # Test in root directory | |
|
101 | # Create a notebook | |||
70 | nm = FileNotebookManager(notebook_dir=td) |
|
102 | nm = FileNotebookManager(notebook_dir=td) | |
|
103 | # Test in root directory | |||
71 | model = nm.create_notebook() |
|
104 | model = nm.create_notebook() | |
72 | assert isinstance(model, dict) |
|
105 | assert isinstance(model, dict) | |
73 | self.assertIn('name', model) |
|
106 | self.assertIn('name', model) | |
@@ -243,3 +276,43 b' class TestNotebookManager(TestCase):' | |||||
243 | copy2 = nm.copy_notebook(name, u'copy 2.ipynb', path=path) |
|
276 | copy2 = nm.copy_notebook(name, u'copy 2.ipynb', path=path) | |
244 | self.assertEqual(copy2['name'], u'copy 2.ipynb') |
|
277 | self.assertEqual(copy2['name'], u'copy 2.ipynb') | |
245 |
|
278 | |||
|
279 | def test_trust_notebook(self): | |||
|
280 | nbm = self.nbm | |||
|
281 | nb, name, path = self.new_notebook() | |||
|
282 | ||||
|
283 | untrusted = nbm.get_notebook(name, path)['content'] | |||
|
284 | assert not nbm.notary.check_signature(untrusted) | |||
|
285 | ||||
|
286 | nbm.trust_notebook(name, path) | |||
|
287 | trusted = nbm.get_notebook(name, path)['content'] | |||
|
288 | assert nbm.notary.check_signature(trusted) | |||
|
289 | ||||
|
290 | def test_mark_trusted_cells(self): | |||
|
291 | nbm = self.nbm | |||
|
292 | nb, name, path = self.new_notebook() | |||
|
293 | ||||
|
294 | nbm.mark_trusted_cells(nb, name, path) | |||
|
295 | for cell in nb.worksheets[0].cells: | |||
|
296 | if cell.cell_type == 'code': | |||
|
297 | assert not cell.trusted | |||
|
298 | ||||
|
299 | nbm.trust_notebook(name, path) | |||
|
300 | nb = nbm.get_notebook(name, path)['content'] | |||
|
301 | nbm.mark_trusted_cells(nb, name, path) | |||
|
302 | for cell in nb.worksheets[0].cells: | |||
|
303 | if cell.cell_type == 'code': | |||
|
304 | assert cell.trusted | |||
|
305 | ||||
|
306 | def test_check_and_sign(self): | |||
|
307 | nbm = self.nbm | |||
|
308 | nb, name, path = self.new_notebook() | |||
|
309 | ||||
|
310 | nbm.mark_trusted_cells(nb, name, path) | |||
|
311 | nbm.check_and_sign(nb, name, path) | |||
|
312 | assert not nbm.notary.check_signature(nb) | |||
|
313 | ||||
|
314 | nbm.trust_notebook(name, path) | |||
|
315 | nb = nbm.get_notebook(name, path)['content'] | |||
|
316 | nbm.mark_trusted_cells(nb, name, path) | |||
|
317 | nbm.check_and_sign(nb, name, path) | |||
|
318 | assert nbm.notary.check_signature(nb) |
@@ -133,6 +133,9 b' var IPython = (function (IPython) {' | |||||
133 | }); |
|
133 | }); | |
134 | this.element.find('#restore_checkpoint').click(function () { |
|
134 | this.element.find('#restore_checkpoint').click(function () { | |
135 | }); |
|
135 | }); | |
|
136 | this.element.find('#trust_notebook').click(function () { | |||
|
137 | IPython.notebook.trust_notebook(); | |||
|
138 | }); | |||
136 | this.element.find('#kill_and_exit').click(function () { |
|
139 | this.element.find('#kill_and_exit').click(function () { | |
137 | IPython.notebook.session.delete(); |
|
140 | IPython.notebook.session.delete(); | |
138 | setTimeout(function(){ |
|
141 | setTimeout(function(){ |
@@ -1786,6 +1786,75 b' var IPython = (function (IPython) {' | |||||
1786 | $([IPython.events]).trigger('notebook_save_failed.Notebook', [xhr, status, error]); |
|
1786 | $([IPython.events]).trigger('notebook_save_failed.Notebook', [xhr, status, error]); | |
1787 | }; |
|
1787 | }; | |
1788 |
|
1788 | |||
|
1789 | /** | |||
|
1790 | * Explicitly trust the output of this notebook. | |||
|
1791 | * | |||
|
1792 | * @method trust_notebook | |||
|
1793 | */ | |||
|
1794 | Notebook.prototype.trust_notebook = function (extra_settings) { | |||
|
1795 | // We do the call with settings so we can set cache to false. | |||
|
1796 | ||||
|
1797 | var settings = { | |||
|
1798 | processData : false, | |||
|
1799 | cache : false, | |||
|
1800 | type : "POST", | |||
|
1801 | success : $.proxy(this._trust_notebook_success, this), | |||
|
1802 | error : $.proxy(this._trust_notebook_error, this) | |||
|
1803 | }; | |||
|
1804 | if (extra_settings) { | |||
|
1805 | for (var key in extra_settings) { | |||
|
1806 | settings[key] = extra_settings[key]; | |||
|
1807 | } | |||
|
1808 | } | |||
|
1809 | ||||
|
1810 | var body = $("<div>").append($("<p>") | |||
|
1811 | .text("A trusted IPython notebook may execute hidden malicious code ") | |||
|
1812 | .append($("<strong>") | |||
|
1813 | .append( | |||
|
1814 | $("<em>").text("when you open it") | |||
|
1815 | ) | |||
|
1816 | ).append(".") | |||
|
1817 | ).append($("<p>") | |||
|
1818 | .text("For more information, see the ") | |||
|
1819 | .append($("<a>").attr("href", "http://ipython.org/security.html") | |||
|
1820 | .text("IPython security documentation") | |||
|
1821 | ).append(".") | |||
|
1822 | ); | |||
|
1823 | ||||
|
1824 | var nb = this; | |||
|
1825 | IPython.dialog.modal({ | |||
|
1826 | title: "Trust this notebook?", | |||
|
1827 | body: body, | |||
|
1828 | ||||
|
1829 | buttons: { | |||
|
1830 | Cancel : {}, | |||
|
1831 | Trust : { | |||
|
1832 | class : "btn-danger", | |||
|
1833 | click : function () { | |||
|
1834 | $([IPython.events]).trigger('notebook_trusting.Notebook'); | |||
|
1835 | var url = utils.url_join_encode( | |||
|
1836 | nb.base_url, | |||
|
1837 | 'api/notebooks', | |||
|
1838 | nb.notebook_path, | |||
|
1839 | nb.notebook_name, | |||
|
1840 | 'trust' | |||
|
1841 | ); | |||
|
1842 | $.ajax(url, settings); | |||
|
1843 | } | |||
|
1844 | } | |||
|
1845 | } | |||
|
1846 | }); | |||
|
1847 | }; | |||
|
1848 | ||||
|
1849 | Notebook.prototype._trust_notebook_success = function (data, status, xhr) { | |||
|
1850 | $([IPython.events]).trigger('notebook_trusted.Notebook'); | |||
|
1851 | window.location.reload(); | |||
|
1852 | }; | |||
|
1853 | ||||
|
1854 | Notebook.prototype._trust_notebook_error = function (xhr, status, error) { | |||
|
1855 | $([IPython.events]).trigger('notebook_trust_failed.Notebook', [xhr, status, error]); | |||
|
1856 | }; | |||
|
1857 | ||||
1789 | Notebook.prototype.new_notebook = function(){ |
|
1858 | Notebook.prototype.new_notebook = function(){ | |
1790 | var path = this.notebook_path; |
|
1859 | var path = this.notebook_path; | |
1791 | var base_url = this.base_url; |
|
1860 | var base_url = this.base_url; |
@@ -86,7 +86,10 b' class="notebook_app"' | |||||
86 | </ul> |
|
86 | </ul> | |
87 | </li> |
|
87 | </li> | |
88 | <li class="divider"></li> |
|
88 | <li class="divider"></li> | |
89 |
|
89 | <li id="trust_notebook" | ||
|
90 | title="Trust the output of this notebook"> | |||
|
91 | <a href="#" >Trust Notebook</a></li> | |||
|
92 | <li class="divider"></li> | |||
90 | <li id="kill_and_exit" |
|
93 | <li id="kill_and_exit" | |
91 | title="Shutdown this notebook's kernel, and close this window"> |
|
94 | title="Shutdown this notebook's kernel, and close this window"> | |
92 | <a href="#" >Close and halt</a></li> |
|
95 | <a href="#" >Close and halt</a></li> |
General Comments 0
You need to be logged in to leave comments.
Login now