From 9cd45f150c31ac4e84ac4099666cd102b4089843 Mon Sep 17 00:00:00 2001 From: Madicken Munk Date: Fri, 20 Jul 2018 16:52:21 -0500 Subject: [PATCH 1/4] add pytest to test env requirements and suggest it in contributing docs --- CONTRIBUTING.rst | 4 ++-- setup.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 0e8057a0a3..13dcfd81db 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -65,11 +65,11 @@ Install dependencies:: To run the Python tests, use:: - nosetests + pytest If you want coverage statistics as well, you can run:: - nosetests --with-coverage --cover-package=jupyter_server jupyter_server + py.test --cov notebook -v --pyargs jupyter_server Building the Documentation -------------------------- diff --git a/setup.py b/setup.py index d460f520c6..a04ec1ef82 100755 --- a/setup.py +++ b/setup.py @@ -92,7 +92,7 @@ ], extras_require = { 'test': ['nose', 'coverage', 'requests', 'nose_warnings_filters', - 'nbval', 'nose-exclude', 'selenium'], + 'nbval', 'nose-exclude', 'selenium', 'pytest', 'pytest-cov'], 'test:sys_platform == "win32"': ['nose-exclude'], }, python_requires='>=3.5', From 2ddb1d2a75cd789ccb1de9d31855c2cf6d78bf96 Mon Sep 17 00:00:00 2001 From: "Aaron Hall, MBA" Date: Thu, 26 Jul 2018 13:51:58 -0400 Subject: [PATCH 2/4] Fill in Checkpoints section (I think it needs more complete examples like ContentsManager has regarding what is returned by these methods.) --- docs/source/extending/contents.rst | 52 +++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/docs/source/extending/contents.rst b/docs/source/extending/contents.rst index 5216d67c53..bf741a42dc 100644 --- a/docs/source/extending/contents.rst +++ b/docs/source/extending/contents.rst @@ -191,12 +191,62 @@ methods: ContentsManager.dir_exists ContentsManager.is_hidden +You may be required to specify a Checkpoints object, as the default one, +``FileCheckpoints``, could be incompatible with your custom +ContentsManager. Customizing Checkpoints ----------------------- +.. currentmodule:: notebook.services.contents.checkpoints + +Customized Checkpoint definitions allows behavior to be +altered and extended. + +The ``Checkpoints`` and ``GenericCheckpointsMixin`` classes +(from :mod:`notebook.services.contents.checkpoints`) +have reusable code and are intended to be used together, +but require the following methods to be implemented. + +.. autosummary:: + Checkpoints.rename_checkpoint + Checkpoints.list_checkpoints + Checkpoints.delete_checkpoint + GenericCheckpointsMixin.create_file_checkpoint + GenericCheckpointsMixin.create_notebook_checkpoint + GenericCheckpointsMixin.get_file_checkpoint + GenericCheckpointsMixin.get_notebook_checkpoint + +No-op example +~~~~~~~~~~~~~ -TODO: +Here is an example of a no-op checkpoints object - note the mixin +comes first. The docstrings indicate what each method should do or +return for a more complete implementation. + +.. code-block:: python +class NoOpCheckpoints(GenericCheckpointsMixin, Checkpoints): + """requires the following methods:""" + def create_file_checkpoint(self, content, format, path): + """ -> checkpoint model""" + def create_notebook_checkpoint(self, nb, path): + """ -> checkpoint model""" + def get_file_checkpoint(self, checkpoint_id, path): + """ -> {'type': 'file', 'content': , 'format': {'text', 'base64'}}""" + def get_notebook_checkpoint(self, checkpoint_id, path): + """ -> {'type': 'notebook', 'content': }""" + def delete_checkpoint(self, checkpoint_id, path): + """deletes a checkpoint for a file""" + def list_checkpoints(self, path): + """returns a list of checkpoint models for a given file, + default just does one per file + """ + return [] + def rename_checkpoint(self, checkpoint_id, old_path, new_path): + """renames checkpoint from old path to new path""" + +See ``GenericFileCheckpoints`` in :mod:`notebook.services.contents.filecheckpoints` +for a more complete example. Testing ------- From 87835e3b6c2359d6eb16f7dba27f70734510b433 Mon Sep 17 00:00:00 2001 From: "Aaron Hall, MBA" Date: Mon, 30 Jul 2018 12:49:34 -0400 Subject: [PATCH 3/4] indent code block --- docs/source/extending/contents.rst | 38 +++++++++++++++--------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/docs/source/extending/contents.rst b/docs/source/extending/contents.rst index bf741a42dc..61575dd92d 100644 --- a/docs/source/extending/contents.rst +++ b/docs/source/extending/contents.rst @@ -225,25 +225,25 @@ return for a more complete implementation. .. code-block:: python -class NoOpCheckpoints(GenericCheckpointsMixin, Checkpoints): - """requires the following methods:""" - def create_file_checkpoint(self, content, format, path): - """ -> checkpoint model""" - def create_notebook_checkpoint(self, nb, path): - """ -> checkpoint model""" - def get_file_checkpoint(self, checkpoint_id, path): - """ -> {'type': 'file', 'content': , 'format': {'text', 'base64'}}""" - def get_notebook_checkpoint(self, checkpoint_id, path): - """ -> {'type': 'notebook', 'content': }""" - def delete_checkpoint(self, checkpoint_id, path): - """deletes a checkpoint for a file""" - def list_checkpoints(self, path): - """returns a list of checkpoint models for a given file, - default just does one per file - """ - return [] - def rename_checkpoint(self, checkpoint_id, old_path, new_path): - """renames checkpoint from old path to new path""" + class NoOpCheckpoints(GenericCheckpointsMixin, Checkpoints): + """requires the following methods:""" + def create_file_checkpoint(self, content, format, path): + """ -> checkpoint model""" + def create_notebook_checkpoint(self, nb, path): + """ -> checkpoint model""" + def get_file_checkpoint(self, checkpoint_id, path): + """ -> {'type': 'file', 'content': , 'format': {'text', 'base64'}}""" + def get_notebook_checkpoint(self, checkpoint_id, path): + """ -> {'type': 'notebook', 'content': }""" + def delete_checkpoint(self, checkpoint_id, path): + """deletes a checkpoint for a file""" + def list_checkpoints(self, path): + """returns a list of checkpoint models for a given file, + default just does one per file + """ + return [] + def rename_checkpoint(self, checkpoint_id, old_path, new_path): + """renames checkpoint from old path to new path""" See ``GenericFileCheckpoints`` in :mod:`notebook.services.contents.filecheckpoints` for a more complete example. From 53bbb0a8f36eb1a55d0fc7306983c3e9ff919644 Mon Sep 17 00:00:00 2001 From: Michael Boyle Date: Mon, 27 Aug 2018 12:37:57 -0400 Subject: [PATCH 4/4] Describe problems and solutions involving CSP headers --- docs/source/public_server.rst | 36 +++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/docs/source/public_server.rst b/docs/source/public_server.rst index 9fc36da110..904a09312f 100644 --- a/docs/source/public_server.rst +++ b/docs/source/public_server.rst @@ -358,6 +358,42 @@ For example, in Firefox, go to the Preferences panel, Advanced section, Network tab, click 'Settings...', and add the address of the Jupyter server to the 'No proxy for' field. +Content-Security-Policy (CSP) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Certain `security guidelines +`_ +recommend that servers use a Content-Security-Policy (CSP) header to prevent +cross-site scripting vulnerabilities, specifically limiting to ``default-src: +https:`` when possible. This directive causes two problems with Jupyter. +First, it disables execution of inline javascript code, which is used +extensively by Jupyter. Second, it limits communication to the https scheme, +and prevents WebSockets from working because they communicate via the wss +scheme (or ws for insecure communication). Jupyter uses WebSockets for +interacting with kernels, so when you visit a server with such a CSP, your +browser will block attempts to use wss, which will cause you to see +"Connection failed" messages from jupyter notebooks, or simply no response +from jupyter terminals. By looking in your browser's javascript console, you +can see any error messages that will explain what is failing. + +To avoid these problem, you need to add ``'unsafe-inline'`` and ``connect-src +https: wss:`` to your CSP header, at least for pages served by jupyter. (That +is, you can leave your CSP unchanged for other parts of your website.) Note +that multiple CSP headers are allowed, but successive CSP headers can only +restrict the policy; they cannot loosen it. For example, if your server sends +both of these headers + + Content-Security-Policy "default-src https: 'unsafe-inline'" + Content-Security-Policy "connect-src https: wss:" + +the first policy will already eliminate wss connections, so the second has no +effect. Therefore, you can't simply add the second header; you have to +actually modify your CSP header to look more like this: + + Content-Security-Policy "default-src https: 'unsafe-inline'; connect-src https: wss:" + + + Docker CMD ~~~~~~~~~~