Repository: ipython/ipython

Issue:
Unknown error with no read permissions
If you do not have read permission on a notebook (e.g., it has `-rw-------` and you are not the owner) and you try to open it, rather than saying permission denied, you get an error that implies there's something wrong with the notebook format:

![image](https://cloud.githubusercontent.com/assets/83444/6356023/53956f6e-bc0f-11e4-884b-aea89261a9e2.png)

In the notebook server, there is an unhandled permission denied traceback:

```
[E 11:09:31.650 NotebookApp] Unhandled error in API request
    Traceback (most recent call last):
      File "/Users/jhamrick/project/tools/ipython/IPython/html/base/handlers.py", line 365, in wrapper
        result = yield gen.maybe_future(method(self, *args, **kwargs))
      File "/usr/local/lib/python2.7/site-packages/tornado/gen.py", line 807, in run
        value = future.result()
      File "/usr/local/lib/python2.7/site-packages/tornado/concurrent.py", line 209, in result
        raise_exc_info(self._exc_info)
      File "/usr/local/lib/python2.7/site-packages/tornado/gen.py", line 212, in wrapper
        yielded = next(result)
      File "/Users/jhamrick/project/tools/ipython/IPython/html/services/contents/handlers.py", line 126, in get
        path=path, type=type, format=format, content=content,
      File "/Users/jhamrick/project/tools/ipython/IPython/html/services/contents/filemanager.py", line 347, in get
        model = self._notebook_model(path, content=content)
      File "/Users/jhamrick/project/tools/ipython/IPython/html/services/contents/filemanager.py", line 306, in _notebook_model
        nb = self._read_notebook(os_path, as_version=4)
      File "/Users/jhamrick/project/tools/ipython/IPython/html/services/contents/fileio.py", line 104, in _read_notebook
        with self.open(os_path, 'r', encoding='utf-8') as f:
      File "/usr/local/Cellar/python/2.7.9/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py", line 17, in __enter__
        return self.gen.next()
      File "/Users/jhamrick/project/tools/ipython/IPython/html/services/contents/fileio.py", line 49, in open
        with io.open(os_path, *args, **kwargs) as f:
    IOError: [Errno 13] Permission denied: '/Users/jhamrick/project/ipynb/Untitled9.ipynb'
```

Perhaps it would be possible to return a more informative generic error message, like just the last line of the traceback or something along those lines?



Discussion:

Resolving patch:
diff --git a/IPython/html/services/contents/fileio.py b/IPython/html/services/contents/fileio.py
--- a/IPython/html/services/contents/fileio.py
+++ b/IPython/html/services/contents/fileio.py
@@ -61,7 +61,7 @@ def perm_to_403(self, os_path=''):
         """context manager for turning permission errors into 403."""
         try:
             yield
-        except OSError as e:
+        except (OSError, IOError) as e:
             if e.errno in {errno.EPERM, errno.EACCES}:
                 # make 403 error message without root prefix
                 # this may not work perfectly on unicode paths on Python 2,


Test patch:
diff --git a/IPython/html/services/contents/tests/test_manager.py b/IPython/html/services/contents/tests/test_manager.py
--- a/IPython/html/services/contents/tests/test_manager.py
+++ b/IPython/html/services/contents/tests/test_manager.py
@@ -5,6 +5,7 @@
 import os
 import time
 
+from nose import SkipTest
 from tornado.web import HTTPError
 from unittest import TestCase
 from tempfile import NamedTemporaryFile
@@ -128,6 +129,25 @@ def test_good_symlink(self):
                 sorted(dir_model['content'], key=lambda x: x['name']),
                 [symlink_model, file_model],
             )
+    
+    def test_403(self):
+        if hasattr(os, 'getuid'):
+            if os.getuid() == 0:
+                raise SkipTest("Can't test permissions as root")
+        
+        with TemporaryDirectory() as td:
+            cm = FileContentsManager(root_dir=td)
+            model = cm.new_untitled(type='file')
+            os_path = cm._get_os_path(model['path'])
+        
+            os.chmod(os_path, 0o400)
+            try:
+                with cm.open(os_path, 'w') as f:
+                    f.write(u"don't care")
+            except HTTPError as e:
+                self.assertEqual(e.status_code, 403)
+            else:
+                self.fail("Should have raised HTTPError(403)")
 
 
 class TestContentsManager(TestCase):