diff --git a/IPython/html/services/contents/tests/test_manager.py b/IPython/html/services/contents/tests/test_manager.py index 74f4716..f531742 100644 --- a/IPython/html/services/contents/tests/test_manager.py +++ b/IPython/html/services/contents/tests/test_manager.py @@ -3,6 +3,7 @@ from __future__ import print_function import os +import time from tornado.web import HTTPError from unittest import TestCase @@ -156,6 +157,7 @@ class TestContentsManager(TestCase): full_model = cm.get(path) nb = full_model['content'] + nb['metadata']['counter'] = int(1e6 * time.time()) self.add_code_cell(nb) cm.save(full_model, path) diff --git a/IPython/nbformat/sign.py b/IPython/nbformat/sign.py index fb7b426..3280262 100644 --- a/IPython/nbformat/sign.py +++ b/IPython/nbformat/sign.py @@ -1,18 +1,27 @@ -"""Functions for signing notebooks""" +"""Utilities for signing notebooks""" # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. import base64 from contextlib import contextmanager +from datetime import datetime import hashlib from hmac import HMAC import io import os +try: + import sqlite3 +except ImportError: + try: + from pysqlite2 import dbapi2 as sqlite3 + except ImportError: + sqlite3 = None + from IPython.utils.io import atomic_writing -from IPython.utils.py3compat import string_types, unicode_type, cast_bytes -from IPython.utils.traitlets import Instance, Bytes, Enum, Any, Unicode, Bool +from IPython.utils.py3compat import unicode_type, cast_bytes +from IPython.utils.traitlets import Instance, Bytes, Enum, Any, Unicode, Bool, Integer from IPython.config import LoggingConfigurable, MultipleInstanceError from IPython.core.application import BaseIPythonApplication, base_flags @@ -93,6 +102,48 @@ class NotebookNotary(LoggingConfigurable): app.initialize(argv=[]) return app.profile_dir + db_file = Unicode(config=True, + help="""The sqlite file in which to store notebook signatures. + By default, this will be in your IPython profile. + You can set it to ':memory:' to disable sqlite writing to the filesystem. + """) + def _db_file_default(self): + if self.profile_dir is None: + return ':memory:' + return os.path.join(self.profile_dir.security_dir, u'nbsignatures.db') + + # 64k entries ~ 12MB + cache_size = Integer(65535, config=True, + help="""The number of notebook signatures to cache. + When the number of signatures exceeds this value, + the oldest 25% of signatures will be culled. + """ + ) + db = Any() + def _db_default(self): + if sqlite3 is None: + self.log.warn("Missing SQLite3, all notebooks will be untrusted!") + return + kwargs = dict(detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES) + db = sqlite3.connect(self.db_file, **kwargs) + self.init_db(db) + return db + + def init_db(self, db): + db.execute(""" + CREATE TABLE IF NOT EXISTS nbsignatures + ( + id integer PRIMARY KEY AUTOINCREMENT, + algorithm text, + signature text, + path text, + last_seen timestamp + )""") + db.execute(""" + CREATE INDEX IF NOT EXISTS algosig ON nbsignatures(algorithm, signature) + """) + db.commit() + algorithm = Enum(algorithms, default_value='sha256', config=True, help="""The hashing algorithm used to sign notebooks.""" ) @@ -168,28 +219,72 @@ class NotebookNotary(LoggingConfigurable): """ if nb.nbformat < 3: return False - stored_signature = nb['metadata'].get('signature', None) - if not stored_signature \ - or not isinstance(stored_signature, string_types) \ - or ':' not in stored_signature: + if self.db is None: return False - stored_algo, sig = stored_signature.split(':', 1) - if self.algorithm != stored_algo: + signature = self.compute_signature(nb) + r = self.db.execute("""SELECT id FROM nbsignatures WHERE + algorithm = ? AND + signature = ?; + """, (self.algorithm, signature)).fetchone() + if r is None: return False - my_signature = self.compute_signature(nb) - return my_signature == sig + self.db.execute("""UPDATE nbsignatures SET last_seen = ? WHERE + algorithm = ? AND + signature = ?; + """, + (datetime.utcnow(), self.algorithm, signature), + ) + self.db.commit() + return True def sign(self, nb): - """Sign a notebook, indicating that its output is trusted - - stores 'algo:hmac-hexdigest' in notebook.metadata.signature + """Sign a notebook, indicating that its output is trusted on this machine - e.g. 'sha256:deadbeef123...' + Stores hash algorithm and hmac digest in a local database of trusted notebooks. """ if nb.nbformat < 3: return signature = self.compute_signature(nb) - nb['metadata']['signature'] = "%s:%s" % (self.algorithm, signature) + self.store_signature(signature, nb) + + def store_signature(self, signature, nb): + if self.db is None: + return + self.db.execute("""INSERT OR IGNORE INTO nbsignatures + (algorithm, signature, last_seen) VALUES (?, ?, ?)""", + (self.algorithm, signature, datetime.utcnow()) + ) + self.db.execute("""UPDATE nbsignatures SET last_seen = ? WHERE + algorithm = ? AND + signature = ?; + """, + (datetime.utcnow(), self.algorithm, signature), + ) + self.db.commit() + n, = self.db.execute("SELECT Count(*) FROM nbsignatures").fetchone() + if n > self.cache_size: + self.cull_db() + + def unsign(self, nb): + """Ensure that a notebook is untrusted + + by removing its signature from the trusted database, if present. + """ + signature = self.compute_signature(nb) + self.db.execute("""DELETE FROM nbsignatures WHERE + algorithm = ? AND + signature = ?; + """, + (self.algorithm, signature) + ) + self.db.commit() + + def cull_db(self): + """Cull oldest 25% of the trusted signatures when the size limit is reached""" + self.db.execute("""DELETE FROM nbsignatures WHERE id IN ( + SELECT id FROM nbsignatures ORDER BY last_seen DESC LIMIT -1 OFFSET ? + ); + """, (max(int(0.75 * self.cache_size), 1),)) def mark_cells(self, nb, trusted): """Mark cells as trusted if the notebook's signature can be verified @@ -222,11 +317,9 @@ class NotebookNotary(LoggingConfigurable): # explicitly safe output if nbformat_version >= 4: - safe = {'text/plain', 'image/png', 'image/jpeg'} unsafe_output_types = ['execute_result', 'display_data'] safe_keys = {"output_type", "execution_count", "metadata"} else: # v3 - safe = {'text', 'png', 'jpeg'} unsafe_output_types = ['pyout', 'display_data'] safe_keys = {"output_type", "prompt_number", "metadata"} @@ -261,7 +354,7 @@ class NotebookNotary(LoggingConfigurable): trust_flags = { 'reset' : ( {'TrustNotebookApp' : { 'reset' : True}}, - """Generate a new key for notebook signature. + """Delete the trusted notebook cache. All previously signed notebooks will become untrusted. """ ), @@ -290,7 +383,7 @@ class TrustNotebookApp(BaseIPythonApplication): flags = trust_flags reset = Bool(False, config=True, - help="""If True, generate a new key for notebook signature. + help="""If True, delete the trusted signature cache. After reset, all previously signed notebooks will become untrusted. """ ) @@ -320,6 +413,9 @@ class TrustNotebookApp(BaseIPythonApplication): def start(self): if self.reset: + if os.path.exists(self.notary.db_file): + print("Removing trusted signature cache: %s" % self.notary.db_file) + os.remove(self.notary.db_file) self.generate_new_key() return if not self.extra_args: diff --git a/IPython/nbformat/tests/test_sign.py b/IPython/nbformat/tests/test_sign.py index 2516343..b0009f9 100644 --- a/IPython/nbformat/tests/test_sign.py +++ b/IPython/nbformat/tests/test_sign.py @@ -3,6 +3,9 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +import copy +import time + from .base import TestsBase from IPython.nbformat import read, sign @@ -13,8 +16,9 @@ class TestNotary(TestsBase): def setUp(self): self.notary = sign.NotebookNotary( + db_file=':memory:', secret=b'secret', - profile_dir=get_ipython().profile_dir + profile_dir=get_ipython().profile_dir, ) with self.fopen(u'test3.ipynb', u'r') as f: self.nb = read(f, as_version=4) @@ -25,10 +29,7 @@ class TestNotary(TestsBase): last_sig = '' for algo in sign.algorithms: self.notary.algorithm = algo - self.notary.sign(self.nb) - sig = self.nb.metadata.signature - print(sig) - self.assertEqual(sig[:len(self.notary.algorithm)+1], '%s:' % self.notary.algorithm) + sig = self.notary.compute_signature(self.nb) self.assertNotEqual(last_sig, sig) last_sig = sig @@ -46,9 +47,49 @@ class TestNotary(TestsBase): self.assertNotEqual(sig1, sig2) def test_sign(self): + self.assertFalse(self.notary.check_signature(self.nb)) + self.notary.sign(self.nb) + self.assertTrue(self.notary.check_signature(self.nb)) + + def test_unsign(self): self.notary.sign(self.nb) - sig = self.nb.metadata.signature - self.assertEqual(sig[:len(self.notary.algorithm)+1], '%s:' % self.notary.algorithm) + self.assertTrue(self.notary.check_signature(self.nb)) + self.notary.unsign(self.nb) + self.assertFalse(self.notary.check_signature(self.nb)) + self.notary.unsign(self.nb) + self.assertFalse(self.notary.check_signature(self.nb)) + + def test_cull_db(self): + # this test has various sleeps of 2ms + # to ensure low resolution timestamps compare as expected + dt = 2e-3 + nbs = [ + copy.deepcopy(self.nb) for i in range(10) + ] + for row in self.notary.db.execute("SELECT * FROM nbsignatures"): + print(row) + self.notary.cache_size = 8 + for i, nb in enumerate(nbs[:8]): + nb.metadata.dirty = i + self.notary.sign(nb) + + for i, nb in enumerate(nbs[:8]): + time.sleep(dt) + self.assertTrue(self.notary.check_signature(nb), 'nb %i is trusted' % i) + + # signing the 9th triggers culling of first 3 + # (75% of 8 = 6, 9 - 6 = 3 culled) + self.notary.sign(nbs[8]) + self.assertFalse(self.notary.check_signature(nbs[0])) + self.assertFalse(self.notary.check_signature(nbs[1])) + self.assertFalse(self.notary.check_signature(nbs[2])) + self.assertTrue(self.notary.check_signature(nbs[3])) + # checking nb3 should keep it from being culled: + self.notary.sign(nbs[0]) + self.notary.sign(nbs[1]) + self.notary.sign(nbs[2]) + self.assertTrue(self.notary.check_signature(nbs[3])) + self.assertFalse(self.notary.check_signature(nbs[4])) def test_check_signature(self): nb = self.nb diff --git a/IPython/nbformat/v4/convert.py b/IPython/nbformat/v4/convert.py index c559bdf..1e9e65e 100644 --- a/IPython/nbformat/v4/convert.py +++ b/IPython/nbformat/v4/convert.py @@ -56,6 +56,7 @@ def upgrade(nb, from_version=3, from_minor=0): cells.append(upgrade_cell(cell)) # upgrade metadata nb.metadata.pop('name', '') + nb.metadata.pop('signature', '') # Validate the converted notebook before returning it _warn_if_invalid(nb, nbformat) return nb diff --git a/IPython/nbformat/v4/nbformat.v4.schema.json b/IPython/nbformat/v4/nbformat.v4.schema.json index 365a08a..e90b0f3 100644 --- a/IPython/nbformat/v4/nbformat.v4.schema.json +++ b/IPython/nbformat/v4/nbformat.v4.schema.json @@ -55,10 +55,6 @@ } } }, - "signature": { - "description": "Hash of the notebook.", - "type": "string" - }, "orig_nbformat": { "description": "Original notebook format (major number) before converting the notebook between versions. This should never be written to a file.", "type": "integer", diff --git a/IPython/nbformat/v4/rwbase.py b/IPython/nbformat/v4/rwbase.py index 68b81e0..9a1ca50 100644 --- a/IPython/nbformat/v4/rwbase.py +++ b/IPython/nbformat/v4/rwbase.py @@ -64,6 +64,7 @@ def strip_transient(nb): """ nb.metadata.pop('orig_nbformat', None) nb.metadata.pop('orig_nbformat_minor', None) + nb.metadata.pop('signature', None) for cell in nb.cells: cell.metadata.pop('trusted', None) return nb diff --git a/examples/Customization/Index.ipynb b/examples/Customization/Index.ipynb index 0822cb8..1cc59c2 100644 --- a/examples/Customization/Index.ipynb +++ b/examples/Customization/Index.ipynb @@ -95,9 +95,7 @@ ] } ], - "metadata": { - "signature": "sha256:de8cb1aff3da9097ba3fc7afab4327fc589f2bb1c154feeeb5ed97c87e37486f" - }, + "metadata": {}, "nbformat": 4, "nbformat_minor": 0 } \ No newline at end of file diff --git a/examples/Embedding/Index.ipynb b/examples/Embedding/Index.ipynb index e5ecb6a..1652f15 100644 --- a/examples/Embedding/Index.ipynb +++ b/examples/Embedding/Index.ipynb @@ -186,9 +186,7 @@ ] } ], - "metadata": { - "signature": "sha256:627cdf03b8de558c9344f9d1e8f0beeb2448e37e492d676e6db7b07d33251a2b" - }, + "metadata": {}, "nbformat": 4, "nbformat_minor": 0 } \ No newline at end of file diff --git a/examples/IPython Kernel/Beyond Plain Python.ipynb b/examples/IPython Kernel/Beyond Plain Python.ipynb index da3e5e0..984dee2 100644 --- a/examples/IPython Kernel/Beyond Plain Python.ipynb +++ b/examples/IPython Kernel/Beyond Plain Python.ipynb @@ -1799,9 +1799,7 @@ ] } ], - "metadata": { - "signature": "sha256:31071a05d0ecd75ed72fe3f0de0ad447a6f85cffe382c26efa5e68db1fee54ee" - }, + "metadata": {}, "nbformat": 4, "nbformat_minor": 0 } \ No newline at end of file diff --git a/examples/IPython Kernel/Capturing Output.ipynb b/examples/IPython Kernel/Capturing Output.ipynb index 2cc7970..ff0f7c9 100644 --- a/examples/IPython Kernel/Capturing Output.ipynb +++ b/examples/IPython Kernel/Capturing Output.ipynb @@ -481,9 +481,7 @@ ] } ], - "metadata": { - "signature": "sha256:df6354daf203e842bc040989d149760382d8ceec769160e4efe8cde9dfcb9107" - }, + "metadata": {}, "nbformat": 4, "nbformat_minor": 0 } \ No newline at end of file diff --git a/examples/IPython Kernel/Custom Display Logic.ipynb b/examples/IPython Kernel/Custom Display Logic.ipynb index 5b32c8b..4f40795 100644 --- a/examples/IPython Kernel/Custom Display Logic.ipynb +++ b/examples/IPython Kernel/Custom Display Logic.ipynb @@ -1317,9 +1317,7 @@ "source": [] } ], - "metadata": { - "signature": "sha256:86c779d5798c4a68bda7e71c8ef320cb7ba9d7e3d0f1bc4b828ee65f617a5ae3" - }, + "metadata": {}, "nbformat": 4, "nbformat_minor": 0 } \ No newline at end of file diff --git a/examples/IPython Kernel/Index.ipynb b/examples/IPython Kernel/Index.ipynb index 9418e0e..f674914 100644 --- a/examples/IPython Kernel/Index.ipynb +++ b/examples/IPython Kernel/Index.ipynb @@ -160,9 +160,7 @@ ] } ], - "metadata": { - "signature": "sha256:ee769d05a7e195e4b8546ef9a866ef03e59bff2f0fcba499d168c06b516aa79a" - }, + "metadata": {}, "nbformat": 4, "nbformat_minor": 0 } \ No newline at end of file diff --git a/examples/IPython Kernel/Plotting in the Notebook.ipynb b/examples/IPython Kernel/Plotting in the Notebook.ipynb index 00e713b..0d09126 100644 --- a/examples/IPython Kernel/Plotting in the Notebook.ipynb +++ b/examples/IPython Kernel/Plotting in the Notebook.ipynb @@ -732,9 +732,7 @@ ] } ], - "metadata": { - "signature": "sha256:74dbf5caa25c937be70dfe2ab509783a01f4a2044850d7044e729300a8c3644d" - }, + "metadata": {}, "nbformat": 4, "nbformat_minor": 0 } \ No newline at end of file diff --git a/examples/IPython Kernel/Raw Input in the Notebook.ipynb b/examples/IPython Kernel/Raw Input in the Notebook.ipynb index f9887b2..78cf9bd 100644 --- a/examples/IPython Kernel/Raw Input in the Notebook.ipynb +++ b/examples/IPython Kernel/Raw Input in the Notebook.ipynb @@ -150,9 +150,7 @@ ] } ], - "metadata": { - "signature": "sha256:ac5c21534f3dd013c78d4d201527f3ed4dea5b6fad4116b8d23c67ba107e48c3" - }, + "metadata": {}, "nbformat": 4, "nbformat_minor": 0 } \ No newline at end of file diff --git a/examples/IPython Kernel/Rich Output.ipynb b/examples/IPython Kernel/Rich Output.ipynb index c3e8f0b..9cbf86b 100644 --- a/examples/IPython Kernel/Rich Output.ipynb +++ b/examples/IPython Kernel/Rich Output.ipynb @@ -3051,9 +3051,7 @@ ] } ], - "metadata": { - "signature": "sha256:cf83dc9e6288480ac94c44a5983b4ee421f0ade792a9fac64bc00719263386c0" - }, + "metadata": {}, "nbformat": 4, "nbformat_minor": 0 } \ No newline at end of file diff --git a/examples/IPython Kernel/SymPy.ipynb b/examples/IPython Kernel/SymPy.ipynb index 101fec7..68172fa 100644 --- a/examples/IPython Kernel/SymPy.ipynb +++ b/examples/IPython Kernel/SymPy.ipynb @@ -1647,9 +1647,7 @@ ] } ], - "metadata": { - "signature": "sha256:a0f4cda82587c5b8e7a4502192d1210abedac9084077604eb60aea15f262a344" - }, + "metadata": {}, "nbformat": 4, "nbformat_minor": 0 } \ No newline at end of file diff --git a/examples/IPython Kernel/Terminal Usage.ipynb b/examples/IPython Kernel/Terminal Usage.ipynb index aa63300..e01e038 100644 --- a/examples/IPython Kernel/Terminal Usage.ipynb +++ b/examples/IPython Kernel/Terminal Usage.ipynb @@ -261,9 +261,7 @@ ] } ], - "metadata": { - "signature": "sha256:993106eecfd7abe1920e1dbe670c4518189c26e7b29dcc541835f7dcf6fffbb2" - }, + "metadata": {}, "nbformat": 4, "nbformat_minor": 0 } \ No newline at end of file diff --git a/examples/IPython Kernel/Third Party Rich Output.ipynb b/examples/IPython Kernel/Third Party Rich Output.ipynb index 1e6cb00..a025bb3 100644 --- a/examples/IPython Kernel/Third Party Rich Output.ipynb +++ b/examples/IPython Kernel/Third Party Rich Output.ipynb @@ -517,9 +517,7 @@ ] } ], - "metadata": { - "signature": "sha256:123d82ef0551f78e5dca94db6e00f1e10ae07d930467cf44709ccc6a9216776a" - }, + "metadata": {}, "nbformat": 4, "nbformat_minor": 0 } \ No newline at end of file diff --git a/examples/IPython Kernel/Trapezoid Rule.ipynb b/examples/IPython Kernel/Trapezoid Rule.ipynb index e73bbb3..2bfb555 100644 --- a/examples/IPython Kernel/Trapezoid Rule.ipynb +++ b/examples/IPython Kernel/Trapezoid Rule.ipynb @@ -374,9 +374,7 @@ ] } ], - "metadata": { - "signature": "sha256:cbf49a9ceac0258773ae0a652e9ac7c13e0d042debbf0115cdc081862b5f7308" - }, + "metadata": {}, "nbformat": 4, "nbformat_minor": 0 } \ No newline at end of file diff --git a/examples/IPython Kernel/Working With External Code.ipynb b/examples/IPython Kernel/Working With External Code.ipynb index 243ca1b..6f6c1d2 100644 --- a/examples/IPython Kernel/Working With External Code.ipynb +++ b/examples/IPython Kernel/Working With External Code.ipynb @@ -333,9 +333,7 @@ ] } ], - "metadata": { - "signature": "sha256:4352d4e1c693d919ce40b29ecf5a536917160df68b19a85caccedb1ea7ad06e1" - }, + "metadata": {}, "nbformat": 4, "nbformat_minor": 0 } \ No newline at end of file diff --git a/examples/Index.ipynb b/examples/Index.ipynb index 831c309..3e9bc4d 100644 --- a/examples/Index.ipynb +++ b/examples/Index.ipynb @@ -41,9 +41,7 @@ ] } ], - "metadata": { - "signature": "sha256:0b4b631419772e40e0f4893f5a0f0fe089a39e46c8862af0256164628394302d" - }, + "metadata": {}, "nbformat": 4, "nbformat_minor": 0 } \ No newline at end of file diff --git a/examples/Interactive Widgets/Beat Frequencies.ipynb b/examples/Interactive Widgets/Beat Frequencies.ipynb index 2ae3228..13049a3 100644 --- a/examples/Interactive Widgets/Beat Frequencies.ipynb +++ b/examples/Interactive Widgets/Beat Frequencies.ipynb @@ -591,9 +591,7 @@ ] } ], - "metadata": { - "signature": "sha256:da6a3d73881e321e5ef406aea3e7d706c9dd405c94675318afde957d292f9cb9" - }, + "metadata": {}, "nbformat": 4, "nbformat_minor": 0 } \ No newline at end of file diff --git a/examples/Interactive Widgets/Exploring Graphs.ipynb b/examples/Interactive Widgets/Exploring Graphs.ipynb index 05f7c1d..74f060d 100644 --- a/examples/Interactive Widgets/Exploring Graphs.ipynb +++ b/examples/Interactive Widgets/Exploring Graphs.ipynb @@ -830,9 +830,7 @@ ] } ], - "metadata": { - "signature": "sha256:61f24f38b76cb12d46c178eadc5d85d61bd0fc27f959236ea756ef8e1adc2693" - }, + "metadata": {}, "nbformat": 4, "nbformat_minor": 0 } \ No newline at end of file diff --git a/examples/Interactive Widgets/Factoring.ipynb b/examples/Interactive Widgets/Factoring.ipynb index bf7e969..ffab0b5 100644 --- a/examples/Interactive Widgets/Factoring.ipynb +++ b/examples/Interactive Widgets/Factoring.ipynb @@ -131,9 +131,7 @@ "source": [] } ], - "metadata": { - "signature": "sha256:5f38c57d9570e4b6f6edf98c38a7bff81cd16365baa11fd40265f1504bfc008c" - }, + "metadata": {}, "nbformat": 4, "nbformat_minor": 0 } \ No newline at end of file diff --git a/examples/Interactive Widgets/Image Browser.ipynb b/examples/Interactive Widgets/Image Browser.ipynb index 96a8005..8430f80 100644 --- a/examples/Interactive Widgets/Image Browser.ipynb +++ b/examples/Interactive Widgets/Image Browser.ipynb @@ -297,9 +297,7 @@ ] } ], - "metadata": { - "signature": "sha256:4e9f4ce8fa9be2e33ffdae399daec11bf3ef63a6f0065b1d59739310ebfe8008" - }, + "metadata": {}, "nbformat": 4, "nbformat_minor": 0 } \ No newline at end of file diff --git a/examples/Interactive Widgets/Image Processing.ipynb b/examples/Interactive Widgets/Image Processing.ipynb index c6d4669..cfa1e10 100644 --- a/examples/Interactive Widgets/Image Processing.ipynb +++ b/examples/Interactive Widgets/Image Processing.ipynb @@ -26762,9 +26762,7 @@ ] } ], - "metadata": { - "signature": "sha256:d75ab1c53fa3389eeac78ecf8e89beb52871950f296aad25776699b6d6125037" - }, + "metadata": {}, "nbformat": 4, "nbformat_minor": 0 } \ No newline at end of file diff --git a/examples/Interactive Widgets/Lorenz Differential Equations.ipynb b/examples/Interactive Widgets/Lorenz Differential Equations.ipynb index 41130b0..4fdfac4 100644 --- a/examples/Interactive Widgets/Lorenz Differential Equations.ipynb +++ b/examples/Interactive Widgets/Lorenz Differential Equations.ipynb @@ -13428,9 +13428,7 @@ ] } ], - "metadata": { - "signature": "sha256:c6ccfb14927d633933da8b5c0f776a8da756d833654b5a3f8b6121b390ca6424" - }, + "metadata": {}, "nbformat": 4, "nbformat_minor": 0 } \ No newline at end of file diff --git a/examples/Notebook/Connecting with the Qt Console.ipynb b/examples/Notebook/Connecting with the Qt Console.ipynb index f620bb8..c76348b 100644 --- a/examples/Notebook/Connecting with the Qt Console.ipynb +++ b/examples/Notebook/Connecting with the Qt Console.ipynb @@ -131,9 +131,7 @@ ] } ], - "metadata": { - "signature": "sha256:9a1dd30de04270174c09ef33ca214e5b15ca3721547420087c1563ad557d78e3" - }, + "metadata": {}, "nbformat": 4, "nbformat_minor": 0 } \ No newline at end of file diff --git a/examples/Notebook/Index.ipynb b/examples/Notebook/Index.ipynb index 44baf4e..6d47cd5 100644 --- a/examples/Notebook/Index.ipynb +++ b/examples/Notebook/Index.ipynb @@ -68,9 +68,7 @@ ] } ], - "metadata": { - "signature": "sha256:5a3c5ebae9154e13957e7c9d28bd3b7697c4b14f965482feb288d2cdf078983e" - }, + "metadata": {}, "nbformat": 4, "nbformat_minor": 0 } \ No newline at end of file diff --git a/examples/Notebook/Notebook Security.ipynb b/examples/Notebook/Notebook Security.ipynb index 8b28ec3..1084e08 100644 --- a/examples/Notebook/Notebook Security.ipynb +++ b/examples/Notebook/Notebook Security.ipynb @@ -1,8 +1,6 @@ { "cells": [], - "metadata": { - "signature": "sha256:0abf067a20ebda26a671db997ac954770350d292dff7b7d6a4ace8808f70aca1" - }, + "metadata": {}, "nbformat": 4, "nbformat_minor": 0 } \ No newline at end of file diff --git a/examples/Notebook/Running the Notebook Server.ipynb b/examples/Notebook/Running the Notebook Server.ipynb index 38ada9a..2a29326 100644 --- a/examples/Notebook/Running the Notebook Server.ipynb +++ b/examples/Notebook/Running the Notebook Server.ipynb @@ -355,9 +355,7 @@ ] } ], - "metadata": { - "signature": "sha256:ee4b22b4c949fe21b3e5cda24f0916ba59d8c09443f4a897d98b96d4a73ac335" - }, + "metadata": {}, "nbformat": 4, "nbformat_minor": 0 } \ No newline at end of file diff --git a/examples/Notebook/Working With Markdown Cells.ipynb b/examples/Notebook/Working With Markdown Cells.ipynb index d8f3dc6..e2dead7 100644 --- a/examples/Notebook/Working With Markdown Cells.ipynb +++ b/examples/Notebook/Working With Markdown Cells.ipynb @@ -296,9 +296,7 @@ ] } ], - "metadata": { - "signature": "sha256:3b7cae0c0936f25e6ccb7acafe310c08a4162a1a7fd66fa9874a52cffa0f64f9" - }, + "metadata": {}, "nbformat": 4, "nbformat_minor": 0 } \ No newline at end of file diff --git a/examples/Parallel Computing/Index.ipynb b/examples/Parallel Computing/Index.ipynb index 6f49df0..f500583 100644 --- a/examples/Parallel Computing/Index.ipynb +++ b/examples/Parallel Computing/Index.ipynb @@ -346,9 +346,7 @@ ] } ], - "metadata": { - "signature": "sha256:1e9336d35cc07875300c5b876df6ce1f1971c2ee94870788c6ea32bbb789c42b" - }, + "metadata": {}, "nbformat": 4, "nbformat_minor": 0 } \ No newline at end of file diff --git a/examples/Parallel Computing/Monte Carlo Options.ipynb b/examples/Parallel Computing/Monte Carlo Options.ipynb index c32dbc5..bd4665a 100644 --- a/examples/Parallel Computing/Monte Carlo Options.ipynb +++ b/examples/Parallel Computing/Monte Carlo Options.ipynb @@ -2533,9 +2533,7 @@ ] } ], - "metadata": { - "signature": "sha256:1b19dedc6473d4e886e549020c6710f2d14c17296168a02e7e7fa9673912b893" - }, + "metadata": {}, "nbformat": 4, "nbformat_minor": 0 } \ No newline at end of file diff --git a/examples/Parallel Computing/Parallel Decorator and map.ipynb b/examples/Parallel Computing/Parallel Decorator and map.ipynb index f288318..45097d6 100644 --- a/examples/Parallel Computing/Parallel Decorator and map.ipynb +++ b/examples/Parallel Computing/Parallel Decorator and map.ipynb @@ -107,9 +107,7 @@ "source": [] } ], - "metadata": { - "signature": "sha256:8781781d8835d77bf0f71b535b72ee2718405543c48e87ad0408242119cdb3cc" - }, + "metadata": {}, "nbformat": 4, "nbformat_minor": 0 } \ No newline at end of file