mirror of
https://github.com/jupyter/notebook.git
synced 2025-03-13 13:17:50 +08:00
commit
7c722d4b6c
@ -207,12 +207,13 @@ class FileNotebookManager(NotebookManager):
|
||||
model['path'] = path
|
||||
model['last_modified'] = last_modified
|
||||
model['created'] = created
|
||||
if content is True:
|
||||
if content:
|
||||
with io.open(os_path, 'r', encoding='utf-8') as f:
|
||||
try:
|
||||
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)
|
||||
model['content'] = nb
|
||||
return model
|
||||
|
||||
@ -236,6 +237,9 @@ class FileNotebookManager(NotebookManager):
|
||||
# Save the notebook file
|
||||
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)
|
||||
|
||||
if 'name' in nb['metadata']:
|
||||
nb['metadata']['name'] = u''
|
||||
try:
|
||||
|
@ -20,9 +20,9 @@ Authors:
|
||||
import os
|
||||
|
||||
from IPython.config.configurable import LoggingConfigurable
|
||||
from IPython.nbformat import current
|
||||
from IPython.nbformat import current, sign
|
||||
from IPython.utils import py3compat
|
||||
from IPython.utils.traitlets import Unicode, TraitError
|
||||
from IPython.utils.traitlets import Instance, Unicode, TraitError
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
# Classes
|
||||
@ -42,6 +42,30 @@ class NotebookManager(LoggingConfigurable):
|
||||
|
||||
filename_ext = Unicode(u'.ipynb')
|
||||
|
||||
notary = Instance(sign.NotebookNotary)
|
||||
def _notary_default(self):
|
||||
return sign.NotebookNotary(parent=self)
|
||||
|
||||
def check_and_sign(self, nb, path, name):
|
||||
"""Check for trusted cells, and sign the notebook.
|
||||
|
||||
Called as a part of saving notebooks.
|
||||
"""
|
||||
if self.notary.check_cells(nb):
|
||||
self.notary.sign(nb)
|
||||
else:
|
||||
self.log.warn("Saving untrusted notebook %s/%s", path, name)
|
||||
|
||||
def mark_trusted_cells(self, nb, path, name):
|
||||
"""Mark cells as trusted if the notebook signature matches.
|
||||
|
||||
Called as a part of loading notebooks.
|
||||
"""
|
||||
trusted = self.notary.check_signature(nb)
|
||||
if not trusted:
|
||||
self.log.warn("Notebook %s/%s is not trusted", path, name)
|
||||
self.notary.mark_cells(nb, trusted)
|
||||
|
||||
def path_exists(self, path):
|
||||
"""Does the API-style path (directory) actually exist?
|
||||
|
||||
|
@ -530,6 +530,7 @@ var IPython = (function (IPython) {
|
||||
} else {
|
||||
this.set_input_prompt();
|
||||
}
|
||||
this.output_area.trusted = data.trusted || false;
|
||||
this.output_area.fromJSON(data.outputs);
|
||||
if (data.collapsed !== undefined) {
|
||||
if (data.collapsed) {
|
||||
@ -552,6 +553,7 @@ var IPython = (function (IPython) {
|
||||
var outputs = this.output_area.toJSON();
|
||||
data.outputs = outputs;
|
||||
data.language = 'python';
|
||||
data.trusted = this.output_area.trusted;
|
||||
data.collapsed = this.collapsed;
|
||||
return data;
|
||||
};
|
||||
|
@ -31,6 +31,7 @@ var IPython = (function (IPython) {
|
||||
this.outputs = [];
|
||||
this.collapsed = false;
|
||||
this.scrolled = false;
|
||||
this.trusted = true;
|
||||
this.clear_queued = null;
|
||||
if (prompt_area === undefined) {
|
||||
this.prompt_area = true;
|
||||
@ -309,7 +310,7 @@ var IPython = (function (IPython) {
|
||||
});
|
||||
return json;
|
||||
};
|
||||
|
||||
|
||||
OutputArea.prototype.append_output = function (json) {
|
||||
this.expand();
|
||||
// Clear the output if clear is queued.
|
||||
@ -331,6 +332,7 @@ var IPython = (function (IPython) {
|
||||
} else if (json.output_type === 'stream') {
|
||||
this.append_stream(json);
|
||||
}
|
||||
|
||||
this.outputs.push(json);
|
||||
|
||||
// Only reset the height to automatic if the height is currently
|
||||
@ -526,12 +528,26 @@ var IPython = (function (IPython) {
|
||||
'text/plain'
|
||||
];
|
||||
|
||||
OutputArea.safe_outputs = {
|
||||
'text/plain' : true,
|
||||
'image/png' : true,
|
||||
'image/jpeg' : true
|
||||
};
|
||||
|
||||
OutputArea.prototype.append_mime_type = function (json, element) {
|
||||
|
||||
for (var type_i in OutputArea.display_order) {
|
||||
var type = OutputArea.display_order[type_i];
|
||||
var append = OutputArea.append_map[type];
|
||||
if ((json[type] !== undefined) && append) {
|
||||
if (!this.trusted && !OutputArea.safe_outputs[type]) {
|
||||
// not trusted show warning and do not display
|
||||
var content = {
|
||||
text : "Untrusted " + type + " output ignored.",
|
||||
stream : "stderr"
|
||||
}
|
||||
this.append_stream(content);
|
||||
continue;
|
||||
}
|
||||
var md = json.metadata || {};
|
||||
append.apply(this, [json[type], md, element]);
|
||||
return true;
|
||||
@ -753,6 +769,7 @@ var IPython = (function (IPython) {
|
||||
// clear all, no need for logic
|
||||
this.element.html("");
|
||||
this.outputs = [];
|
||||
this.trusted = true;
|
||||
this.unscroll_area();
|
||||
return;
|
||||
};
|
||||
@ -765,13 +782,6 @@ var IPython = (function (IPython) {
|
||||
var len = outputs.length;
|
||||
var data;
|
||||
|
||||
// We don't want to display javascript on load, so remove it from the
|
||||
// display order for the duration of this function call, but be sure to
|
||||
// put it back in there so incoming messages that contain javascript
|
||||
// representations get displayed
|
||||
var js_index = OutputArea.display_order.indexOf('application/javascript');
|
||||
OutputArea.display_order.splice(js_index, 1);
|
||||
|
||||
for (var i=0; i<len; i++) {
|
||||
data = outputs[i];
|
||||
var msg_type = data.output_type;
|
||||
@ -784,9 +794,6 @@ var IPython = (function (IPython) {
|
||||
|
||||
this.append_output(data);
|
||||
}
|
||||
|
||||
// reinsert javascript into display order, see note above
|
||||
OutputArea.display_order.splice(js_index, 0, 'application/javascript');
|
||||
};
|
||||
|
||||
|
||||
|
@ -27,9 +27,9 @@ function assert_has(short_name, json, result, result2) {
|
||||
this.test.assertTrue(json[0].hasOwnProperty(short_name),
|
||||
'toJSON() representation uses ' + short_name);
|
||||
this.test.assertTrue(result.hasOwnProperty(long_name),
|
||||
'toJSON() original embeded JSON keeps ' + long_name);
|
||||
'toJSON() original embedded JSON keeps ' + long_name);
|
||||
this.test.assertTrue(result2.hasOwnProperty(long_name),
|
||||
'fromJSON() embeded ' + short_name + ' gets mime key ' + long_name);
|
||||
'fromJSON() embedded ' + short_name + ' gets mime key ' + long_name);
|
||||
}
|
||||
|
||||
// helper function for checkout that the first two cells have a particular
|
||||
@ -40,11 +40,11 @@ function check_output_area(output_type, keys) {
|
||||
this.wait_for_output(0);
|
||||
json = this.evaluate(function() {
|
||||
var json = IPython.notebook.get_cell(0).output_area.toJSON();
|
||||
// appended cell will initially be empty, lets add it some output
|
||||
var cell = IPython.notebook.get_cell(1).output_area.fromJSON(json);
|
||||
// appended cell will initially be empty, let's add some output
|
||||
IPython.notebook.get_cell(1).output_area.fromJSON(json);
|
||||
return json;
|
||||
});
|
||||
// The evaluate call above happens asyncrhonously: wait for cell[1] to have output
|
||||
// The evaluate call above happens asynchronously: wait for cell[1] to have output
|
||||
this.wait_for_output(1);
|
||||
var result = this.get_output_cell(0);
|
||||
var result2 = this.get_output_cell(1);
|
||||
@ -88,12 +88,19 @@ casper.notebook_test(function () {
|
||||
var num_cells = this.get_cells_length();
|
||||
this.test.assertEquals(num_cells, 2, '%%javascript magic works');
|
||||
this.test.assertTrue(result.hasOwnProperty('application/javascript'),
|
||||
'testing JS embeded with mime key');
|
||||
'testing JS embedded with mime key');
|
||||
});
|
||||
|
||||
//this.thenEvaluate(function() { IPython.notebook.save_notebook(); });
|
||||
this.then(function () {
|
||||
clear_and_execute(this, [
|
||||
"%%javascript",
|
||||
"var a=5;"
|
||||
].join('\n'));
|
||||
});
|
||||
|
||||
|
||||
this.then(function ( ) {
|
||||
this.then(function () {
|
||||
check_output_area.apply(this, ['display_data', ['javascript']]);
|
||||
|
||||
});
|
||||
@ -223,7 +230,9 @@ casper.notebook_test(function () {
|
||||
|
||||
});
|
||||
|
||||
this.then(function ( ) {
|
||||
this.wait_for_output(0, 1);
|
||||
|
||||
this.then(function () {
|
||||
var long_name = 'text/superfancymimetype';
|
||||
var result = this.get_output_cell(0);
|
||||
this.test.assertTrue(result.hasOwnProperty(long_name),
|
||||
|
@ -35,8 +35,10 @@ casper.notebook_test(function () {
|
||||
});
|
||||
|
||||
this.then(function () {
|
||||
var result = this.get_output_cell(0);
|
||||
this.test.assertFalsy(result, "after shutdown: no execution results");
|
||||
var outputs = this.evaluate(function() {
|
||||
return IPython.notebook.get_cell(0).output_area.outputs;
|
||||
})
|
||||
this.test.assertEquals(outputs.length, 0, "after shutdown: no execution results");
|
||||
this.test.assertNot(this.kernel_running(),
|
||||
'after shutdown: IPython.notebook.kernel.running is false ');
|
||||
});
|
||||
|
@ -57,15 +57,18 @@ casper.delete_current_notebook = function () {
|
||||
});
|
||||
};
|
||||
|
||||
// wait for output in a given cell
|
||||
casper.wait_for_output = function (cell_num) {
|
||||
this.waitFor(function (c) {
|
||||
return this.evaluate(function get_output(c) {
|
||||
var cell = IPython.notebook.get_cell(c);
|
||||
return cell.output_area.outputs.length != 0;
|
||||
},
|
||||
// pass parameter from the test suite js to the browser code js
|
||||
{c : cell_num});
|
||||
// wait for the nth output in a given cell
|
||||
casper.wait_for_output = function (cell_num, out_num) {
|
||||
out_num = out_num || 0;
|
||||
this.then(function() {
|
||||
this.waitFor(function (c, o) {
|
||||
return this.evaluate(function get_output(c, o) {
|
||||
var cell = IPython.notebook.get_cell(c);
|
||||
return cell.output_area.outputs.length > o;
|
||||
},
|
||||
// pass parameter from the test suite js to the browser code js
|
||||
{c : cell_num, o : out_num});
|
||||
});
|
||||
},
|
||||
function then() { },
|
||||
function timeout() {
|
||||
@ -73,7 +76,7 @@ casper.wait_for_output = function (cell_num) {
|
||||
});
|
||||
};
|
||||
|
||||
// return the output of a given cell
|
||||
// return an output of a given cell
|
||||
casper.get_output_cell = function (cell_num, out_num) {
|
||||
out_num = out_num || 0;
|
||||
var result = casper.evaluate(function (c, o) {
|
||||
@ -81,7 +84,18 @@ casper.get_output_cell = function (cell_num, out_num) {
|
||||
return cell.output_area.outputs[o];
|
||||
},
|
||||
{c : cell_num, o : out_num});
|
||||
return result;
|
||||
if (!result) {
|
||||
var num_outputs = casper.evaluate(function (c) {
|
||||
var cell = IPython.notebook.get_cell(c);
|
||||
return cell.output_area.outputs.length;
|
||||
},
|
||||
{c : cell_num});
|
||||
this.test.assertTrue(false,
|
||||
"Cell " + cell_num + " has no output #" + out_num + " (" + num_outputs + " total)"
|
||||
);
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
// return the number of cells in the notebook
|
||||
|
@ -462,6 +462,35 @@ on available options, use::
|
||||
:ref:`notebook_public_server`
|
||||
|
||||
|
||||
.. _signing_notebooks:
|
||||
|
||||
Signing Notebooks
|
||||
-----------------
|
||||
|
||||
To prevent untrusted code from executing on users' behalf when notebooks open,
|
||||
we have added a signature to the notebook, stored in metadata.
|
||||
The notebook server verifies this signature when a notebook is opened.
|
||||
If the signature stored in the notebook metadata does not match,
|
||||
javascript and HTML output will not be displayed on load,
|
||||
and must be regenerated by re-executing the cells.
|
||||
|
||||
Any notebook that you have executed yourself *in its entirety* will be considered trusted,
|
||||
and its HTML and javascript output will be displayed on load.
|
||||
|
||||
If you need to see HTML or Javascript output without re-executing,
|
||||
you can explicitly trust notebooks, such as those shared with you,
|
||||
or those that you have written yourself prior to IPython 2.0,
|
||||
at the command-line with::
|
||||
|
||||
$ ipython trust mynotebook.ipynb [other notebooks.ipynb]
|
||||
|
||||
This just generates a new signature stored in each notebook.
|
||||
|
||||
You can generate a new notebook signing key with::
|
||||
|
||||
$ ipython trust --reset
|
||||
|
||||
|
||||
Importing ``.py`` files
|
||||
-----------------------
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user