Show More
@@ -309,15 +309,12 b' class ContentsManager(LoggingConfigurable):' | |||||
309 | Parameters |
|
309 | Parameters | |
310 | ---------- |
|
310 | ---------- | |
311 | nb : dict |
|
311 | nb : dict | |
312 |
The notebook |
|
312 | The notebook dict | |
313 | name : string |
|
313 | name : string | |
314 | The filename of the notebook (for logging) |
|
314 | The filename of the notebook (for logging) | |
315 | path : string |
|
315 | path : string | |
316 | The notebook's directory (for logging) |
|
316 | The notebook's directory (for logging) | |
317 | """ |
|
317 | """ | |
318 | # can't sign old notebooks |
|
|||
319 | if nb['nbformat'] != 4: |
|
|||
320 | return |
|
|||
321 | if self.notary.check_cells(nb): |
|
318 | if self.notary.check_cells(nb): | |
322 | self.notary.sign(nb) |
|
319 | self.notary.sign(nb) | |
323 | else: |
|
320 | else: |
@@ -46,6 +46,20 b' def yield_everything(obj):' | |||||
46 | else: |
|
46 | else: | |
47 | yield unicode_type(obj).encode('utf8') |
|
47 | yield unicode_type(obj).encode('utf8') | |
48 |
|
48 | |||
|
49 | def yield_code_cells(nb): | |||
|
50 | """Iterator that yields all cells in a notebook | |||
|
51 | ||||
|
52 | nbformat version independent | |||
|
53 | """ | |||
|
54 | if nb.nbformat >= 4: | |||
|
55 | for cell in nb['cells']: | |||
|
56 | if cell['cell_type'] == 'code': | |||
|
57 | yield cell | |||
|
58 | elif nb.nbformat == 3: | |||
|
59 | for ws in nb['worksheets']: | |||
|
60 | for cell in ws['cells']: | |||
|
61 | if cell['cell_type'] == 'code': | |||
|
62 | yield cell | |||
49 |
|
63 | |||
50 | @contextmanager |
|
64 | @contextmanager | |
51 | def signature_removed(nb): |
|
65 | def signature_removed(nb): | |
@@ -152,6 +166,8 b' class NotebookNotary(LoggingConfigurable):' | |||||
152 | - the requested scheme is available from hashlib |
|
166 | - the requested scheme is available from hashlib | |
153 | - the computed hash from notebook_signature matches the stored hash |
|
167 | - the computed hash from notebook_signature matches the stored hash | |
154 | """ |
|
168 | """ | |
|
169 | if nb.nbformat < 3: | |||
|
170 | return False | |||
155 | stored_signature = nb['metadata'].get('signature', None) |
|
171 | stored_signature = nb['metadata'].get('signature', None) | |
156 | if not stored_signature \ |
|
172 | if not stored_signature \ | |
157 | or not isinstance(stored_signature, string_types) \ |
|
173 | or not isinstance(stored_signature, string_types) \ | |
@@ -170,6 +186,8 b' class NotebookNotary(LoggingConfigurable):' | |||||
170 |
|
186 | |||
171 | e.g. 'sha256:deadbeef123...' |
|
187 | e.g. 'sha256:deadbeef123...' | |
172 | """ |
|
188 | """ | |
|
189 | if nb.nbformat < 3: | |||
|
190 | return | |||
173 | signature = self.compute_signature(nb) |
|
191 | signature = self.compute_signature(nb) | |
174 | nb['metadata']['signature'] = "%s:%s" % (self.algorithm, signature) |
|
192 | nb['metadata']['signature'] = "%s:%s" % (self.algorithm, signature) | |
175 |
|
193 | |||
@@ -181,11 +199,13 b' class NotebookNotary(LoggingConfigurable):' | |||||
181 |
|
199 | |||
182 | This function is the inverse of check_cells |
|
200 | This function is the inverse of check_cells | |
183 | """ |
|
201 | """ | |
184 | for cell in nb['cells']: |
|
202 | if nb.nbformat < 3: | |
185 | if cell['cell_type'] == 'code': |
|
203 | return | |
186 | cell['metadata']['trusted'] = trusted |
|
204 | ||
|
205 | for cell in yield_code_cells(nb): | |||
|
206 | cell['metadata']['trusted'] = trusted | |||
187 |
|
207 | |||
188 | def _check_cell(self, cell): |
|
208 | def _check_cell(self, cell, nbformat_version): | |
189 | """Do we trust an individual cell? |
|
209 | """Do we trust an individual cell? | |
190 |
|
210 | |||
191 | Return True if: |
|
211 | Return True if: | |
@@ -201,16 +221,21 b' class NotebookNotary(LoggingConfigurable):' | |||||
201 | return True |
|
221 | return True | |
202 |
|
222 | |||
203 | # explicitly safe output |
|
223 | # explicitly safe output | |
204 | safe = { |
|
224 | if nbformat_version >= 4: | |
205 |
'text/plain', 'image/png', 'image/jpeg' |
|
225 | safe = {'text/plain', 'image/png', 'image/jpeg'} | |
206 | } |
|
226 | unsafe_output_types = ['execute_result', 'display_data'] | |
|
227 | safe_keys = {"output_type", "execution_count", "metadata"} | |||
|
228 | else: # v3 | |||
|
229 | safe = {'text', 'png', 'jpeg'} | |||
|
230 | unsafe_output_types = ['pyout', 'display_data'] | |||
|
231 | safe_keys = {"output_type", "prompt_number", "metadata"} | |||
207 |
|
232 | |||
208 | for output in cell['outputs']: |
|
233 | for output in cell['outputs']: | |
209 | output_type = output['output_type'] |
|
234 | output_type = output['output_type'] | |
210 |
if output_type in |
|
235 | if output_type in unsafe_output_types: | |
211 | # if there are any data keys not in the safe whitelist |
|
236 | # if there are any data keys not in the safe whitelist | |
212 | output_keys = set(output).difference({"output_type", "execution_count", "metadata"}) |
|
237 | output_keys = set(output) | |
213 | if output_keys.difference(safe): |
|
238 | if output_keys.difference(safe_keys): | |
214 | return False |
|
239 | return False | |
215 |
|
240 | |||
216 | return True |
|
241 | return True | |
@@ -222,12 +247,12 b' class NotebookNotary(LoggingConfigurable):' | |||||
222 |
|
247 | |||
223 | This function is the inverse of mark_cells. |
|
248 | This function is the inverse of mark_cells. | |
224 | """ |
|
249 | """ | |
|
250 | if nb.nbformat < 3: | |||
|
251 | return False | |||
225 | trusted = True |
|
252 | trusted = True | |
226 |
for cell in nb |
|
253 | for cell in yield_code_cells(nb): | |
227 | if cell['cell_type'] != 'code': |
|
|||
228 | continue |
|
|||
229 | # only distrust a cell if it actually has some output to distrust |
|
254 | # only distrust a cell if it actually has some output to distrust | |
230 | if not self._check_cell(cell): |
|
255 | if not self._check_cell(cell, nb.nbformat): | |
231 | trusted = False |
|
256 | trusted = False | |
232 |
|
257 | |||
233 | return trusted |
|
258 | return trusted |
@@ -18,6 +18,8 b' class TestNotary(TestsBase):' | |||||
18 | ) |
|
18 | ) | |
19 | with self.fopen(u'test3.ipynb', u'r') as f: |
|
19 | with self.fopen(u'test3.ipynb', u'r') as f: | |
20 | self.nb = read(f, as_version=4) |
|
20 | self.nb = read(f, as_version=4) | |
|
21 | with self.fopen(u'test3.ipynb', u'r') as f: | |||
|
22 | self.nb3 = read(f, as_version=3) | |||
21 |
|
23 | |||
22 | def test_algorithms(self): |
|
24 | def test_algorithms(self): | |
23 | last_sig = '' |
|
25 | last_sig = '' | |
@@ -108,4 +110,41 b' class TestNotary(TestsBase):' | |||||
108 | if cell.cell_type == 'code': |
|
110 | if cell.cell_type == 'code': | |
109 | cell.outputs = [] |
|
111 | cell.outputs = [] | |
110 | self.assertTrue(self.notary.check_cells(nb)) |
|
112 | self.assertTrue(self.notary.check_cells(nb)) | |
|
113 | ||||
|
114 | def test_mark_cells_untrusted_v3(self): | |||
|
115 | nb = self.nb3 | |||
|
116 | cells = nb.worksheets[0].cells | |||
|
117 | self.notary.mark_cells(nb, False) | |||
|
118 | for cell in cells: | |||
|
119 | self.assertNotIn('trusted', cell) | |||
|
120 | if cell.cell_type == 'code': | |||
|
121 | self.assertIn('trusted', cell.metadata) | |||
|
122 | self.assertFalse(cell.metadata.trusted) | |||
|
123 | else: | |||
|
124 | self.assertNotIn('trusted', cell.metadata) | |||
|
125 | ||||
|
126 | def test_mark_cells_trusted_v3(self): | |||
|
127 | nb = self.nb3 | |||
|
128 | cells = nb.worksheets[0].cells | |||
|
129 | self.notary.mark_cells(nb, True) | |||
|
130 | for cell in cells: | |||
|
131 | self.assertNotIn('trusted', cell) | |||
|
132 | if cell.cell_type == 'code': | |||
|
133 | self.assertIn('trusted', cell.metadata) | |||
|
134 | self.assertTrue(cell.metadata.trusted) | |||
|
135 | else: | |||
|
136 | self.assertNotIn('trusted', cell.metadata) | |||
|
137 | ||||
|
138 | def test_check_cells_v3(self): | |||
|
139 | nb = self.nb3 | |||
|
140 | cells = nb.worksheets[0].cells | |||
|
141 | self.notary.mark_cells(nb, True) | |||
|
142 | self.assertTrue(self.notary.check_cells(nb)) | |||
|
143 | for cell in cells: | |||
|
144 | self.assertNotIn('trusted', cell) | |||
|
145 | self.notary.mark_cells(nb, False) | |||
|
146 | self.assertFalse(self.notary.check_cells(nb)) | |||
|
147 | for cell in cells: | |||
|
148 | self.assertNotIn('trusted', cell) | |||
|
149 | ||||
111 |
|
150 |
General Comments 0
You need to be logged in to leave comments.
Login now