##// END OF EJS Templates
handle various permission failures...
Min RK -
Show More
@@ -4,10 +4,11 b''
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 import base64
6 import base64
7 import errno
7 import io
8 import io
8 import os
9 import os
9 import glob
10 import shutil
10 import shutil
11 from contextlib import contextmanager
11
12
12 from tornado import web
13 from tornado import web
13
14
@@ -16,9 +17,9 b' from IPython import nbformat'
16 from IPython.utils.io import atomic_writing
17 from IPython.utils.io import atomic_writing
17 from IPython.utils.path import ensure_dir_exists
18 from IPython.utils.path import ensure_dir_exists
18 from IPython.utils.traitlets import Unicode, Bool, TraitError
19 from IPython.utils.traitlets import Unicode, Bool, TraitError
19 from IPython.utils.py3compat import getcwd
20 from IPython.utils.py3compat import getcwd, str_to_unicode
20 from IPython.utils import tz
21 from IPython.utils import tz
21 from IPython.html.utils import is_hidden, to_os_path, url_path_join
22 from IPython.html.utils import is_hidden, to_os_path, to_api_path
22
23
23
24
24 class FileContentsManager(ContentsManager):
25 class FileContentsManager(ContentsManager):
@@ -30,7 +31,38 b' class FileContentsManager(ContentsManager):'
30 return self.parent.notebook_dir
31 return self.parent.notebook_dir
31 except AttributeError:
32 except AttributeError:
32 return getcwd()
33 return getcwd()
33
34
35 @contextmanager
36 def perm_to_403(self, os_path=''):
37 """context manager for turning permission errors into 403"""
38 try:
39 yield
40 except OSError as e:
41 if e.errno in {errno.EPERM, errno.EACCES}:
42 # make 403 error message without root prefix
43 # this may not work perfectly on unicode paths on Python 2,
44 # but nobody should be doing that anyway.
45 if not os_path:
46 os_path = str_to_unicode(e.filename or 'unknown file')
47 path = to_api_path(os_path, self.root_dir)
48 raise web.HTTPError(403, u'Permission denied: %s' % path)
49 else:
50 raise
51
52 @contextmanager
53 def open(self, os_path, *args, **kwargs):
54 """wrapper around io.open that turns permission errors into 403"""
55 with self.perm_to_403(os_path):
56 with io.open(os_path, *args, **kwargs) as f:
57 yield f
58
59 @contextmanager
60 def open_w(self, os_path, *args, **kwargs):
61 """wrapper around atomic_writing that turns permission errors into 403"""
62 with self.perm_to_403(os_path):
63 with atomic_writing(os_path, *args, **kwargs) as f:
64 yield f
65
34 save_script = Bool(False, config=True, help='DEPRECATED, IGNORED')
66 save_script = Bool(False, config=True, help='DEPRECATED, IGNORED')
35 def _save_script_changed(self):
67 def _save_script_changed(self):
36 self.log.warn("""
68 self.log.warn("""
@@ -172,6 +204,11 b' class FileContentsManager(ContentsManager):'
172 model['created'] = created
204 model['created'] = created
173 model['content'] = None
205 model['content'] = None
174 model['format'] = None
206 model['format'] = None
207 try:
208 model['writable'] = os.access(os_path, os.W_OK)
209 except OSError:
210 self.log.error("Failed to check write permissions on %s", os_path)
211 model['writable'] = False
175 return model
212 return model
176
213
177 def _dir_model(self, path, content=True):
214 def _dir_model(self, path, content=True):
@@ -181,7 +218,7 b' class FileContentsManager(ContentsManager):'
181 """
218 """
182 os_path = self._get_os_path(path)
219 os_path = self._get_os_path(path)
183
220
184 four_o_four = u'directory does not exist: %r' % os_path
221 four_o_four = u'directory does not exist: %r' % path
185
222
186 if not os.path.isdir(os_path):
223 if not os.path.isdir(os_path):
187 raise web.HTTPError(404, four_o_four)
224 raise web.HTTPError(404, four_o_four)
@@ -232,7 +269,7 b' class FileContentsManager(ContentsManager):'
232 if not os.path.isfile(os_path):
269 if not os.path.isfile(os_path):
233 # could be FIFO
270 # could be FIFO
234 raise web.HTTPError(400, "Cannot get content of non-file %s" % os_path)
271 raise web.HTTPError(400, "Cannot get content of non-file %s" % os_path)
235 with io.open(os_path, 'rb') as f:
272 with self.open(os_path, 'rb') as f:
236 bcontent = f.read()
273 bcontent = f.read()
237
274
238 if format != 'base64':
275 if format != 'base64':
@@ -261,7 +298,7 b' class FileContentsManager(ContentsManager):'
261 model['type'] = 'notebook'
298 model['type'] = 'notebook'
262 if content:
299 if content:
263 os_path = self._get_os_path(path)
300 os_path = self._get_os_path(path)
264 with io.open(os_path, 'r', encoding='utf-8') as f:
301 with self.open(os_path, 'r', encoding='utf-8') as f:
265 try:
302 try:
266 nb = nbformat.read(f, as_version=4)
303 nb = nbformat.read(f, as_version=4)
267 except Exception as e:
304 except Exception as e:
@@ -321,7 +358,7 b' class FileContentsManager(ContentsManager):'
321
358
322 self.check_and_sign(nb, path)
359 self.check_and_sign(nb, path)
323
360
324 with atomic_writing(os_path, encoding='utf-8') as f:
361 with self.open_w(os_path, encoding='utf-8') as f:
325 nbformat.write(nb, f, version=nbformat.NO_CONVERT)
362 nbformat.write(nb, f, version=nbformat.NO_CONVERT)
326
363
327 def _save_file(self, os_path, model, path=''):
364 def _save_file(self, os_path, model, path=''):
@@ -338,7 +375,7 b' class FileContentsManager(ContentsManager):'
338 bcontent = base64.decodestring(b64_bytes)
375 bcontent = base64.decodestring(b64_bytes)
339 except Exception as e:
376 except Exception as e:
340 raise web.HTTPError(400, u'Encoding error saving %s: %s' % (os_path, e))
377 raise web.HTTPError(400, u'Encoding error saving %s: %s' % (os_path, e))
341 with atomic_writing(os_path, text=False) as f:
378 with self.open_w(os_path, text=False) as f:
342 f.write(bcontent)
379 f.write(bcontent)
343
380
344 def _save_directory(self, os_path, model, path=''):
381 def _save_directory(self, os_path, model, path=''):
@@ -346,7 +383,8 b' class FileContentsManager(ContentsManager):'
346 if is_hidden(os_path, self.root_dir):
383 if is_hidden(os_path, self.root_dir):
347 raise web.HTTPError(400, u'Cannot create hidden directory %r' % os_path)
384 raise web.HTTPError(400, u'Cannot create hidden directory %r' % os_path)
348 if not os.path.exists(os_path):
385 if not os.path.exists(os_path):
349 os.mkdir(os_path)
386 with self.perm_to_403():
387 os.mkdir(os_path)
350 elif not os.path.isdir(os_path):
388 elif not os.path.isdir(os_path):
351 raise web.HTTPError(400, u'Not a directory: %s' % (os_path))
389 raise web.HTTPError(400, u'Not a directory: %s' % (os_path))
352 else:
390 else:
@@ -379,7 +417,8 b' class FileContentsManager(ContentsManager):'
379 except web.HTTPError:
417 except web.HTTPError:
380 raise
418 raise
381 except Exception as e:
419 except Exception as e:
382 raise web.HTTPError(400, u'Unexpected error while saving file: %s %s' % (os_path, e))
420 self.log.error(u'Error while saving file: %s %s', path, e, exc_info=True)
421 raise web.HTTPError(500, u'Unexpected error while saving file: %s %s' % (path, e))
383
422
384 validation_message = None
423 validation_message = None
385 if model['type'] == 'notebook':
424 if model['type'] == 'notebook':
@@ -423,14 +462,17 b' class FileContentsManager(ContentsManager):'
423 cp_path = self.get_checkpoint_path(checkpoint_id, path)
462 cp_path = self.get_checkpoint_path(checkpoint_id, path)
424 if os.path.isfile(cp_path):
463 if os.path.isfile(cp_path):
425 self.log.debug("Unlinking checkpoint %s", cp_path)
464 self.log.debug("Unlinking checkpoint %s", cp_path)
426 os.unlink(cp_path)
465 with self.perm_to_403():
466 rm(cp_path)
427
467
428 if os.path.isdir(os_path):
468 if os.path.isdir(os_path):
429 self.log.debug("Removing directory %s", os_path)
469 self.log.debug("Removing directory %s", os_path)
430 shutil.rmtree(os_path)
470 with self.perm_to_403():
471 shutil.rmtree(os_path)
431 else:
472 else:
432 self.log.debug("Unlinking file %s", os_path)
473 self.log.debug("Unlinking file %s", os_path)
433 rm(os_path)
474 with self.perm_to_403():
475 rm(os_path)
434
476
435 def rename(self, old_path, new_path):
477 def rename(self, old_path, new_path):
436 """Rename a file."""
478 """Rename a file."""
@@ -448,7 +490,10 b' class FileContentsManager(ContentsManager):'
448
490
449 # Move the file
491 # Move the file
450 try:
492 try:
451 shutil.move(old_os_path, new_os_path)
493 with self.perm_to_403():
494 shutil.move(old_os_path, new_os_path)
495 except web.HTTPError:
496 raise
452 except Exception as e:
497 except Exception as e:
453 raise web.HTTPError(500, u'Unknown error renaming file: %s %s' % (old_path, e))
498 raise web.HTTPError(500, u'Unknown error renaming file: %s %s' % (old_path, e))
454
499
@@ -460,7 +505,8 b' class FileContentsManager(ContentsManager):'
460 new_cp_path = self.get_checkpoint_path(checkpoint_id, new_path)
505 new_cp_path = self.get_checkpoint_path(checkpoint_id, new_path)
461 if os.path.isfile(old_cp_path):
506 if os.path.isfile(old_cp_path):
462 self.log.debug("Renaming checkpoint %s -> %s", old_cp_path, new_cp_path)
507 self.log.debug("Renaming checkpoint %s -> %s", old_cp_path, new_cp_path)
463 shutil.move(old_cp_path, new_cp_path)
508 with self.perm_to_403():
509 shutil.move(old_cp_path, new_cp_path)
464
510
465 # Checkpoint-related utilities
511 # Checkpoint-related utilities
466
512
@@ -477,7 +523,8 b' class FileContentsManager(ContentsManager):'
477 )
523 )
478 os_path = self._get_os_path(path=parent)
524 os_path = self._get_os_path(path=parent)
479 cp_dir = os.path.join(os_path, self.checkpoint_dir)
525 cp_dir = os.path.join(os_path, self.checkpoint_dir)
480 ensure_dir_exists(cp_dir)
526 with self.perm_to_403():
527 ensure_dir_exists(cp_dir)
481 cp_path = os.path.join(cp_dir, filename)
528 cp_path = os.path.join(cp_dir, filename)
482 return cp_path
529 return cp_path
483
530
@@ -505,7 +552,8 b' class FileContentsManager(ContentsManager):'
505 checkpoint_id = u"checkpoint"
552 checkpoint_id = u"checkpoint"
506 cp_path = self.get_checkpoint_path(checkpoint_id, path)
553 cp_path = self.get_checkpoint_path(checkpoint_id, path)
507 self.log.debug("creating checkpoint for %s", path)
554 self.log.debug("creating checkpoint for %s", path)
508 self._copy(src_path, cp_path)
555 with self.perm_to_403():
556 self._copy(src_path, cp_path)
509
557
510 # return the checkpoint info
558 # return the checkpoint info
511 return self.get_checkpoint_model(checkpoint_id, path)
559 return self.get_checkpoint_model(checkpoint_id, path)
@@ -537,10 +585,11 b' class FileContentsManager(ContentsManager):'
537 )
585 )
538 # ensure notebook is readable (never restore from an unreadable notebook)
586 # ensure notebook is readable (never restore from an unreadable notebook)
539 if cp_path.endswith('.ipynb'):
587 if cp_path.endswith('.ipynb'):
540 with io.open(cp_path, 'r', encoding='utf-8') as f:
588 with self.open(cp_path, 'r', encoding='utf-8') as f:
541 nbformat.read(f, as_version=4)
589 nbformat.read(f, as_version=4)
542 self._copy(cp_path, nb_path)
543 self.log.debug("copying %s -> %s", cp_path, nb_path)
590 self.log.debug("copying %s -> %s", cp_path, nb_path)
591 with self.perm_to_403():
592 self._copy(cp_path, nb_path)
544
593
545 def delete_checkpoint(self, checkpoint_id, path):
594 def delete_checkpoint(self, checkpoint_id, path):
546 """delete a file's checkpoint"""
595 """delete a file's checkpoint"""
@@ -110,7 +110,7 b' define(['
110 CodeMirror.runMode(code, mode, el);
110 CodeMirror.runMode(code, mode, el);
111 callback(null, el.innerHTML);
111 callback(null, el.innerHTML);
112 } catch (err) {
112 } catch (err) {
113 console.log("Failed to highlight " + lang + " code", error);
113 console.log("Failed to highlight " + lang + " code", err);
114 callback(err, code);
114 callback(err, code);
115 }
115 }
116 }, function (err) {
116 }, function (err) {
@@ -132,6 +132,7 b' define(['
132 this.undelete_index = null;
132 this.undelete_index = null;
133 this.undelete_below = false;
133 this.undelete_below = false;
134 this.paste_enabled = false;
134 this.paste_enabled = false;
135 this.writable = false;
135 // It is important to start out in command mode to match the intial mode
136 // It is important to start out in command mode to match the intial mode
136 // of the KeyboardManager.
137 // of the KeyboardManager.
137 this.mode = 'command';
138 this.mode = 'command';
@@ -1896,6 +1897,10 b' define(['
1896 if (this.autosave_timer) {
1897 if (this.autosave_timer) {
1897 clearInterval(this.autosave_timer);
1898 clearInterval(this.autosave_timer);
1898 }
1899 }
1900 if (!this.writable) {
1901 // disable autosave if not writable
1902 interval = 0;
1903 }
1899
1904
1900 this.autosave_interval = this.minimum_autosave_interval = interval;
1905 this.autosave_interval = this.minimum_autosave_interval = interval;
1901 if (interval) {
1906 if (interval) {
@@ -1918,12 +1923,18 b' define(['
1918 * @method save_notebook
1923 * @method save_notebook
1919 */
1924 */
1920 Notebook.prototype.save_notebook = function () {
1925 Notebook.prototype.save_notebook = function () {
1921 if(!this._fully_loaded){
1926 if (!this._fully_loaded) {
1922 this.events.trigger('notebook_save_failed.Notebook',
1927 this.events.trigger('notebook_save_failed.Notebook',
1923 new Error("Load failed, save is disabled")
1928 new Error("Load failed, save is disabled")
1924 );
1929 );
1925 return;
1930 return;
1931 } else if (!this.writable) {
1932 this.events.trigger('notebook_save_failed.Notebook',
1933 new Error("Notebook is read-only")
1934 );
1935 return;
1926 }
1936 }
1937
1927 // Create a JSON model to be sent to the server.
1938 // Create a JSON model to be sent to the server.
1928 var model = {
1939 var model = {
1929 type : "notebook",
1940 type : "notebook",
@@ -2052,7 +2063,8 b' define(['
2052 });
2063 });
2053 };
2064 };
2054
2065
2055 Notebook.prototype.copy_notebook = function(){
2066 Notebook.prototype.copy_notebook = function () {
2067 var that = this;
2056 var base_url = this.base_url;
2068 var base_url = this.base_url;
2057 var w = window.open();
2069 var w = window.open();
2058 var parent = utils.url_path_split(this.notebook_path)[0];
2070 var parent = utils.url_path_split(this.notebook_path)[0];
@@ -2064,7 +2076,7 b' define(['
2064 },
2076 },
2065 function(error) {
2077 function(error) {
2066 w.close();
2078 w.close();
2067 console.log(error);
2079 that.events.trigger('notebook_copy_failed', error);
2068 }
2080 }
2069 );
2081 );
2070 };
2082 };
@@ -2175,6 +2187,7 b' define(['
2175 }
2187 }
2176 this.set_dirty(false);
2188 this.set_dirty(false);
2177 this.scroll_to_top();
2189 this.scroll_to_top();
2190 this.writable = data.writable || false;
2178 var nbmodel = data.content;
2191 var nbmodel = data.content;
2179 var orig_nbformat = nbmodel.metadata.orig_nbformat;
2192 var orig_nbformat = nbmodel.metadata.orig_nbformat;
2180 var orig_nbformat_minor = nbmodel.metadata.orig_nbformat_minor;
2193 var orig_nbformat_minor = nbmodel.metadata.orig_nbformat_minor;
@@ -2249,7 +2262,12 b' define(['
2249 } else {
2262 } else {
2250 celltoolbar.CellToolbar.global_hide();
2263 celltoolbar.CellToolbar.global_hide();
2251 }
2264 }
2252
2265
2266 if (!this.writable) {
2267 this.set_autosave_interval(0);
2268 this.events.trigger('notebook_read_only.Notebook');
2269 }
2270
2253 // now that we're fully loaded, it is safe to restore save functionality
2271 // now that we're fully loaded, it is safe to restore save functionality
2254 this._fully_loaded = true;
2272 this._fully_loaded = true;
2255 this.events.trigger('notebook_loaded.Notebook');
2273 this.events.trigger('notebook_loaded.Notebook');
@@ -340,8 +340,11 b' define(['
340 this.events.on('notebook_saved.Notebook', function () {
340 this.events.on('notebook_saved.Notebook', function () {
341 nnw.set_message("Notebook saved",2000);
341 nnw.set_message("Notebook saved",2000);
342 });
342 });
343 this.events.on('notebook_save_failed.Notebook', function (evt, xhr, status, data) {
343 this.events.on('notebook_save_failed.Notebook', function (evt, error) {
344 nnw.warning(data || "Notebook save failed");
344 nnw.warning(error.message || "Notebook save failed");
345 });
346 this.events.on('notebook_copy_failed.Notebook', function (evt, error) {
347 nnw.warning(error.message || "Notebook copy failed");
345 });
348 });
346
349
347 // Checkpoint events
350 // Checkpoint events
@@ -46,6 +46,11 b' define(['
46 this.events.on('notebook_save_failed.Notebook', function () {
46 this.events.on('notebook_save_failed.Notebook', function () {
47 that.set_save_status('Autosave Failed!');
47 that.set_save_status('Autosave Failed!');
48 });
48 });
49 this.events.on('notebook_read_only.Notebook', function () {
50 that.set_save_status('(read only)');
51 // disable future set_save_status
52 that.set_save_status = function () {};
53 });
49 this.events.on('checkpoints_listed.Notebook', function (event, data) {
54 this.events.on('checkpoints_listed.Notebook', function (event, data) {
50 that._set_last_checkpoint(data[0]);
55 that._set_last_checkpoint(data[0]);
51 });
56 });
@@ -1,16 +1,7 b''
1 """Notebook related utilities
1 """Notebook related utilities"""
2
2
3 Authors:
3 # Copyright (c) IPython Development Team.
4
4 # Distributed under the terms of the Modified BSD License.
5 * Brian Granger
6 """
7
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2011 The IPython Development Team
10 #
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
14
5
15 from __future__ import print_function
6 from __future__ import print_function
16
7
@@ -28,9 +19,6 b' from IPython.utils import py3compat'
28 # It is used by BSD to indicate hidden files.
19 # It is used by BSD to indicate hidden files.
29 UF_HIDDEN = getattr(stat, 'UF_HIDDEN', 32768)
20 UF_HIDDEN = getattr(stat, 'UF_HIDDEN', 32768)
30
21
31 #-----------------------------------------------------------------------------
32 # Imports
33 #-----------------------------------------------------------------------------
34
22
35 def url_path_join(*pieces):
23 def url_path_join(*pieces):
36 """Join components of url into a relative url
24 """Join components of url into a relative url
@@ -137,4 +125,17 b" def to_os_path(path, root=''):"
137 parts = [p for p in parts if p != ''] # remove duplicate splits
125 parts = [p for p in parts if p != ''] # remove duplicate splits
138 path = os.path.join(root, *parts)
126 path = os.path.join(root, *parts)
139 return path
127 return path
128
129 def to_api_path(os_path, root=''):
130 """Convert a filesystem path to an API path
140
131
132 If given, root will be removed from the path.
133 root must be a filesystem path already.
134 """
135 if os_path.startswith(root):
136 os_path = os_path[len(root):]
137 parts = os_path.strip(os.path.sep).split(os.path.sep)
138 parts = [p for p in parts if p != ''] # remove duplicate splits
139 path = '/'.join(parts)
140 return path
141
General Comments 0
You need to be logged in to leave comments. Login now