diff --git a/IPython/html/services/notebooks/filenbmanager.py b/IPython/html/services/notebooks/filenbmanager.py
index 749892f..81aeeed 100644
--- a/IPython/html/services/notebooks/filenbmanager.py
+++ b/IPython/html/services/notebooks/filenbmanager.py
@@ -26,7 +26,7 @@ import shutil
from tornado import web
from .nbmanager import NotebookManager
-from IPython.nbformat import current
+from IPython.nbformat import current, sign
from IPython.utils.traitlets import Unicode, Dict, Bool, TraitError
from IPython.utils import tz
@@ -207,13 +207,14 @@ 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))
model['content'] = nb
+ sign.mark_trusted_cells(nb, self.secret)
return model
def save_notebook_model(self, model, name='', path=''):
@@ -236,6 +237,10 @@ class FileNotebookManager(NotebookManager):
# Save the notebook file
os_path = self.get_os_path(new_name, new_path)
nb = current.to_notebook_json(model['content'])
+
+ if sign.check_trusted_cells(nb):
+ sign.trust_notebook(nb, self.secret, self.signature_scheme)
+
if 'name' in nb['metadata']:
nb['metadata']['name'] = u''
try:
diff --git a/IPython/html/services/notebooks/nbmanager.py b/IPython/html/services/notebooks/nbmanager.py
index 0660e3a..a638e8d 100644
--- a/IPython/html/services/notebooks/nbmanager.py
+++ b/IPython/html/services/notebooks/nbmanager.py
@@ -17,12 +17,16 @@ Authors:
# Imports
#-----------------------------------------------------------------------------
+import base64
+import hashlib
+import io
import os
from IPython.config.configurable import LoggingConfigurable
+from IPython.core.application import BaseIPythonApplication
from IPython.nbformat import current
from IPython.utils import py3compat
-from IPython.utils.traitlets import Unicode, TraitError
+from IPython.utils.traitlets import Unicode, TraitError, Enum, Bytes
#-----------------------------------------------------------------------------
# Classes
@@ -42,6 +46,35 @@ class NotebookManager(LoggingConfigurable):
filename_ext = Unicode(u'.ipynb')
+ signature_scheme = Enum(hashlib.algorithms, default_value='sha256', config=True,
+ help="""The signature scheme used to sign notebooks."""
+ )
+
+ secret = Bytes(config=True,
+ help="""The secret key with which notebooks are signed."""
+ )
+ def _secret_default(self):
+ # note : this assumes an Application is running
+ profile_dir = BaseIPythonApplication.instance().profile_dir
+ secret_file = os.path.join(profile_dir.security_dir, 'notebook_secret')
+ if os.path.exists(secret_file):
+ with io.open(secret_file, 'rb') as f:
+ return f.read()
+ else:
+ secret = base64.encodestring(os.urandom(1024))
+ self.log.info("Writing output secret to %s", secret_file)
+ with io.open(secret_file, 'wb') as f:
+ f.write(secret)
+ try:
+ os.chmod(secret_file, 0o600)
+ except OSError:
+ self.log.warn(
+ "Could not set permissions on %s",
+ secret_file
+ )
+ return secret
+
+
def path_exists(self, path):
"""Does the API-style path (directory) actually exist?
diff --git a/IPython/html/static/notebook/js/codecell.js b/IPython/html/static/notebook/js/codecell.js
index fed8c31..a36e82a 100644
--- a/IPython/html/static/notebook/js/codecell.js
+++ b/IPython/html/static/notebook/js/codecell.js
@@ -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;
};
diff --git a/IPython/html/static/notebook/js/outputarea.js b/IPython/html/static/notebook/js/outputarea.js
index 3e67352..972aae7 100644
--- a/IPython/html/static/notebook/js/outputarea.js
+++ b/IPython/html/static/notebook/js/outputarea.js
@@ -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;
@@ -757,6 +773,7 @@ var IPython = (function (IPython) {
// clear all, no need for logic
this.element.html("");
this.outputs = [];
+ this.trusted = true;
this.unscroll_area();
return;
};
@@ -769,13 +786,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