##// END OF EJS Templates
TEST: Move test_escape_root to TestFileContentsManager...
Scott Sanderson -
Show More
@@ -1,500 +1,498 b''
1 1 # coding: utf-8
2 2 """Tests for the notebook manager."""
3 3 from __future__ import print_function
4 4
5 5 import os
6 6 import sys
7 7 import time
8 8 from contextlib import contextmanager
9 9
10 10 from nose import SkipTest
11 11 from tornado.web import HTTPError
12 12 from unittest import TestCase
13 13 from tempfile import NamedTemporaryFile
14 14
15 15 from IPython.nbformat import v4 as nbformat
16 16
17 17 from IPython.utils.tempdir import TemporaryDirectory
18 18 from IPython.utils.traitlets import TraitError
19 19 from IPython.html.utils import url_path_join
20 20 from IPython.testing import decorators as dec
21 21
22 22 from ..filemanager import FileContentsManager
23 23
24 24
25 25 def _make_dir(contents_manager, api_path):
26 26 """
27 27 Make a directory.
28 28 """
29 29 os_path = contents_manager._get_os_path(api_path)
30 30 try:
31 31 os.makedirs(os_path)
32 32 except OSError:
33 33 print("Directory already exists: %r" % os_path)
34 34
35 35
36 36 class TestFileContentsManager(TestCase):
37 37
38 @contextmanager
39 def assertRaisesHTTPError(self, status, msg=None):
40 msg = msg or "Should have raised HTTPError(%i)" % status
41 try:
42 yield
43 except HTTPError as e:
44 self.assertEqual(e.status_code, status)
45 else:
46 self.fail(msg)
47
38 48 def symlink(self, contents_manager, src, dst):
39 49 """Make a symlink to src from dst
40 50
41 51 src and dst are api_paths
42 52 """
43 53 src_os_path = contents_manager._get_os_path(src)
44 54 dst_os_path = contents_manager._get_os_path(dst)
45 55 print(src_os_path, dst_os_path, os.path.isfile(src_os_path))
46 56 os.symlink(src_os_path, dst_os_path)
47 57
48 58 def test_root_dir(self):
49 59 with TemporaryDirectory() as td:
50 60 fm = FileContentsManager(root_dir=td)
51 61 self.assertEqual(fm.root_dir, td)
52 62
53 63 def test_missing_root_dir(self):
54 64 with TemporaryDirectory() as td:
55 65 root = os.path.join(td, 'notebook', 'dir', 'is', 'missing')
56 66 self.assertRaises(TraitError, FileContentsManager, root_dir=root)
57 67
58 68 def test_invalid_root_dir(self):
59 69 with NamedTemporaryFile() as tf:
60 70 self.assertRaises(TraitError, FileContentsManager, root_dir=tf.name)
61 71
62 72 def test_get_os_path(self):
63 73 # full filesystem path should be returned with correct operating system
64 74 # separators.
65 75 with TemporaryDirectory() as td:
66 76 root = td
67 77 fm = FileContentsManager(root_dir=root)
68 78 path = fm._get_os_path('/path/to/notebook/test.ipynb')
69 79 rel_path_list = '/path/to/notebook/test.ipynb'.split('/')
70 80 fs_path = os.path.join(fm.root_dir, *rel_path_list)
71 81 self.assertEqual(path, fs_path)
72 82
73 83 fm = FileContentsManager(root_dir=root)
74 84 path = fm._get_os_path('test.ipynb')
75 85 fs_path = os.path.join(fm.root_dir, 'test.ipynb')
76 86 self.assertEqual(path, fs_path)
77 87
78 88 fm = FileContentsManager(root_dir=root)
79 89 path = fm._get_os_path('////test.ipynb')
80 90 fs_path = os.path.join(fm.root_dir, 'test.ipynb')
81 91 self.assertEqual(path, fs_path)
82 92
83 93 def test_checkpoint_subdir(self):
84 94 subd = u'sub βˆ‚ir'
85 95 cp_name = 'test-cp.ipynb'
86 96 with TemporaryDirectory() as td:
87 97 root = td
88 98 os.mkdir(os.path.join(td, subd))
89 99 fm = FileContentsManager(root_dir=root)
90 100 cpm = fm.checkpoints
91 101 cp_dir = cpm.checkpoint_path(
92 102 'cp', 'test.ipynb'
93 103 )
94 104 cp_subdir = cpm.checkpoint_path(
95 105 'cp', '/%s/test.ipynb' % subd
96 106 )
97 107 self.assertNotEqual(cp_dir, cp_subdir)
98 108 self.assertEqual(cp_dir, os.path.join(root, cpm.checkpoint_dir, cp_name))
99 109 self.assertEqual(cp_subdir, os.path.join(root, subd, cpm.checkpoint_dir, cp_name))
100 110
101 111 @dec.skip_win32
102 112 def test_bad_symlink(self):
103 113 with TemporaryDirectory() as td:
104 114 cm = FileContentsManager(root_dir=td)
105 115 path = 'test bad symlink'
106 116 _make_dir(cm, path)
107 117
108 118 file_model = cm.new_untitled(path=path, ext='.txt')
109 119
110 120 # create a broken symlink
111 121 self.symlink(cm, "target", '%s/%s' % (path, 'bad symlink'))
112 122 model = cm.get(path)
113 123 self.assertEqual(model['content'], [file_model])
114 124
115 125 @dec.skip_win32
116 126 def test_good_symlink(self):
117 127 with TemporaryDirectory() as td:
118 128 cm = FileContentsManager(root_dir=td)
119 129 parent = 'test good symlink'
120 130 name = 'good symlink'
121 131 path = '{0}/{1}'.format(parent, name)
122 132 _make_dir(cm, parent)
123 133
124 134 file_model = cm.new(path=parent + '/zfoo.txt')
125 135
126 136 # create a good symlink
127 137 self.symlink(cm, file_model['path'], path)
128 138 symlink_model = cm.get(path, content=False)
129 139 dir_model = cm.get(parent)
130 140 self.assertEqual(
131 141 sorted(dir_model['content'], key=lambda x: x['name']),
132 142 [symlink_model, file_model],
133 143 )
134 144
135 145 def test_403(self):
136 146 if hasattr(os, 'getuid'):
137 147 if os.getuid() == 0:
138 148 raise SkipTest("Can't test permissions as root")
139 149 if sys.platform.startswith('win'):
140 150 raise SkipTest("Can't test permissions on Windows")
141 151
142 152 with TemporaryDirectory() as td:
143 153 cm = FileContentsManager(root_dir=td)
144 154 model = cm.new_untitled(type='file')
145 155 os_path = cm._get_os_path(model['path'])
146 156
147 157 os.chmod(os_path, 0o400)
148 158 try:
149 159 with cm.open(os_path, 'w') as f:
150 160 f.write(u"don't care")
151 161 except HTTPError as e:
152 162 self.assertEqual(e.status_code, 403)
153 163 else:
154 164 self.fail("Should have raised HTTPError(403)")
155 165
166 def test_escape_root(self):
167 with TemporaryDirectory() as td:
168 cm = FileContentsManager(root_dir=td)
169 # make foo, bar next to root
170 with open(os.path.join(cm.root_dir, '..', 'foo'), 'w') as f:
171 f.write('foo')
172 with open(os.path.join(cm.root_dir, '..', 'bar'), 'w') as f:
173 f.write('bar')
174
175 with self.assertRaisesHTTPError(404):
176 cm.get('..')
177 with self.assertRaisesHTTPError(404):
178 cm.get('foo/../../../bar')
179 with self.assertRaisesHTTPError(404):
180 cm.delete('../foo')
181 with self.assertRaisesHTTPError(404):
182 cm.rename('../foo', '../bar')
183 with self.assertRaisesHTTPError(404):
184 cm.save(model={
185 'type': 'file',
186 'content': u'',
187 'format': 'text',
188 }, path='../foo')
189
156 190
157 191 class TestContentsManager(TestCase):
158 192
159 193 def setUp(self):
160 194 self._temp_dir = TemporaryDirectory()
161 195 self.td = self._temp_dir.name
162 196 self.contents_manager = FileContentsManager(
163 197 root_dir=self.td,
164 198 )
165 199
166 200 def tearDown(self):
167 201 self._temp_dir.cleanup()
168 202
169 @contextmanager
170 def assertRaisesHTTPError(self, status, msg=None):
171 msg = msg or "Should have raised HTTPError(%i)" % status
172 try:
173 yield
174 except HTTPError as e:
175 self.assertEqual(e.status_code, status)
176 else:
177 self.fail(msg)
178
179 203 def make_dir(self, api_path):
180 204 """make a subdirectory at api_path
181 205
182 206 override in subclasses if contents are not on the filesystem.
183 207 """
184 208 _make_dir(self.contents_manager, api_path)
185 209
186 210 def add_code_cell(self, nb):
187 211 output = nbformat.new_output("display_data", {'application/javascript': "alert('hi');"})
188 212 cell = nbformat.new_code_cell("print('hi')", outputs=[output])
189 213 nb.cells.append(cell)
190 214
191 215 def new_notebook(self):
192 216 cm = self.contents_manager
193 217 model = cm.new_untitled(type='notebook')
194 218 name = model['name']
195 219 path = model['path']
196 220
197 221 full_model = cm.get(path)
198 222 nb = full_model['content']
199 223 nb['metadata']['counter'] = int(1e6 * time.time())
200 224 self.add_code_cell(nb)
201 225
202 226 cm.save(full_model, path)
203 227 return nb, name, path
204 228
205 229 def test_new_untitled(self):
206 230 cm = self.contents_manager
207 231 # Test in root directory
208 232 model = cm.new_untitled(type='notebook')
209 233 assert isinstance(model, dict)
210 234 self.assertIn('name', model)
211 235 self.assertIn('path', model)
212 236 self.assertIn('type', model)
213 237 self.assertEqual(model['type'], 'notebook')
214 238 self.assertEqual(model['name'], 'Untitled.ipynb')
215 239 self.assertEqual(model['path'], 'Untitled.ipynb')
216 240
217 241 # Test in sub-directory
218 242 model = cm.new_untitled(type='directory')
219 243 assert isinstance(model, dict)
220 244 self.assertIn('name', model)
221 245 self.assertIn('path', model)
222 246 self.assertIn('type', model)
223 247 self.assertEqual(model['type'], 'directory')
224 248 self.assertEqual(model['name'], 'Untitled Folder')
225 249 self.assertEqual(model['path'], 'Untitled Folder')
226 250 sub_dir = model['path']
227 251
228 252 model = cm.new_untitled(path=sub_dir)
229 253 assert isinstance(model, dict)
230 254 self.assertIn('name', model)
231 255 self.assertIn('path', model)
232 256 self.assertIn('type', model)
233 257 self.assertEqual(model['type'], 'file')
234 258 self.assertEqual(model['name'], 'untitled')
235 259 self.assertEqual(model['path'], '%s/untitled' % sub_dir)
236 260
237 261 def test_get(self):
238 262 cm = self.contents_manager
239 263 # Create a notebook
240 264 model = cm.new_untitled(type='notebook')
241 265 name = model['name']
242 266 path = model['path']
243 267
244 268 # Check that we 'get' on the notebook we just created
245 269 model2 = cm.get(path)
246 270 assert isinstance(model2, dict)
247 271 self.assertIn('name', model2)
248 272 self.assertIn('path', model2)
249 273 self.assertEqual(model['name'], name)
250 274 self.assertEqual(model['path'], path)
251 275
252 276 nb_as_file = cm.get(path, content=True, type='file')
253 277 self.assertEqual(nb_as_file['path'], path)
254 278 self.assertEqual(nb_as_file['type'], 'file')
255 279 self.assertEqual(nb_as_file['format'], 'text')
256 280 self.assertNotIsInstance(nb_as_file['content'], dict)
257 281
258 282 nb_as_bin_file = cm.get(path, content=True, type='file', format='base64')
259 283 self.assertEqual(nb_as_bin_file['format'], 'base64')
260 284
261 285 # Test in sub-directory
262 286 sub_dir = '/foo/'
263 287 self.make_dir('foo')
264 288 model = cm.new_untitled(path=sub_dir, ext='.ipynb')
265 289 model2 = cm.get(sub_dir + name)
266 290 assert isinstance(model2, dict)
267 291 self.assertIn('name', model2)
268 292 self.assertIn('path', model2)
269 293 self.assertIn('content', model2)
270 294 self.assertEqual(model2['name'], 'Untitled.ipynb')
271 295 self.assertEqual(model2['path'], '{0}/{1}'.format(sub_dir.strip('/'), name))
272 296
273 297 # Test with a regular file.
274 298 file_model_path = cm.new_untitled(path=sub_dir, ext='.txt')['path']
275 299 file_model = cm.get(file_model_path)
276 300 self.assertDictContainsSubset(
277 301 {
278 302 'content': u'',
279 303 'format': u'text',
280 304 'mimetype': u'text/plain',
281 305 'name': u'untitled.txt',
282 306 'path': u'foo/untitled.txt',
283 307 'type': u'file',
284 308 'writable': True,
285 309 },
286 310 file_model,
287 311 )
288 312 self.assertIn('created', file_model)
289 313 self.assertIn('last_modified', file_model)
290 314
291 315 # Test getting directory model
292 316
293 317 # Create a sub-sub directory to test getting directory contents with a
294 318 # subdir.
295 319 self.make_dir('foo/bar')
296 320 dirmodel = cm.get('foo')
297 321 self.assertEqual(dirmodel['type'], 'directory')
298 322 self.assertIsInstance(dirmodel['content'], list)
299 323 self.assertEqual(len(dirmodel['content']), 3)
300 324 self.assertEqual(dirmodel['path'], 'foo')
301 325 self.assertEqual(dirmodel['name'], 'foo')
302 326
303 327 # Directory contents should match the contents of each individual entry
304 328 # when requested with content=False.
305 329 model2_no_content = cm.get(sub_dir + name, content=False)
306 330 file_model_no_content = cm.get(u'foo/untitled.txt', content=False)
307 331 sub_sub_dir_no_content = cm.get('foo/bar', content=False)
308 332 self.assertEqual(sub_sub_dir_no_content['path'], 'foo/bar')
309 333 self.assertEqual(sub_sub_dir_no_content['name'], 'bar')
310 334
311 335 for entry in dirmodel['content']:
312 336 # Order isn't guaranteed by the spec, so this is a hacky way of
313 337 # verifying that all entries are matched.
314 338 if entry['path'] == sub_sub_dir_no_content['path']:
315 339 self.assertEqual(entry, sub_sub_dir_no_content)
316 340 elif entry['path'] == model2_no_content['path']:
317 341 self.assertEqual(entry, model2_no_content)
318 342 elif entry['path'] == file_model_no_content['path']:
319 343 self.assertEqual(entry, file_model_no_content)
320 344 else:
321 345 self.fail("Unexpected directory entry: %s" % entry())
322 346
323 347 with self.assertRaises(HTTPError):
324 348 cm.get('foo', type='file')
325 349
326 350 def test_update(self):
327 351 cm = self.contents_manager
328 352 # Create a notebook
329 353 model = cm.new_untitled(type='notebook')
330 354 name = model['name']
331 355 path = model['path']
332 356
333 357 # Change the name in the model for rename
334 358 model['path'] = 'test.ipynb'
335 359 model = cm.update(model, path)
336 360 assert isinstance(model, dict)
337 361 self.assertIn('name', model)
338 362 self.assertIn('path', model)
339 363 self.assertEqual(model['name'], 'test.ipynb')
340 364
341 365 # Make sure the old name is gone
342 366 self.assertRaises(HTTPError, cm.get, path)
343 367
344 368 # Test in sub-directory
345 369 # Create a directory and notebook in that directory
346 370 sub_dir = '/foo/'
347 371 self.make_dir('foo')
348 372 model = cm.new_untitled(path=sub_dir, type='notebook')
349 373 path = model['path']
350 374
351 375 # Change the name in the model for rename
352 376 d = path.rsplit('/', 1)[0]
353 377 new_path = model['path'] = d + '/test_in_sub.ipynb'
354 378 model = cm.update(model, path)
355 379 assert isinstance(model, dict)
356 380 self.assertIn('name', model)
357 381 self.assertIn('path', model)
358 382 self.assertEqual(model['name'], 'test_in_sub.ipynb')
359 383 self.assertEqual(model['path'], new_path)
360 384
361 385 # Make sure the old name is gone
362 386 self.assertRaises(HTTPError, cm.get, path)
363 387
364 388 def test_save(self):
365 389 cm = self.contents_manager
366 390 # Create a notebook
367 391 model = cm.new_untitled(type='notebook')
368 392 name = model['name']
369 393 path = model['path']
370 394
371 395 # Get the model with 'content'
372 396 full_model = cm.get(path)
373 397
374 398 # Save the notebook
375 399 model = cm.save(full_model, path)
376 400 assert isinstance(model, dict)
377 401 self.assertIn('name', model)
378 402 self.assertIn('path', model)
379 403 self.assertEqual(model['name'], name)
380 404 self.assertEqual(model['path'], path)
381 405
382 406 # Test in sub-directory
383 407 # Create a directory and notebook in that directory
384 408 sub_dir = '/foo/'
385 409 self.make_dir('foo')
386 410 model = cm.new_untitled(path=sub_dir, type='notebook')
387 411 name = model['name']
388 412 path = model['path']
389 413 model = cm.get(path)
390 414
391 415 # Change the name in the model for rename
392 416 model = cm.save(model, path)
393 417 assert isinstance(model, dict)
394 418 self.assertIn('name', model)
395 419 self.assertIn('path', model)
396 420 self.assertEqual(model['name'], 'Untitled.ipynb')
397 421 self.assertEqual(model['path'], 'foo/Untitled.ipynb')
398 422
399 423 def test_delete(self):
400 424 cm = self.contents_manager
401 425 # Create a notebook
402 426 nb, name, path = self.new_notebook()
403 427
404 428 # Delete the notebook
405 429 cm.delete(path)
406 430
407 431 # Check that deleting a non-existent path raises an error.
408 432 self.assertRaises(HTTPError, cm.delete, path)
409 433
410 434 # Check that a 'get' on the deleted notebook raises and error
411 435 self.assertRaises(HTTPError, cm.get, path)
412 436
413 437 def test_copy(self):
414 438 cm = self.contents_manager
415 439 parent = u'Γ₯ b'
416 440 name = u'nb √.ipynb'
417 441 path = u'{0}/{1}'.format(parent, name)
418 442 self.make_dir(parent)
419 443
420 444 orig = cm.new(path=path)
421 445 # copy with unspecified name
422 446 copy = cm.copy(path)
423 447 self.assertEqual(copy['name'], orig['name'].replace('.ipynb', '-Copy1.ipynb'))
424 448
425 449 # copy with specified name
426 450 copy2 = cm.copy(path, u'Γ₯ b/copy 2.ipynb')
427 451 self.assertEqual(copy2['name'], u'copy 2.ipynb')
428 452 self.assertEqual(copy2['path'], u'Γ₯ b/copy 2.ipynb')
429 453 # copy with specified path
430 454 copy2 = cm.copy(path, u'/')
431 455 self.assertEqual(copy2['name'], name)
432 456 self.assertEqual(copy2['path'], name)
433 457
434 458 def test_trust_notebook(self):
435 459 cm = self.contents_manager
436 460 nb, name, path = self.new_notebook()
437 461
438 462 untrusted = cm.get(path)['content']
439 463 assert not cm.notary.check_cells(untrusted)
440 464
441 465 # print(untrusted)
442 466 cm.trust_notebook(path)
443 467 trusted = cm.get(path)['content']
444 468 # print(trusted)
445 469 assert cm.notary.check_cells(trusted)
446 470
447 471 def test_mark_trusted_cells(self):
448 472 cm = self.contents_manager
449 473 nb, name, path = self.new_notebook()
450 474
451 475 cm.mark_trusted_cells(nb, path)
452 476 for cell in nb.cells:
453 477 if cell.cell_type == 'code':
454 478 assert not cell.metadata.trusted
455 479
456 480 cm.trust_notebook(path)
457 481 nb = cm.get(path)['content']
458 482 for cell in nb.cells:
459 483 if cell.cell_type == 'code':
460 484 assert cell.metadata.trusted
461 485
462 486 def test_check_and_sign(self):
463 487 cm = self.contents_manager
464 488 nb, name, path = self.new_notebook()
465 489
466 490 cm.mark_trusted_cells(nb, path)
467 491 cm.check_and_sign(nb, path)
468 492 assert not cm.notary.check_signature(nb)
469 493
470 494 cm.trust_notebook(path)
471 495 nb = cm.get(path)['content']
472 496 cm.mark_trusted_cells(nb, path)
473 497 cm.check_and_sign(nb, path)
474 498 assert cm.notary.check_signature(nb)
475
476 def test_escape_root(self):
477 cm = self.contents_manager
478 # make foo, bar next to root
479 with open(os.path.join(cm.root_dir, '..', 'foo'), 'w') as f:
480 f.write('foo')
481 with open(os.path.join(cm.root_dir, '..', 'bar'), 'w') as f:
482 f.write('bar')
483
484 with self.assertRaisesHTTPError(404):
485 cm.get('..')
486 with self.assertRaisesHTTPError(404):
487 cm.get('foo/../../../bar')
488 with self.assertRaisesHTTPError(404):
489 cm.delete('../foo')
490 with self.assertRaisesHTTPError(404):
491 cm.rename('../foo', '../bar')
492 with self.assertRaisesHTTPError(404):
493 cm.save(model={
494 'type': 'file',
495 'content': u'',
496 'format': 'text',
497 }, path='../foo')
498
499
500
General Comments 0
You need to be logged in to leave comments. Login now