Add Trust Notebook to File menu

MinRK 12 years ago
parent 46665483ed
commit 1ae689f30c

@ -275,7 +275,7 @@ class FileNotebookManager(NotebookManager):
nb = current.read(f, u'json')
except Exception as e:
raise web.HTTPError(400, u"Unreadable Notebook: %s %s" % (os_path, e))
self.mark_trusted_cells(nb, path, name)
self.mark_trusted_cells(nb, name, path)
model['content'] = nb
return model
@ -300,7 +300,7 @@ class FileNotebookManager(NotebookManager):
os_path = self._get_os_path(new_name, new_path)
nb = current.to_notebook_json(model['content'])
self.check_and_sign(nb, new_path, new_name)
self.check_and_sign(nb, new_name, new_path)
if 'name' in nb['metadata']:
nb['metadata']['name'] = u''

@ -222,6 +222,17 @@ class NotebookHandler(IPythonHandler):
self.finish()
class NotebookTrustHandler(IPythonHandler):
SUPPORTED_METHODS = ('POST')
@web.authenticated
def post(self, path='', name=None):
"""trust the specified notebook"""
self.notebook_manager.trust_notebook(name, path)
self.set_status(200)
class NotebookCheckpointsHandler(IPythonHandler):
SUPPORTED_METHODS = ('GET', 'POST')
@ -279,6 +290,7 @@ class ModifyNotebookCheckpointsHandler(IPythonHandler):
_checkpoint_id_regex = r"(?P<checkpoint_id>[\w-]+)"
default_handlers = [
(r"/api/notebooks%s/trust" % notebook_path_regex, NotebookTrustHandler),
(r"/api/notebooks%s/checkpoints" % notebook_path_regex, NotebookCheckpointsHandler),
(r"/api/notebooks%s/checkpoints/%s" % (notebook_path_regex, _checkpoint_id_regex),
ModifyNotebookCheckpointsHandler),
@ -286,5 +298,3 @@ default_handlers = [
(r"/api/notebooks%s" % path_regex, NotebookHandler),
]

@ -224,9 +224,18 @@ class NotebookManager(LoggingConfigurable):
def log_info(self):
self.log.info(self.info_string())
# NotebookManager methods provided for use in subclasses.
def check_and_sign(self, nb, path, name):
def trust_notebook(self, name, path=''):
"""Check for trusted cells, and sign the notebook.
Called as a part of saving notebooks.
"""
model = self.get_notebook(name, path)
nb = model['content']
self.log.warn("Trusting notebook %s/%s", path, name)
self.notary.mark_cells(nb, True)
self.save_notebook(model, name, path)
def check_and_sign(self, nb, name, path=''):
"""Check for trusted cells, and sign the notebook.
Called as a part of saving notebooks.
@ -236,7 +245,7 @@ class NotebookManager(LoggingConfigurable):
else:
self.log.warn("Saving untrusted notebook %s/%s", path, name)
def mark_trusted_cells(self, nb, path, name):
def mark_trusted_cells(self, nb, name, path=''):
"""Mark cells as trusted if the notebook signature matches.
Called as a part of loading notebooks.

@ -2,12 +2,15 @@
"""Tests for the notebook manager."""
from __future__ import print_function
import logging
import os
from tornado.web import HTTPError
from unittest import TestCase
from tempfile import NamedTemporaryFile
from IPython.nbformat import current
from IPython.utils.tempdir import TemporaryDirectory
from IPython.utils.traitlets import TraitError
from IPython.html.utils import url_path_join
@ -55,6 +58,14 @@ class TestFileNotebookManager(TestCase):
class TestNotebookManager(TestCase):
def setUp(self):
self._temp_dir = TemporaryDirectory()
self.td = self._temp_dir.name
self.nbm = FileNotebookManager(notebook_dir=self.td, log=logging.getLogger())
def tearDown(self):
self._temp_dir.cleanup()
def make_dir(self, abs_path, rel_path):
"""make subdirectory, rel_path is the relative path
to that directory from the location where the server started"""
@ -63,11 +74,33 @@ class TestNotebookManager(TestCase):
os.makedirs(os_path)
except OSError:
print("Directory already exists: %r" % os_path)
def add_code_cell(self, nb):
output = current.new_output("display_data", output_javascript="alert('hi');")
cell = current.new_code_cell("print('hi')", outputs=[output])
if not nb.worksheets:
nb.worksheets.append(current.new_worksheet())
nb.worksheets[0].cells.append(cell)
def new_notebook(self):
nbm = self.nbm
model = nbm.create_notebook()
name = model['name']
path = model['path']
full_model = nbm.get_notebook(name, path)
nb = full_model['content']
self.add_code_cell(nb)
nbm.save_notebook(full_model, name, path)
return nb, name, path
def test_create_notebook(self):
with TemporaryDirectory() as td:
# Test in root directory
# Create a notebook
nm = FileNotebookManager(notebook_dir=td)
# Test in root directory
model = nm.create_notebook()
assert isinstance(model, dict)
self.assertIn('name', model)
@ -243,3 +276,43 @@ class TestNotebookManager(TestCase):
copy2 = nm.copy_notebook(name, u'copy 2.ipynb', path=path)
self.assertEqual(copy2['name'], u'copy 2.ipynb')
def test_trust_notebook(self):
nbm = self.nbm
nb, name, path = self.new_notebook()
untrusted = nbm.get_notebook(name, path)['content']
assert not nbm.notary.check_signature(untrusted)
nbm.trust_notebook(name, path)
trusted = nbm.get_notebook(name, path)['content']
assert nbm.notary.check_signature(trusted)
def test_mark_trusted_cells(self):
nbm = self.nbm
nb, name, path = self.new_notebook()
nbm.mark_trusted_cells(nb, name, path)
for cell in nb.worksheets[0].cells:
if cell.cell_type == 'code':
assert not cell.trusted
nbm.trust_notebook(name, path)
nb = nbm.get_notebook(name, path)['content']
nbm.mark_trusted_cells(nb, name, path)
for cell in nb.worksheets[0].cells:
if cell.cell_type == 'code':
assert cell.trusted
def test_check_and_sign(self):
nbm = self.nbm
nb, name, path = self.new_notebook()
nbm.mark_trusted_cells(nb, name, path)
nbm.check_and_sign(nb, name, path)
assert not nbm.notary.check_signature(nb)
nbm.trust_notebook(name, path)
nb = nbm.get_notebook(name, path)['content']
nbm.mark_trusted_cells(nb, name, path)
nbm.check_and_sign(nb, name, path)
assert nbm.notary.check_signature(nb)

@ -133,6 +133,9 @@ var IPython = (function (IPython) {
});
this.element.find('#restore_checkpoint').click(function () {
});
this.element.find('#trust_notebook').click(function () {
IPython.notebook.trust_notebook();
});
this.element.find('#kill_and_exit').click(function () {
IPython.notebook.session.delete();
setTimeout(function(){

@ -1786,6 +1786,75 @@ var IPython = (function (IPython) {
$([IPython.events]).trigger('notebook_save_failed.Notebook', [xhr, status, error]);
};
/**
* Explicitly trust the output of this notebook.
*
* @method trust_notebook
*/
Notebook.prototype.trust_notebook = function (extra_settings) {
// We do the call with settings so we can set cache to false.
var settings = {
processData : false,
cache : false,
type : "POST",
success : $.proxy(this._trust_notebook_success, this),
error : $.proxy(this._trust_notebook_error, this)
};
if (extra_settings) {
for (var key in extra_settings) {
settings[key] = extra_settings[key];
}
}
var body = $("<div>").append($("<p>")
.text("A trusted IPython notebook may execute hidden malicious code ")
.append($("<strong>")
.append(
$("<em>").text("when you open it")
)
).append(".")
).append($("<p>")
.text("For more information, see the ")
.append($("<a>").attr("href", "http://ipython.org/security.html")
.text("IPython security documentation")
).append(".")
);
var nb = this;
IPython.dialog.modal({
title: "Trust this notebook?",
body: body,
buttons: {
Cancel : {},
Trust : {
class : "btn-danger",
click : function () {
$([IPython.events]).trigger('notebook_trusting.Notebook');
var url = utils.url_join_encode(
nb.base_url,
'api/notebooks',
nb.notebook_path,
nb.notebook_name,
'trust'
);
$.ajax(url, settings);
}
}
}
});
};
Notebook.prototype._trust_notebook_success = function (data, status, xhr) {
$([IPython.events]).trigger('notebook_trusted.Notebook');
window.location.reload();
};
Notebook.prototype._trust_notebook_error = function (xhr, status, error) {
$([IPython.events]).trigger('notebook_trust_failed.Notebook', [xhr, status, error]);
};
Notebook.prototype.new_notebook = function(){
var path = this.notebook_path;
var base_url = this.base_url;

@ -86,7 +86,10 @@ class="notebook_app"
</ul>
</li>
<li class="divider"></li>
<li id="trust_notebook"
title="Trust the output of this notebook">
<a href="#" >Trust Notebook</a></li>
<li class="divider"></li>
<li id="kill_and_exit"
title="Shutdown this notebook's kernel, and close this window">
<a href="#" >Close and halt</a></li>

Loading…
Cancel
Save