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