From f69eb96cff7149b38bca068f4b7faaa7baf55902 Mon Sep 17 00:00:00 2001 From: Ryan Date: Fri, 20 May 2022 12:33:45 -0400 Subject: [PATCH] added hidden checks on FileContentsManager and accompanying tests --- notebook/services/contents/filemanager.py | 39 +++-- .../services/contents/tests/test_manager.py | 143 ++++++++++++++++++ 2 files changed, 172 insertions(+), 10 deletions(-) diff --git a/notebook/services/contents/filemanager.py b/notebook/services/contents/filemanager.py index f635d793fd..f9aa2cefb0 100644 --- a/notebook/services/contents/filemanager.py +++ b/notebook/services/contents/filemanager.py @@ -242,6 +242,12 @@ def _base_model(self, path): os_path = self._get_os_path(path) info = os.lstat(os_path) + four_o_four = "file or directory does not exist: %r" % path + + if is_hidden(os_path, self.root_dir) and not self.allow_hidden: + self.log.info("Refusing to serve hidden file or file in hidden directory %r, via 404 Error", os_path) + raise web.HTTPError(404, four_o_four) + try: # size of file size = info.st_size @@ -364,12 +370,6 @@ def _file_model(self, path, content=True, format=None): os_path = self._get_os_path(path) - four_o_four = "file does not exist: %r" % path - - if is_hidden(os_path, self.root_dir) and not self.allow_hidden: - self.log.info("Refusing to serve hidden file or file in hidden directory %r, via 404 Error", os_path) - raise web.HTTPError(404, four_o_four) - model['mimetype'] = mimetypes.guess_type(os_path)[0] if content: @@ -430,11 +430,17 @@ def get(self, path, content=True, type=None, format=None): of the file or directory as well. """ path = path.strip('/') + os_path = self._get_os_path(path) + four_o_four = "file or directory does not exist: %r" % path if not self.exists(path): - raise web.HTTPError(404, f'No such file or directory: {path}') + raise web.HTTPError(404, four_o_four) - os_path = self._get_os_path(path) + if is_hidden(os_path, self.root_dir) and not self.allow_hidden: + self.log.info("Refusing to serve hidden file or file in hidden directory %r, via 404 Error", os_path) + raise web.HTTPError(404, four_o_four) + + if os.path.isdir(os_path): if type not in (None, 'directory'): raise web.HTTPError(400, @@ -472,6 +478,10 @@ def save(self, model, path=''): raise web.HTTPError(400, 'No file content provided') os_path = self._get_os_path(path) + + if is_hidden(os_path, self.root_dir) and not self.allow_hidden: + raise web.HTTPError(400, f'Cannot create hidden file or directory {os_path!r}') + self.log.debug("Saving %s", os_path) self.run_pre_save_hook(model=model, path=path) @@ -515,8 +525,14 @@ def delete_file(self, path): path = path.strip('/') os_path = self._get_os_path(path) rm = os.unlink - if not os.path.exists(os_path): - raise web.HTTPError(404, f'File or directory does not exist: {os_path}') + + four_o_four = "file or directory does not exist: %r" % path + + if not self.exists(path): + raise web.HTTPError(404, four_o_four) + + if is_hidden(os_path, self.root_dir) and not self.allow_hidden: + raise web.HTTPError(400, f'Cannot delete hidden file or directory {os_path!r}') def is_non_empty_dir(os_path): if os.path.isdir(os_path): @@ -559,6 +575,9 @@ def rename_file(self, old_path, new_path): if new_path == old_path: return + if (is_hidden(old_path, self.root_dir) or is_hidden(new_path, self.root_dir)) and not self.allow_hidden: + raise web.HTTPError(400, f'Cannot rename hidden file or directory {os_path!r}') + # Perform path validation prior to converting to os-specific value since this # is still relative to root_dir. self._validate_path(new_path) diff --git a/notebook/services/contents/tests/test_manager.py b/notebook/services/contents/tests/test_manager.py index 4e65ee90f3..05043d6888 100644 --- a/notebook/services/contents/tests/test_manager.py +++ b/notebook/services/contents/tests/test_manager.py @@ -181,6 +181,149 @@ def test_403(self): else: self.fail("Should have raised HTTPError(403)") + @skipIf(sys.platform.startswith('win'), "Can't test hidden files on Windows") + def test_400(self): + #Test Delete behavior + #Test delete of file in hidden directory + with self.assertRaises(HTTPError) as excinfo: + with TemporaryDirectory() as td: + cm = FileContentsManager(root_dir=td) + hidden_dir = '.hidden' + file_in_hidden_path = os.path.join(hidden_dir,'visible.txt') + _make_dir(cm, hidden_dir) + model = cm.new(path=file_in_hidden_path) + os_path = cm._get_os_path(model['path']) + + try: + result = cm.delete_file(os_path) + except HTTPError as e: + self.assertEqual(e.status_code, 400) + else: + self.fail("Should have raised HTTPError(400)") + #Test delete hidden file in visible directory + with self.assertRaises(HTTPError) as excinfo: + with TemporaryDirectory() as td: + cm = FileContentsManager(root_dir=td) + hidden_dir = 'visible' + file_in_hidden_path = os.path.join(hidden_dir,'.hidden.txt') + _make_dir(cm, hidden_dir) + model = cm.new(path=file_in_hidden_path) + os_path = cm._get_os_path(model['path']) + + try: + result = cm.delete_file(os_path) + except HTTPError as e: + self.assertEqual(e.status_code, 400) + else: + self.fail("Should have raised HTTPError(400)") + + #Test Save behavior + #Test save of file in hidden directory + with self.assertRaises(HTTPError) as excinfo: + with TemporaryDirectory() as td: + cm = FileContentsManager(root_dir=td) + hidden_dir = '.hidden' + file_in_hidden_path = os.path.join(hidden_dir,'visible.txt') + _make_dir(cm, hidden_dir) + model = cm.new(path=file_in_hidden_path) + os_path = cm._get_os_path(model['path']) + + try: + result = cm.save(model,path=os_path) + except HTTPError as e: + self.assertEqual(e.status_code, 400) + else: + self.fail("Should have raised HTTPError(400)") + #Test save hidden file in visible directory + with self.assertRaises(HTTPError) as excinfo: + with TemporaryDirectory() as td: + cm = FileContentsManager(root_dir=td) + hidden_dir = 'visible' + file_in_hidden_path = os.path.join(hidden_dir,'.hidden.txt') + _make_dir(cm, hidden_dir) + model = cm.new(path=file_in_hidden_path) + os_path = cm._get_os_path(model['path']) + + try: + result = cm.save(model,path=os_path) + except HTTPError as e: + self.assertEqual(e.status_code, 400) + else: + self.fail("Should have raised HTTPError(400)") + + #Test rename behavior + #Test rename with source file in hidden directory + with self.assertRaises(HTTPError) as excinfo: + with TemporaryDirectory() as td: + cm = FileContentsManager(root_dir=td) + hidden_dir = '.hidden' + file_in_hidden_path = os.path.join(hidden_dir,'visible.txt') + _make_dir(cm, hidden_dir) + model = cm.new(path=file_in_hidden_path) + old_path = cm._get_os_path(model['path']) + new_path = "new.txt" + + try: + result = cm.rename_file(old_path, new_path) + except HTTPError as e: + self.assertEqual(e.status_code, 400) + else: + self.fail("Should have raised HTTPError(400)") + + #Test rename of dest file in hidden directory + with self.assertRaises(HTTPError) as excinfo: + with TemporaryDirectory() as td: + cm = FileContentsManager(root_dir=td) + hidden_dir = '.hidden' + file_in_hidden_path = os.path.join(hidden_dir,'visible.txt') + _make_dir(cm, hidden_dir) + model = cm.new(path=file_in_hidden_path) + new_path = cm._get_os_path(model['path']) + old_path = "old.txt" + + try: + result = cm.rename_file(old_path, new_path) + except HTTPError as e: + self.assertEqual(e.status_code, 400) + else: + self.fail("Should have raised HTTPError(400)") + + #Test rename with hidden source file in visible directory + with self.assertRaises(HTTPError) as excinfo: + with TemporaryDirectory() as td: + cm = FileContentsManager(root_dir=td) + hidden_dir = 'visible' + file_in_hidden_path = os.path.join(hidden_dir,'.hidden.txt') + _make_dir(cm, hidden_dir) + model = cm.new(path=file_in_hidden_path) + old_path = cm._get_os_path(model['path']) + new_path = "new.txt" + + try: + result = cm.rename_file(old_path, new_path) + except HTTPError as e: + self.assertEqual(e.status_code, 400) + else: + self.fail("Should have raised HTTPError(400)") + + #Test rename with hidden dest file in visible directory + with self.assertRaises(HTTPError) as excinfo: + with TemporaryDirectory() as td: + cm = FileContentsManager(root_dir=td) + hidden_dir = 'visible' + file_in_hidden_path = os.path.join(hidden_dir,'.hidden.txt') + _make_dir(cm, hidden_dir) + model = cm.new(path=file_in_hidden_path) + new_path = cm._get_os_path(model['path']) + old_path = "old.txt" + + try: + result = cm.rename_file(old_path, new_path) + except HTTPError as e: + self.assertEqual(e.status_code, 400) + else: + self.fail("Should have raised HTTPError(400)") + @skipIf(sys.platform.startswith('win'), "Can't test hidden files on Windows") def test_404(self): #Test visible file in hidden folder