##// END OF EJS Templates
add nbformat.sign.NotebookNotary
MinRK -
Show More
@@ -1,419 +1,419 b''
1 """A notebook manager that uses the local file system for storage.
1 """A notebook manager that uses the local file system for storage.
2
2
3 Authors:
3 Authors:
4
4
5 * Brian Granger
5 * Brian Granger
6 * Zach Sailer
6 * Zach Sailer
7 """
7 """
8
8
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10 # Copyright (C) 2011 The IPython Development Team
10 # Copyright (C) 2011 The IPython Development Team
11 #
11 #
12 # Distributed under the terms of the BSD License. The full license is in
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
13 # the file COPYING, distributed as part of this software.
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15
15
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 # Imports
17 # Imports
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 import io
20 import io
21 import itertools
21 import itertools
22 import os
22 import os
23 import glob
23 import glob
24 import shutil
24 import shutil
25
25
26 from tornado import web
26 from tornado import web
27
27
28 from .nbmanager import NotebookManager
28 from .nbmanager import NotebookManager
29 from IPython.nbformat import current, sign
29 from IPython.nbformat import current, sign
30 from IPython.utils.traitlets import Unicode, Dict, Bool, TraitError
30 from IPython.utils.traitlets import Unicode, Dict, Bool, TraitError
31 from IPython.utils import tz
31 from IPython.utils import tz
32
32
33 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
34 # Classes
34 # Classes
35 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
36
36
37 class FileNotebookManager(NotebookManager):
37 class FileNotebookManager(NotebookManager):
38
38
39 save_script = Bool(False, config=True,
39 save_script = Bool(False, config=True,
40 help="""Automatically create a Python script when saving the notebook.
40 help="""Automatically create a Python script when saving the notebook.
41
41
42 For easier use of import, %run and %load across notebooks, a
42 For easier use of import, %run and %load across notebooks, a
43 <notebook-name>.py script will be created next to any
43 <notebook-name>.py script will be created next to any
44 <notebook-name>.ipynb on each save. This can also be set with the
44 <notebook-name>.ipynb on each save. This can also be set with the
45 short `--script` flag.
45 short `--script` flag.
46 """
46 """
47 )
47 )
48
48
49 checkpoint_dir = Unicode(config=True,
49 checkpoint_dir = Unicode(config=True,
50 help="""The location in which to keep notebook checkpoints
50 help="""The location in which to keep notebook checkpoints
51
51
52 By default, it is notebook-dir/.ipynb_checkpoints
52 By default, it is notebook-dir/.ipynb_checkpoints
53 """
53 """
54 )
54 )
55 def _checkpoint_dir_default(self):
55 def _checkpoint_dir_default(self):
56 return os.path.join(self.notebook_dir, '.ipynb_checkpoints')
56 return os.path.join(self.notebook_dir, '.ipynb_checkpoints')
57
57
58 def _checkpoint_dir_changed(self, name, old, new):
58 def _checkpoint_dir_changed(self, name, old, new):
59 """do a bit of validation of the checkpoint dir"""
59 """do a bit of validation of the checkpoint dir"""
60 if not os.path.isabs(new):
60 if not os.path.isabs(new):
61 # If we receive a non-absolute path, make it absolute.
61 # If we receive a non-absolute path, make it absolute.
62 abs_new = os.path.abspath(new)
62 abs_new = os.path.abspath(new)
63 self.checkpoint_dir = abs_new
63 self.checkpoint_dir = abs_new
64 return
64 return
65 if os.path.exists(new) and not os.path.isdir(new):
65 if os.path.exists(new) and not os.path.isdir(new):
66 raise TraitError("checkpoint dir %r is not a directory" % new)
66 raise TraitError("checkpoint dir %r is not a directory" % new)
67 if not os.path.exists(new):
67 if not os.path.exists(new):
68 self.log.info("Creating checkpoint dir %s", new)
68 self.log.info("Creating checkpoint dir %s", new)
69 try:
69 try:
70 os.mkdir(new)
70 os.mkdir(new)
71 except:
71 except:
72 raise TraitError("Couldn't create checkpoint dir %r" % new)
72 raise TraitError("Couldn't create checkpoint dir %r" % new)
73
73
74 def get_notebook_names(self, path=''):
74 def get_notebook_names(self, path=''):
75 """List all notebook names in the notebook dir and path."""
75 """List all notebook names in the notebook dir and path."""
76 path = path.strip('/')
76 path = path.strip('/')
77 if not os.path.isdir(self.get_os_path(path=path)):
77 if not os.path.isdir(self.get_os_path(path=path)):
78 raise web.HTTPError(404, 'Directory not found: ' + path)
78 raise web.HTTPError(404, 'Directory not found: ' + path)
79 names = glob.glob(self.get_os_path('*'+self.filename_ext, path))
79 names = glob.glob(self.get_os_path('*'+self.filename_ext, path))
80 names = [os.path.basename(name)
80 names = [os.path.basename(name)
81 for name in names]
81 for name in names]
82 return names
82 return names
83
83
84 def increment_filename(self, basename, path='', ext='.ipynb'):
84 def increment_filename(self, basename, path='', ext='.ipynb'):
85 """Return a non-used filename of the form basename<int>."""
85 """Return a non-used filename of the form basename<int>."""
86 path = path.strip('/')
86 path = path.strip('/')
87 for i in itertools.count():
87 for i in itertools.count():
88 name = u'{basename}{i}{ext}'.format(basename=basename, i=i, ext=ext)
88 name = u'{basename}{i}{ext}'.format(basename=basename, i=i, ext=ext)
89 os_path = self.get_os_path(name, path)
89 os_path = self.get_os_path(name, path)
90 if not os.path.isfile(os_path):
90 if not os.path.isfile(os_path):
91 break
91 break
92 return name
92 return name
93
93
94 def path_exists(self, path):
94 def path_exists(self, path):
95 """Does the API-style path (directory) actually exist?
95 """Does the API-style path (directory) actually exist?
96
96
97 Parameters
97 Parameters
98 ----------
98 ----------
99 path : string
99 path : string
100 The path to check. This is an API path (`/` separated,
100 The path to check. This is an API path (`/` separated,
101 relative to base notebook-dir).
101 relative to base notebook-dir).
102
102
103 Returns
103 Returns
104 -------
104 -------
105 exists : bool
105 exists : bool
106 Whether the path is indeed a directory.
106 Whether the path is indeed a directory.
107 """
107 """
108 path = path.strip('/')
108 path = path.strip('/')
109 os_path = self.get_os_path(path=path)
109 os_path = self.get_os_path(path=path)
110 return os.path.isdir(os_path)
110 return os.path.isdir(os_path)
111
111
112 def get_os_path(self, name=None, path=''):
112 def get_os_path(self, name=None, path=''):
113 """Given a notebook name and a URL path, return its file system
113 """Given a notebook name and a URL path, return its file system
114 path.
114 path.
115
115
116 Parameters
116 Parameters
117 ----------
117 ----------
118 name : string
118 name : string
119 The name of a notebook file with the .ipynb extension
119 The name of a notebook file with the .ipynb extension
120 path : string
120 path : string
121 The relative URL path (with '/' as separator) to the named
121 The relative URL path (with '/' as separator) to the named
122 notebook.
122 notebook.
123
123
124 Returns
124 Returns
125 -------
125 -------
126 path : string
126 path : string
127 A file system path that combines notebook_dir (location where
127 A file system path that combines notebook_dir (location where
128 server started), the relative path, and the filename with the
128 server started), the relative path, and the filename with the
129 current operating system's url.
129 current operating system's url.
130 """
130 """
131 parts = path.strip('/').split('/')
131 parts = path.strip('/').split('/')
132 parts = [p for p in parts if p != ''] # remove duplicate splits
132 parts = [p for p in parts if p != ''] # remove duplicate splits
133 if name is not None:
133 if name is not None:
134 parts.append(name)
134 parts.append(name)
135 path = os.path.join(self.notebook_dir, *parts)
135 path = os.path.join(self.notebook_dir, *parts)
136 return path
136 return path
137
137
138 def notebook_exists(self, name, path=''):
138 def notebook_exists(self, name, path=''):
139 """Returns a True if the notebook exists. Else, returns False.
139 """Returns a True if the notebook exists. Else, returns False.
140
140
141 Parameters
141 Parameters
142 ----------
142 ----------
143 name : string
143 name : string
144 The name of the notebook you are checking.
144 The name of the notebook you are checking.
145 path : string
145 path : string
146 The relative path to the notebook (with '/' as separator)
146 The relative path to the notebook (with '/' as separator)
147
147
148 Returns
148 Returns
149 -------
149 -------
150 bool
150 bool
151 """
151 """
152 path = path.strip('/')
152 path = path.strip('/')
153 nbpath = self.get_os_path(name, path=path)
153 nbpath = self.get_os_path(name, path=path)
154 return os.path.isfile(nbpath)
154 return os.path.isfile(nbpath)
155
155
156 def list_notebooks(self, path):
156 def list_notebooks(self, path):
157 """Returns a list of dictionaries that are the standard model
157 """Returns a list of dictionaries that are the standard model
158 for all notebooks in the relative 'path'.
158 for all notebooks in the relative 'path'.
159
159
160 Parameters
160 Parameters
161 ----------
161 ----------
162 path : str
162 path : str
163 the URL path that describes the relative path for the
163 the URL path that describes the relative path for the
164 listed notebooks
164 listed notebooks
165
165
166 Returns
166 Returns
167 -------
167 -------
168 notebooks : list of dicts
168 notebooks : list of dicts
169 a list of the notebook models without 'content'
169 a list of the notebook models without 'content'
170 """
170 """
171 path = path.strip('/')
171 path = path.strip('/')
172 notebook_names = self.get_notebook_names(path)
172 notebook_names = self.get_notebook_names(path)
173 notebooks = []
173 notebooks = []
174 for name in notebook_names:
174 for name in notebook_names:
175 model = self.get_notebook_model(name, path, content=False)
175 model = self.get_notebook_model(name, path, content=False)
176 notebooks.append(model)
176 notebooks.append(model)
177 notebooks = sorted(notebooks, key=lambda item: item['name'])
177 notebooks = sorted(notebooks, key=lambda item: item['name'])
178 return notebooks
178 return notebooks
179
179
180 def get_notebook_model(self, name, path='', content=True):
180 def get_notebook_model(self, name, path='', content=True):
181 """ Takes a path and name for a notebook and returns its model
181 """ Takes a path and name for a notebook and returns its model
182
182
183 Parameters
183 Parameters
184 ----------
184 ----------
185 name : str
185 name : str
186 the name of the notebook
186 the name of the notebook
187 path : str
187 path : str
188 the URL path that describes the relative path for
188 the URL path that describes the relative path for
189 the notebook
189 the notebook
190
190
191 Returns
191 Returns
192 -------
192 -------
193 model : dict
193 model : dict
194 the notebook model. If contents=True, returns the 'contents'
194 the notebook model. If contents=True, returns the 'contents'
195 dict in the model as well.
195 dict in the model as well.
196 """
196 """
197 path = path.strip('/')
197 path = path.strip('/')
198 if not self.notebook_exists(name=name, path=path):
198 if not self.notebook_exists(name=name, path=path):
199 raise web.HTTPError(404, u'Notebook does not exist: %s' % name)
199 raise web.HTTPError(404, u'Notebook does not exist: %s' % name)
200 os_path = self.get_os_path(name, path)
200 os_path = self.get_os_path(name, path)
201 info = os.stat(os_path)
201 info = os.stat(os_path)
202 last_modified = tz.utcfromtimestamp(info.st_mtime)
202 last_modified = tz.utcfromtimestamp(info.st_mtime)
203 created = tz.utcfromtimestamp(info.st_ctime)
203 created = tz.utcfromtimestamp(info.st_ctime)
204 # Create the notebook model.
204 # Create the notebook model.
205 model ={}
205 model ={}
206 model['name'] = name
206 model['name'] = name
207 model['path'] = path
207 model['path'] = path
208 model['last_modified'] = last_modified
208 model['last_modified'] = last_modified
209 model['created'] = created
209 model['created'] = created
210 if content:
210 if content:
211 with io.open(os_path, 'r', encoding='utf-8') as f:
211 with io.open(os_path, 'r', encoding='utf-8') as f:
212 try:
212 try:
213 nb = current.read(f, u'json')
213 nb = current.read(f, u'json')
214 except Exception as e:
214 except Exception as e:
215 raise web.HTTPError(400, u"Unreadable Notebook: %s %s" % (os_path, e))
215 raise web.HTTPError(400, u"Unreadable Notebook: %s %s" % (os_path, e))
216 model['content'] = nb
216 model['content'] = nb
217 sign.mark_trusted_cells(nb, self.secret)
217 sign.mark_trusted_cells(nb, self.notary.secret)
218 return model
218 return model
219
219
220 def save_notebook_model(self, model, name='', path=''):
220 def save_notebook_model(self, model, name='', path=''):
221 """Save the notebook model and return the model with no content."""
221 """Save the notebook model and return the model with no content."""
222 path = path.strip('/')
222 path = path.strip('/')
223
223
224 if 'content' not in model:
224 if 'content' not in model:
225 raise web.HTTPError(400, u'No notebook JSON data provided')
225 raise web.HTTPError(400, u'No notebook JSON data provided')
226
226
227 # One checkpoint should always exist
227 # One checkpoint should always exist
228 if self.notebook_exists(name, path) and not self.list_checkpoints(name, path):
228 if self.notebook_exists(name, path) and not self.list_checkpoints(name, path):
229 self.create_checkpoint(name, path)
229 self.create_checkpoint(name, path)
230
230
231 new_path = model.get('path', path).strip('/')
231 new_path = model.get('path', path).strip('/')
232 new_name = model.get('name', name)
232 new_name = model.get('name', name)
233
233
234 if path != new_path or name != new_name:
234 if path != new_path or name != new_name:
235 self.rename_notebook(name, path, new_name, new_path)
235 self.rename_notebook(name, path, new_name, new_path)
236
236
237 # Save the notebook file
237 # Save the notebook file
238 os_path = self.get_os_path(new_name, new_path)
238 os_path = self.get_os_path(new_name, new_path)
239 nb = current.to_notebook_json(model['content'])
239 nb = current.to_notebook_json(model['content'])
240
240
241 if sign.check_trusted_cells(nb):
241 if sign.check_trusted_cells(nb):
242 sign.trust_notebook(nb, self.secret, self.signature_scheme)
242 sign.trust_notebook(nb, self.notary.secret, self.notary.signature_scheme)
243
243
244 if 'name' in nb['metadata']:
244 if 'name' in nb['metadata']:
245 nb['metadata']['name'] = u''
245 nb['metadata']['name'] = u''
246 try:
246 try:
247 self.log.debug("Autosaving notebook %s", os_path)
247 self.log.debug("Autosaving notebook %s", os_path)
248 with io.open(os_path, 'w', encoding='utf-8') as f:
248 with io.open(os_path, 'w', encoding='utf-8') as f:
249 current.write(nb, f, u'json')
249 current.write(nb, f, u'json')
250 except Exception as e:
250 except Exception as e:
251 raise web.HTTPError(400, u'Unexpected error while autosaving notebook: %s %s' % (os_path, e))
251 raise web.HTTPError(400, u'Unexpected error while autosaving notebook: %s %s' % (os_path, e))
252
252
253 # Save .py script as well
253 # Save .py script as well
254 if self.save_script:
254 if self.save_script:
255 py_path = os.path.splitext(os_path)[0] + '.py'
255 py_path = os.path.splitext(os_path)[0] + '.py'
256 self.log.debug("Writing script %s", py_path)
256 self.log.debug("Writing script %s", py_path)
257 try:
257 try:
258 with io.open(py_path, 'w', encoding='utf-8') as f:
258 with io.open(py_path, 'w', encoding='utf-8') as f:
259 current.write(nb, f, u'py')
259 current.write(nb, f, u'py')
260 except Exception as e:
260 except Exception as e:
261 raise web.HTTPError(400, u'Unexpected error while saving notebook as script: %s %s' % (py_path, e))
261 raise web.HTTPError(400, u'Unexpected error while saving notebook as script: %s %s' % (py_path, e))
262
262
263 model = self.get_notebook_model(new_name, new_path, content=False)
263 model = self.get_notebook_model(new_name, new_path, content=False)
264 return model
264 return model
265
265
266 def update_notebook_model(self, model, name, path=''):
266 def update_notebook_model(self, model, name, path=''):
267 """Update the notebook's path and/or name"""
267 """Update the notebook's path and/or name"""
268 path = path.strip('/')
268 path = path.strip('/')
269 new_name = model.get('name', name)
269 new_name = model.get('name', name)
270 new_path = model.get('path', path).strip('/')
270 new_path = model.get('path', path).strip('/')
271 if path != new_path or name != new_name:
271 if path != new_path or name != new_name:
272 self.rename_notebook(name, path, new_name, new_path)
272 self.rename_notebook(name, path, new_name, new_path)
273 model = self.get_notebook_model(new_name, new_path, content=False)
273 model = self.get_notebook_model(new_name, new_path, content=False)
274 return model
274 return model
275
275
276 def delete_notebook_model(self, name, path=''):
276 def delete_notebook_model(self, name, path=''):
277 """Delete notebook by name and path."""
277 """Delete notebook by name and path."""
278 path = path.strip('/')
278 path = path.strip('/')
279 os_path = self.get_os_path(name, path)
279 os_path = self.get_os_path(name, path)
280 if not os.path.isfile(os_path):
280 if not os.path.isfile(os_path):
281 raise web.HTTPError(404, u'Notebook does not exist: %s' % os_path)
281 raise web.HTTPError(404, u'Notebook does not exist: %s' % os_path)
282
282
283 # clear checkpoints
283 # clear checkpoints
284 for checkpoint in self.list_checkpoints(name, path):
284 for checkpoint in self.list_checkpoints(name, path):
285 checkpoint_id = checkpoint['id']
285 checkpoint_id = checkpoint['id']
286 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
286 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
287 if os.path.isfile(cp_path):
287 if os.path.isfile(cp_path):
288 self.log.debug("Unlinking checkpoint %s", cp_path)
288 self.log.debug("Unlinking checkpoint %s", cp_path)
289 os.unlink(cp_path)
289 os.unlink(cp_path)
290
290
291 self.log.debug("Unlinking notebook %s", os_path)
291 self.log.debug("Unlinking notebook %s", os_path)
292 os.unlink(os_path)
292 os.unlink(os_path)
293
293
294 def rename_notebook(self, old_name, old_path, new_name, new_path):
294 def rename_notebook(self, old_name, old_path, new_name, new_path):
295 """Rename a notebook."""
295 """Rename a notebook."""
296 old_path = old_path.strip('/')
296 old_path = old_path.strip('/')
297 new_path = new_path.strip('/')
297 new_path = new_path.strip('/')
298 if new_name == old_name and new_path == old_path:
298 if new_name == old_name and new_path == old_path:
299 return
299 return
300
300
301 new_os_path = self.get_os_path(new_name, new_path)
301 new_os_path = self.get_os_path(new_name, new_path)
302 old_os_path = self.get_os_path(old_name, old_path)
302 old_os_path = self.get_os_path(old_name, old_path)
303
303
304 # Should we proceed with the move?
304 # Should we proceed with the move?
305 if os.path.isfile(new_os_path):
305 if os.path.isfile(new_os_path):
306 raise web.HTTPError(409, u'Notebook with name already exists: %s' % new_os_path)
306 raise web.HTTPError(409, u'Notebook with name already exists: %s' % new_os_path)
307 if self.save_script:
307 if self.save_script:
308 old_py_path = os.path.splitext(old_os_path)[0] + '.py'
308 old_py_path = os.path.splitext(old_os_path)[0] + '.py'
309 new_py_path = os.path.splitext(new_os_path)[0] + '.py'
309 new_py_path = os.path.splitext(new_os_path)[0] + '.py'
310 if os.path.isfile(new_py_path):
310 if os.path.isfile(new_py_path):
311 raise web.HTTPError(409, u'Python script with name already exists: %s' % new_py_path)
311 raise web.HTTPError(409, u'Python script with name already exists: %s' % new_py_path)
312
312
313 # Move the notebook file
313 # Move the notebook file
314 try:
314 try:
315 os.rename(old_os_path, new_os_path)
315 os.rename(old_os_path, new_os_path)
316 except Exception as e:
316 except Exception as e:
317 raise web.HTTPError(500, u'Unknown error renaming notebook: %s %s' % (old_os_path, e))
317 raise web.HTTPError(500, u'Unknown error renaming notebook: %s %s' % (old_os_path, e))
318
318
319 # Move the checkpoints
319 # Move the checkpoints
320 old_checkpoints = self.list_checkpoints(old_name, old_path)
320 old_checkpoints = self.list_checkpoints(old_name, old_path)
321 for cp in old_checkpoints:
321 for cp in old_checkpoints:
322 checkpoint_id = cp['id']
322 checkpoint_id = cp['id']
323 old_cp_path = self.get_checkpoint_path(checkpoint_id, old_name, old_path)
323 old_cp_path = self.get_checkpoint_path(checkpoint_id, old_name, old_path)
324 new_cp_path = self.get_checkpoint_path(checkpoint_id, new_name, new_path)
324 new_cp_path = self.get_checkpoint_path(checkpoint_id, new_name, new_path)
325 if os.path.isfile(old_cp_path):
325 if os.path.isfile(old_cp_path):
326 self.log.debug("Renaming checkpoint %s -> %s", old_cp_path, new_cp_path)
326 self.log.debug("Renaming checkpoint %s -> %s", old_cp_path, new_cp_path)
327 os.rename(old_cp_path, new_cp_path)
327 os.rename(old_cp_path, new_cp_path)
328
328
329 # Move the .py script
329 # Move the .py script
330 if self.save_script:
330 if self.save_script:
331 os.rename(old_py_path, new_py_path)
331 os.rename(old_py_path, new_py_path)
332
332
333 # Checkpoint-related utilities
333 # Checkpoint-related utilities
334
334
335 def get_checkpoint_path(self, checkpoint_id, name, path=''):
335 def get_checkpoint_path(self, checkpoint_id, name, path=''):
336 """find the path to a checkpoint"""
336 """find the path to a checkpoint"""
337 path = path.strip('/')
337 path = path.strip('/')
338 basename, _ = os.path.splitext(name)
338 basename, _ = os.path.splitext(name)
339 filename = u"{name}-{checkpoint_id}{ext}".format(
339 filename = u"{name}-{checkpoint_id}{ext}".format(
340 name=basename,
340 name=basename,
341 checkpoint_id=checkpoint_id,
341 checkpoint_id=checkpoint_id,
342 ext=self.filename_ext,
342 ext=self.filename_ext,
343 )
343 )
344 cp_path = os.path.join(path, self.checkpoint_dir, filename)
344 cp_path = os.path.join(path, self.checkpoint_dir, filename)
345 return cp_path
345 return cp_path
346
346
347 def get_checkpoint_model(self, checkpoint_id, name, path=''):
347 def get_checkpoint_model(self, checkpoint_id, name, path=''):
348 """construct the info dict for a given checkpoint"""
348 """construct the info dict for a given checkpoint"""
349 path = path.strip('/')
349 path = path.strip('/')
350 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
350 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
351 stats = os.stat(cp_path)
351 stats = os.stat(cp_path)
352 last_modified = tz.utcfromtimestamp(stats.st_mtime)
352 last_modified = tz.utcfromtimestamp(stats.st_mtime)
353 info = dict(
353 info = dict(
354 id = checkpoint_id,
354 id = checkpoint_id,
355 last_modified = last_modified,
355 last_modified = last_modified,
356 )
356 )
357 return info
357 return info
358
358
359 # public checkpoint API
359 # public checkpoint API
360
360
361 def create_checkpoint(self, name, path=''):
361 def create_checkpoint(self, name, path=''):
362 """Create a checkpoint from the current state of a notebook"""
362 """Create a checkpoint from the current state of a notebook"""
363 path = path.strip('/')
363 path = path.strip('/')
364 nb_path = self.get_os_path(name, path)
364 nb_path = self.get_os_path(name, path)
365 # only the one checkpoint ID:
365 # only the one checkpoint ID:
366 checkpoint_id = u"checkpoint"
366 checkpoint_id = u"checkpoint"
367 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
367 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
368 self.log.debug("creating checkpoint for notebook %s", name)
368 self.log.debug("creating checkpoint for notebook %s", name)
369 if not os.path.exists(self.checkpoint_dir):
369 if not os.path.exists(self.checkpoint_dir):
370 os.mkdir(self.checkpoint_dir)
370 os.mkdir(self.checkpoint_dir)
371 shutil.copy2(nb_path, cp_path)
371 shutil.copy2(nb_path, cp_path)
372
372
373 # return the checkpoint info
373 # return the checkpoint info
374 return self.get_checkpoint_model(checkpoint_id, name, path)
374 return self.get_checkpoint_model(checkpoint_id, name, path)
375
375
376 def list_checkpoints(self, name, path=''):
376 def list_checkpoints(self, name, path=''):
377 """list the checkpoints for a given notebook
377 """list the checkpoints for a given notebook
378
378
379 This notebook manager currently only supports one checkpoint per notebook.
379 This notebook manager currently only supports one checkpoint per notebook.
380 """
380 """
381 path = path.strip('/')
381 path = path.strip('/')
382 checkpoint_id = "checkpoint"
382 checkpoint_id = "checkpoint"
383 path = self.get_checkpoint_path(checkpoint_id, name, path)
383 path = self.get_checkpoint_path(checkpoint_id, name, path)
384 if not os.path.exists(path):
384 if not os.path.exists(path):
385 return []
385 return []
386 else:
386 else:
387 return [self.get_checkpoint_model(checkpoint_id, name, path)]
387 return [self.get_checkpoint_model(checkpoint_id, name, path)]
388
388
389
389
390 def restore_checkpoint(self, checkpoint_id, name, path=''):
390 def restore_checkpoint(self, checkpoint_id, name, path=''):
391 """restore a notebook to a checkpointed state"""
391 """restore a notebook to a checkpointed state"""
392 path = path.strip('/')
392 path = path.strip('/')
393 self.log.info("restoring Notebook %s from checkpoint %s", name, checkpoint_id)
393 self.log.info("restoring Notebook %s from checkpoint %s", name, checkpoint_id)
394 nb_path = self.get_os_path(name, path)
394 nb_path = self.get_os_path(name, path)
395 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
395 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
396 if not os.path.isfile(cp_path):
396 if not os.path.isfile(cp_path):
397 self.log.debug("checkpoint file does not exist: %s", cp_path)
397 self.log.debug("checkpoint file does not exist: %s", cp_path)
398 raise web.HTTPError(404,
398 raise web.HTTPError(404,
399 u'Notebook checkpoint does not exist: %s-%s' % (name, checkpoint_id)
399 u'Notebook checkpoint does not exist: %s-%s' % (name, checkpoint_id)
400 )
400 )
401 # ensure notebook is readable (never restore from an unreadable notebook)
401 # ensure notebook is readable (never restore from an unreadable notebook)
402 with io.open(cp_path, 'r', encoding='utf-8') as f:
402 with io.open(cp_path, 'r', encoding='utf-8') as f:
403 nb = current.read(f, u'json')
403 nb = current.read(f, u'json')
404 shutil.copy2(cp_path, nb_path)
404 shutil.copy2(cp_path, nb_path)
405 self.log.debug("copying %s -> %s", cp_path, nb_path)
405 self.log.debug("copying %s -> %s", cp_path, nb_path)
406
406
407 def delete_checkpoint(self, checkpoint_id, name, path=''):
407 def delete_checkpoint(self, checkpoint_id, name, path=''):
408 """delete a notebook's checkpoint"""
408 """delete a notebook's checkpoint"""
409 path = path.strip('/')
409 path = path.strip('/')
410 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
410 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
411 if not os.path.isfile(cp_path):
411 if not os.path.isfile(cp_path):
412 raise web.HTTPError(404,
412 raise web.HTTPError(404,
413 u'Notebook checkpoint does not exist: %s%s-%s' % (path, name, checkpoint_id)
413 u'Notebook checkpoint does not exist: %s%s-%s' % (path, name, checkpoint_id)
414 )
414 )
415 self.log.debug("unlinking %s", cp_path)
415 self.log.debug("unlinking %s", cp_path)
416 os.unlink(cp_path)
416 os.unlink(cp_path)
417
417
418 def info_string(self):
418 def info_string(self):
419 return "Serving notebooks from local directory: %s" % self.notebook_dir
419 return "Serving notebooks from local directory: %s" % self.notebook_dir
@@ -1,207 +1,178 b''
1 """A base class notebook manager.
1 """A base class notebook manager.
2
2
3 Authors:
3 Authors:
4
4
5 * Brian Granger
5 * Brian Granger
6 * Zach Sailer
6 * Zach Sailer
7 """
7 """
8
8
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10 # Copyright (C) 2011 The IPython Development Team
10 # Copyright (C) 2011 The IPython Development Team
11 #
11 #
12 # Distributed under the terms of the BSD License. The full license is in
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
13 # the file COPYING, distributed as part of this software.
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15
15
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 # Imports
17 # Imports
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 import base64
21 import hashlib
22 import io
23 import os
20 import os
24
21
25 from IPython.config.configurable import LoggingConfigurable
22 from IPython.config.configurable import LoggingConfigurable
26 from IPython.core.application import BaseIPythonApplication
23 from IPython.nbformat import current, sign
27 from IPython.nbformat import current
28 from IPython.utils import py3compat
24 from IPython.utils import py3compat
29 from IPython.utils.traitlets import Unicode, TraitError, Enum, Bytes
25 from IPython.utils.traitlets import Instance, Unicode, TraitError
30
26
31 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
32 # Classes
28 # Classes
33 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
34
30
35 class NotebookManager(LoggingConfigurable):
31 class NotebookManager(LoggingConfigurable):
36
32
37 # Todo:
33 # Todo:
38 # The notebook_dir attribute is used to mean a couple of different things:
34 # The notebook_dir attribute is used to mean a couple of different things:
39 # 1. Where the notebooks are stored if FileNotebookManager is used.
35 # 1. Where the notebooks are stored if FileNotebookManager is used.
40 # 2. The cwd of the kernel for a project.
36 # 2. The cwd of the kernel for a project.
41 # Right now we use this attribute in a number of different places and
37 # Right now we use this attribute in a number of different places and
42 # we are going to have to disentangle all of this.
38 # we are going to have to disentangle all of this.
43 notebook_dir = Unicode(py3compat.getcwd(), config=True, help="""
39 notebook_dir = Unicode(py3compat.getcwd(), config=True, help="""
44 The directory to use for notebooks.
40 The directory to use for notebooks.
45 """)
41 """)
46
42
47 filename_ext = Unicode(u'.ipynb')
43 filename_ext = Unicode(u'.ipynb')
48
44
49 signature_scheme = Enum(hashlib.algorithms, default_value='sha256', config=True,
45 notary = Instance(sign.NotebookNotary)
50 help="""The signature scheme used to sign notebooks."""
46 def _notary_default(self):
51 )
47 return sign.NotebookNotary(parent=self)
52
53 secret = Bytes(config=True,
54 help="""The secret key with which notebooks are signed."""
55 )
56 def _secret_default(self):
57 # note : this assumes an Application is running
58 profile_dir = BaseIPythonApplication.instance().profile_dir
59 secret_file = os.path.join(profile_dir.security_dir, 'notebook_secret')
60 if os.path.exists(secret_file):
61 with io.open(secret_file, 'rb') as f:
62 return f.read()
63 else:
64 secret = base64.encodestring(os.urandom(1024))
65 self.log.info("Writing output secret to %s", secret_file)
66 with io.open(secret_file, 'wb') as f:
67 f.write(secret)
68 try:
69 os.chmod(secret_file, 0o600)
70 except OSError:
71 self.log.warn(
72 "Could not set permissions on %s",
73 secret_file
74 )
75 return secret
76
77
48
78 def path_exists(self, path):
49 def path_exists(self, path):
79 """Does the API-style path (directory) actually exist?
50 """Does the API-style path (directory) actually exist?
80
51
81 Override this method in subclasses.
52 Override this method in subclasses.
82
53
83 Parameters
54 Parameters
84 ----------
55 ----------
85 path : string
56 path : string
86 The
57 The
87
58
88 Returns
59 Returns
89 -------
60 -------
90 exists : bool
61 exists : bool
91 Whether the path does indeed exist.
62 Whether the path does indeed exist.
92 """
63 """
93 raise NotImplementedError
64 raise NotImplementedError
94
65
95 def _notebook_dir_changed(self, name, old, new):
66 def _notebook_dir_changed(self, name, old, new):
96 """Do a bit of validation of the notebook dir."""
67 """Do a bit of validation of the notebook dir."""
97 if not os.path.isabs(new):
68 if not os.path.isabs(new):
98 # If we receive a non-absolute path, make it absolute.
69 # If we receive a non-absolute path, make it absolute.
99 self.notebook_dir = os.path.abspath(new)
70 self.notebook_dir = os.path.abspath(new)
100 return
71 return
101 if os.path.exists(new) and not os.path.isdir(new):
72 if os.path.exists(new) and not os.path.isdir(new):
102 raise TraitError("notebook dir %r is not a directory" % new)
73 raise TraitError("notebook dir %r is not a directory" % new)
103 if not os.path.exists(new):
74 if not os.path.exists(new):
104 self.log.info("Creating notebook dir %s", new)
75 self.log.info("Creating notebook dir %s", new)
105 try:
76 try:
106 os.mkdir(new)
77 os.mkdir(new)
107 except:
78 except:
108 raise TraitError("Couldn't create notebook dir %r" % new)
79 raise TraitError("Couldn't create notebook dir %r" % new)
109
80
110 # Main notebook API
81 # Main notebook API
111
82
112 def increment_filename(self, basename, path=''):
83 def increment_filename(self, basename, path=''):
113 """Increment a notebook filename without the .ipynb to make it unique.
84 """Increment a notebook filename without the .ipynb to make it unique.
114
85
115 Parameters
86 Parameters
116 ----------
87 ----------
117 basename : unicode
88 basename : unicode
118 The name of a notebook without the ``.ipynb`` file extension.
89 The name of a notebook without the ``.ipynb`` file extension.
119 path : unicode
90 path : unicode
120 The URL path of the notebooks directory
91 The URL path of the notebooks directory
121 """
92 """
122 return basename
93 return basename
123
94
124 def list_notebooks(self, path=''):
95 def list_notebooks(self, path=''):
125 """Return a list of notebook dicts without content.
96 """Return a list of notebook dicts without content.
126
97
127 This returns a list of dicts, each of the form::
98 This returns a list of dicts, each of the form::
128
99
129 dict(notebook_id=notebook,name=name)
100 dict(notebook_id=notebook,name=name)
130
101
131 This list of dicts should be sorted by name::
102 This list of dicts should be sorted by name::
132
103
133 data = sorted(data, key=lambda item: item['name'])
104 data = sorted(data, key=lambda item: item['name'])
134 """
105 """
135 raise NotImplementedError('must be implemented in a subclass')
106 raise NotImplementedError('must be implemented in a subclass')
136
107
137 def get_notebook_model(self, name, path='', content=True):
108 def get_notebook_model(self, name, path='', content=True):
138 """Get the notebook model with or without content."""
109 """Get the notebook model with or without content."""
139 raise NotImplementedError('must be implemented in a subclass')
110 raise NotImplementedError('must be implemented in a subclass')
140
111
141 def save_notebook_model(self, model, name, path=''):
112 def save_notebook_model(self, model, name, path=''):
142 """Save the notebook model and return the model with no content."""
113 """Save the notebook model and return the model with no content."""
143 raise NotImplementedError('must be implemented in a subclass')
114 raise NotImplementedError('must be implemented in a subclass')
144
115
145 def update_notebook_model(self, model, name, path=''):
116 def update_notebook_model(self, model, name, path=''):
146 """Update the notebook model and return the model with no content."""
117 """Update the notebook model and return the model with no content."""
147 raise NotImplementedError('must be implemented in a subclass')
118 raise NotImplementedError('must be implemented in a subclass')
148
119
149 def delete_notebook_model(self, name, path=''):
120 def delete_notebook_model(self, name, path=''):
150 """Delete notebook by name and path."""
121 """Delete notebook by name and path."""
151 raise NotImplementedError('must be implemented in a subclass')
122 raise NotImplementedError('must be implemented in a subclass')
152
123
153 def create_notebook_model(self, model=None, path=''):
124 def create_notebook_model(self, model=None, path=''):
154 """Create a new notebook and return its model with no content."""
125 """Create a new notebook and return its model with no content."""
155 path = path.strip('/')
126 path = path.strip('/')
156 if model is None:
127 if model is None:
157 model = {}
128 model = {}
158 if 'content' not in model:
129 if 'content' not in model:
159 metadata = current.new_metadata(name=u'')
130 metadata = current.new_metadata(name=u'')
160 model['content'] = current.new_notebook(metadata=metadata)
131 model['content'] = current.new_notebook(metadata=metadata)
161 if 'name' not in model:
132 if 'name' not in model:
162 model['name'] = self.increment_filename('Untitled', path)
133 model['name'] = self.increment_filename('Untitled', path)
163
134
164 model['path'] = path
135 model['path'] = path
165 model = self.save_notebook_model(model, model['name'], model['path'])
136 model = self.save_notebook_model(model, model['name'], model['path'])
166 return model
137 return model
167
138
168 def copy_notebook(self, from_name, to_name=None, path=''):
139 def copy_notebook(self, from_name, to_name=None, path=''):
169 """Copy an existing notebook and return its new model.
140 """Copy an existing notebook and return its new model.
170
141
171 If to_name not specified, increment `from_name-Copy#.ipynb`.
142 If to_name not specified, increment `from_name-Copy#.ipynb`.
172 """
143 """
173 path = path.strip('/')
144 path = path.strip('/')
174 model = self.get_notebook_model(from_name, path)
145 model = self.get_notebook_model(from_name, path)
175 if not to_name:
146 if not to_name:
176 base = os.path.splitext(from_name)[0] + '-Copy'
147 base = os.path.splitext(from_name)[0] + '-Copy'
177 to_name = self.increment_filename(base, path)
148 to_name = self.increment_filename(base, path)
178 model['name'] = to_name
149 model['name'] = to_name
179 model = self.save_notebook_model(model, to_name, path)
150 model = self.save_notebook_model(model, to_name, path)
180 return model
151 return model
181
152
182 # Checkpoint-related
153 # Checkpoint-related
183
154
184 def create_checkpoint(self, name, path=''):
155 def create_checkpoint(self, name, path=''):
185 """Create a checkpoint of the current state of a notebook
156 """Create a checkpoint of the current state of a notebook
186
157
187 Returns a checkpoint_id for the new checkpoint.
158 Returns a checkpoint_id for the new checkpoint.
188 """
159 """
189 raise NotImplementedError("must be implemented in a subclass")
160 raise NotImplementedError("must be implemented in a subclass")
190
161
191 def list_checkpoints(self, name, path=''):
162 def list_checkpoints(self, name, path=''):
192 """Return a list of checkpoints for a given notebook"""
163 """Return a list of checkpoints for a given notebook"""
193 return []
164 return []
194
165
195 def restore_checkpoint(self, checkpoint_id, name, path=''):
166 def restore_checkpoint(self, checkpoint_id, name, path=''):
196 """Restore a notebook from one of its checkpoints"""
167 """Restore a notebook from one of its checkpoints"""
197 raise NotImplementedError("must be implemented in a subclass")
168 raise NotImplementedError("must be implemented in a subclass")
198
169
199 def delete_checkpoint(self, checkpoint_id, name, path=''):
170 def delete_checkpoint(self, checkpoint_id, name, path=''):
200 """delete a checkpoint for a notebook"""
171 """delete a checkpoint for a notebook"""
201 raise NotImplementedError("must be implemented in a subclass")
172 raise NotImplementedError("must be implemented in a subclass")
202
173
203 def log_info(self):
174 def log_info(self):
204 self.log.info(self.info_string())
175 self.log.info(self.info_string())
205
176
206 def info_string(self):
177 def info_string(self):
207 return "Serving notebooks"
178 return "Serving notebooks"
@@ -1,141 +1,193 b''
1 """Functions for signing notebooks"""
1 """Functions for signing notebooks"""
2 #-----------------------------------------------------------------------------
2 #-----------------------------------------------------------------------------
3 # Copyright (C) 2014, The IPython Development Team
3 # Copyright (C) 2014, The IPython Development Team
4 #
4 #
5 # Distributed under the terms of the BSD License. The full license is in
5 # Distributed under the terms of the BSD License. The full license is in
6 # the file COPYING, distributed as part of this software.
6 # the file COPYING, distributed as part of this software.
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8
8
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10 # Imports
10 # Imports
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 import base64
13 from contextlib import contextmanager
14 from contextlib import contextmanager
14 import hashlib
15 import hashlib
15 from hmac import HMAC
16 from hmac import HMAC
17 import io
18 import os
16
19
17 from IPython.utils.py3compat import string_types, unicode_type, cast_bytes
20 from IPython.utils.py3compat import string_types, unicode_type, cast_bytes
21 from IPython.config import LoggingConfigurable
22 from IPython.utils.traitlets import Instance, Bytes, Enum
18
23
19 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
20 # Code
25 # Code
21 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
22
27
23
28
24 def yield_everything(obj):
29 def yield_everything(obj):
25 """Yield every item in a container as bytes
30 """Yield every item in a container as bytes
26
31
27 Allows any JSONable object to be passed to an HMAC digester
32 Allows any JSONable object to be passed to an HMAC digester
28 without having to serialize the whole thing.
33 without having to serialize the whole thing.
29 """
34 """
30 if isinstance(obj, dict):
35 if isinstance(obj, dict):
31 for key in sorted(obj):
36 for key in sorted(obj):
32 value = obj[key]
37 value = obj[key]
33 yield cast_bytes(key)
38 yield cast_bytes(key)
34 for b in yield_everything(value):
39 for b in yield_everything(value):
35 yield b
40 yield b
36 elif isinstance(obj, (list, tuple)):
41 elif isinstance(obj, (list, tuple)):
37 for element in obj:
42 for element in obj:
38 for b in yield_everything(element):
43 for b in yield_everything(element):
39 yield b
44 yield b
40 elif isinstance(obj, unicode_type):
45 elif isinstance(obj, unicode_type):
41 yield obj.encode('utf8')
46 yield obj.encode('utf8')
42 else:
47 else:
43 yield unicode_type(obj).encode('utf8')
48 yield unicode_type(obj).encode('utf8')
44
49
45
50
46 @contextmanager
51 @contextmanager
47 def signature_removed(nb):
52 def signature_removed(nb):
48 """Context manager for operating on a notebook with its signature removed
53 """Context manager for operating on a notebook with its signature removed
49
54
50 Used for excluding the previous signature when computing a notebook's signature.
55 Used for excluding the previous signature when computing a notebook's signature.
51 """
56 """
52 save_signature = nb['metadata'].pop('signature', None)
57 save_signature = nb['metadata'].pop('signature', None)
53 try:
58 try:
54 yield
59 yield
55 finally:
60 finally:
56 if save_signature is not None:
61 if save_signature is not None:
57 nb['metadata']['signature'] = save_signature
62 nb['metadata']['signature'] = save_signature
58
63
59
64
60 def notebook_signature(nb, secret, scheme):
65 def notebook_signature(nb, secret, scheme):
61 """Compute a notebook's signature
66 """Compute a notebook's signature
62
67
63 by hashing the entire contents of the notebook via HMAC digest.
68 by hashing the entire contents of the notebook via HMAC digest.
64 scheme is the hashing scheme, which must be an attribute of the hashlib module,
69 scheme is the hashing scheme, which must be an attribute of the hashlib module,
65 as listed in hashlib.algorithms.
70 as listed in hashlib.algorithms.
66 """
71 """
67 hmac = HMAC(secret, digestmod=getattr(hashlib, scheme))
72 hmac = HMAC(secret, digestmod=getattr(hashlib, scheme))
68 # don't include the previous hash in the content to hash
73 # don't include the previous hash in the content to hash
69 with signature_removed(nb):
74 with signature_removed(nb):
70 # sign the whole thing
75 # sign the whole thing
71 for b in yield_everything(nb):
76 for b in yield_everything(nb):
72 hmac.update(b)
77 hmac.update(b)
73
78
74 return hmac.hexdigest()
79 return hmac.hexdigest()
75
80
76
81
77 def check_notebook_signature(nb, secret):
82 def check_notebook_signature(nb, secret):
78 """Check a notebook's stored signature
83 """Check a notebook's stored signature
79
84
80 If a signature is stored in the notebook's metadata,
85 If a signature is stored in the notebook's metadata,
81 a new signature is computed using the same hashing scheme,
86 a new signature is computed using the same hashing scheme,
82 and compared.
87 and compared.
83
88
84 If no signature can be found, or the scheme of the existing signature is unavailable,
89 If no signature can be found, or the scheme of the existing signature is unavailable,
85 it will return False.
90 it will return False.
86 """
91 """
87 stored_signature = nb['metadata'].get('signature', None)
92 stored_signature = nb['metadata'].get('signature', None)
88 if not stored_signature \
93 if not stored_signature \
89 or not isinstance(stored_signature, string_types) \
94 or not isinstance(stored_signature, string_types) \
90 or ':' not in stored_signature:
95 or ':' not in stored_signature:
91 return False
96 return False
92 scheme, sig = stored_signature.split(':', 1)
97 scheme, sig = stored_signature.split(':', 1)
93 try:
98 try:
94 my_signature = notebook_signature(nb, secret, scheme)
99 my_signature = notebook_signature(nb, secret, scheme)
95 except AttributeError:
100 except AttributeError:
96 return False
101 return False
97 return my_signature == sig
102 return my_signature == sig
98
103
99
104
100 def trust_notebook(nb, secret, scheme):
105 def trust_notebook(nb, secret, scheme):
101 """Re-sign a notebook, indicating that its output is trusted
106 """Re-sign a notebook, indicating that its output is trusted
102
107
103 stores 'scheme:hmac-hexdigest' in notebook.metadata.signature
108 stores 'scheme:hmac-hexdigest' in notebook.metadata.signature
104
109
105 e.g. 'sha256:deadbeef123...'
110 e.g. 'sha256:deadbeef123...'
106 """
111 """
107 signature = notebook_signature(nb, secret, scheme)
112 signature = notebook_signature(nb, secret, scheme)
108 nb['metadata']['signature'] = "%s:%s" % (scheme, signature)
113 nb['metadata']['signature'] = "%s:%s" % (scheme, signature)
109
114
110
115
111 def mark_trusted_cells(nb, secret):
116 def mark_trusted_cells(nb, secret):
112 """Mark cells as trusted if the notebook's signature can be verified
117 """Mark cells as trusted if the notebook's signature can be verified
113
118
114 Sets ``cell.trusted = True | False`` on all code cells,
119 Sets ``cell.trusted = True | False`` on all code cells,
115 depending on whether the stored signature can be verified.
120 depending on whether the stored signature can be verified.
116 """
121 """
117 if not nb['worksheets']:
122 if not nb['worksheets']:
118 # nothing to mark if there are no cells
123 # nothing to mark if there are no cells
119 return True
124 return True
120 trusted = check_notebook_signature(nb, secret)
125 trusted = check_notebook_signature(nb, secret)
121 for cell in nb['worksheets'][0]['cells']:
126 for cell in nb['worksheets'][0]['cells']:
122 if cell['cell_type'] == 'code':
127 if cell['cell_type'] == 'code':
123 cell['trusted'] = trusted
128 cell['trusted'] = trusted
124 return trusted
129 return trusted
125
130
126
131
127 def check_trusted_cells(nb):
132 def check_trusted_cells(nb):
128 """Return whether all code cells are trusted
133 """Return whether all code cells are trusted
129
134
130 If there are no code cells, return True.
135 If there are no code cells, return True.
131 """
136 """
132 if not nb['worksheets']:
137 if not nb['worksheets']:
133 return True
138 return True
134 for cell in nb['worksheets'][0]['cells']:
139 for cell in nb['worksheets'][0]['cells']:
135 if cell['cell_type'] != 'code':
140 if cell['cell_type'] != 'code':
136 continue
141 continue
137 if not cell.get('trusted', False):
142 if not cell.get('trusted', False):
138 return False
143 return False
139 return True
144 return True
140
145
141 No newline at end of file
146
147 class NotebookNotary(LoggingConfigurable):
148 """A class for configuring notebook signatures
149
150 It stores the secret with which to sign notebooks,
151 and the hashing scheme to use for notebook signatures.
152 """
153
154 signature_scheme = Enum(hashlib.algorithms, default_value='sha256', config=True,
155 help="""The signature scheme used to sign notebooks."""
156 )
157
158 profile_dir = Instance("IPython.core.profiledir.ProfileDir")
159 def _profile_dir_default(self):
160 from IPython.core.application import BaseIPythonApplication
161 if BaseIPythonApplication.initialized():
162 app = BaseIPythonApplication.instance()
163 else:
164 # create an app, without the global instance
165 app = BaseIPythonApplication()
166 app.initialize()
167 return app.profile_dir
168
169 secret = Bytes(config=True,
170 help="""The secret key with which notebooks are signed."""
171 )
172 def _secret_default(self):
173 # note : this assumes an Application is running
174 profile_dir = self.profile_dir
175 secret_file = os.path.join(profile_dir.security_dir, 'notebook_secret')
176 if os.path.exists(secret_file):
177 with io.open(secret_file, 'rb') as f:
178 return f.read()
179 else:
180 secret = base64.encodestring(os.urandom(1024))
181 self.log.info("Writing output secret to %s", secret_file)
182 with io.open(secret_file, 'wb') as f:
183 f.write(secret)
184 try:
185 os.chmod(secret_file, 0o600)
186 except OSError:
187 self.log.warn(
188 "Could not set permissions on %s",
189 secret_file
190 )
191 return secret
192
193 No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now