From 960b65813aebeb310a542f8feed01635d37ccef3 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Fri, 23 Feb 2024 14:04:26 +0100 Subject: [PATCH 1/3] Add __version__ and declare version to be 3.2.3 --- jupyter_server_proxy/__init__.py | 2 ++ jupyterlab-server-proxy/package.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/jupyter_server_proxy/__init__.py b/jupyter_server_proxy/__init__.py index f93bf865..88f50f50 100644 --- a/jupyter_server_proxy/__init__.py +++ b/jupyter_server_proxy/__init__.py @@ -3,6 +3,8 @@ from jupyter_server.utils import url_path_join as ujoin from .api import ServersInfoHandler, IconHandler +__version__ = "3.2.3" + # Jupyter Extension points def _jupyter_server_extension_points(): return [{ diff --git a/jupyterlab-server-proxy/package.json b/jupyterlab-server-proxy/package.json index 466a89e3..133113f2 100644 --- a/jupyterlab-server-proxy/package.json +++ b/jupyterlab-server-proxy/package.json @@ -1,6 +1,6 @@ { "name": "@jupyterlab/server-proxy", - "version": "3.2.2", + "version": "3.2.3", "description": "Jupyter server extension to supervise and proxy web services", "keywords": [ "jupyter", From 4f407173e29b8262b64227ef5ea8a548759e3641 Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Fri, 23 Feb 2024 16:11:14 +0100 Subject: [PATCH 2/3] Check authentication when websockets open --- jupyter_server_proxy/handlers.py | 35 +++++++++++++++++++++++++-- setup.py | 1 + tests/test_proxies.py | 41 ++++++++++++++------------------ 3 files changed, 52 insertions(+), 25 deletions(-) diff --git a/jupyter_server_proxy/handlers.py b/jupyter_server_proxy/handlers.py index 88d448de..874e77e0 100644 --- a/jupyter_server_proxy/handlers.py +++ b/jupyter_server_proxy/handlers.py @@ -124,6 +124,39 @@ def check_origin(self, origin=None): async def open(self, port, proxied_path): raise NotImplementedError('Subclasses of ProxyHandler should implement open') + async def prepare(self, *args, **kwargs): + """ + Enforce authentication on *all* requests. + + This method is called *before* any other method for all requests. + See https://www.tornadoweb.org/en/stable/web.html#tornado.web.RequestHandler.prepare. + """ + # Due to https://github.com/jupyter-server/jupyter_server/issues/1012, + # we can not decorate `prepare` with `@web.authenticated`. + # `super().prepare`, which calls `JupyterHandler.prepare`, *must* be called + # before `@web.authenticated` can work. Since `@web.authenticated` is a decorator + # that relies on the decorated method to get access to request information, we can + # not call it directly. Instead, we create an empty lambda that takes a request_handler, + # decorate that with web.authenticated, and call the decorated function. + # super().prepare became async with jupyter_server v2 + _prepared = super().prepare(*args, **kwargs) + if _prepared is not None: + await _prepared + + # If this is a GET request that wants to be upgraded to a websocket, users not + # already authenticated gets a straightforward 403. Everything else is dealt + # with by `web.authenticated`, which does a 302 to the appropriate login url. + # Websockets are purely API calls made by JS rather than a direct user facing page, + # so redirects do not make sense for them. + if ( + self.request.method == "GET" + and self.request.headers.get("Upgrade", "").lower() == "websocket" + ): + if not self.current_user: + raise web.HTTPError(403) + else: + web.authenticated(lambda request_handler: None)(self) + async def http_get(self, host, port, proxy_path=''): '''Our non-websocket GET.''' raise NotImplementedError('Subclasses of ProxyHandler should implement http_get') @@ -265,7 +298,6 @@ def _check_host_allowlist(self, host): else: return host in self.host_allowlist - @web.authenticated async def proxy(self, host, port, proxied_path): ''' This serverextension handles: @@ -664,7 +696,6 @@ async def ensure_process(self): raise - @web.authenticated async def proxy(self, port, path): if not path.startswith('/'): path = '/' + path diff --git a/setup.py b/setup.py index 901b7eab..769d52a9 100644 --- a/setup.py +++ b/setup.py @@ -93,6 +93,7 @@ # acceptance tests additionally require firefox and geckodriver "test": [ "pytest", + "pytest-asyncio", "pytest-cov", "pytest-html" ], diff --git a/tests/test_proxies.py b/tests/test_proxies.py index 7d780551..c0605398 100644 --- a/tests/test_proxies.py +++ b/tests/test_proxies.py @@ -6,6 +6,7 @@ from http.client import HTTPConnection from urllib.parse import quote import pytest +from tornado.httpclient import HTTPClientError from tornado.websocket import websocket_connect PORT = os.getenv('TEST_PORT', 8888) @@ -246,15 +247,9 @@ def test_server_content_encoding_header(): assert f.read() == b'this is a test' -@pytest.fixture(scope="module") -def event_loop(): - loop = asyncio.get_event_loop() - yield loop - loop.close() - - -async def _websocket_echo(): - url = "ws://localhost:{}/python-websocket/echosocket".format(PORT) +@pytest.mark.asyncio +async def test_server_proxy_websocket_messages(): + url = "ws://localhost:{}/python-websocket/echosocket?token={}".format(PORT, TOKEN) conn = await websocket_connect(url) expected_msg = "Hello, world!" await conn.write_message(expected_msg) @@ -262,12 +257,9 @@ async def _websocket_echo(): assert msg == expected_msg -def test_server_proxy_websocket(event_loop): - event_loop.run_until_complete(_websocket_echo()) - - -async def _websocket_headers(): - url = "ws://localhost:{}/python-websocket/headerssocket".format(PORT) +@pytest.mark.asyncio +async def test_server_proxy_websocket_headers(): + url = "ws://localhost:{}/python-websocket/headerssocket?token={}".format(PORT, TOKEN) conn = await websocket_connect(url) await conn.write_message("Hello") msg = await conn.read_message() @@ -276,20 +268,23 @@ async def _websocket_headers(): assert headers['X-Custom-Header'] == 'pytest-23456' -def test_server_proxy_websocket_headers(event_loop): - event_loop.run_until_complete(_websocket_headers()) - - -async def _websocket_subprotocols(): - url = "ws://localhost:{}/python-websocket/subprotocolsocket".format(PORT) +@pytest.mark.asyncio +async def test_server_proxy_websocket_subprotocols(): + url = "ws://localhost:{}/python-websocket/subprotocolsocket?token={}".format(PORT, TOKEN) conn = await websocket_connect(url, subprotocols=["protocol_1", "protocol_2"]) await conn.write_message("Hello, world!") msg = await conn.read_message() assert json.loads(msg) == ["protocol_1", "protocol_2"] -def test_server_proxy_websocket_subprotocols(event_loop): - event_loop.run_until_complete(_websocket_subprotocols()) +@pytest.mark.asyncio +async def test_websocket_no_auth_failure(): + # Intentionally do not pass an appropriate token, which should cause a 403 + url = "ws://localhost:{}/python-websocket/headerssocket".format(PORT) + + with pytest.raises(HTTPClientError, match=r".*HTTP 403: Forbidden.*"): + await websocket_connect(url) + @pytest.mark.parametrize( "proxy_path, status", From 836ac5c97f455d6c0c94685b60bf301325b1d515 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Fri, 23 Feb 2024 16:32:59 +0100 Subject: [PATCH 3/3] Retroactively add changelog for 3.2.2 --- CHANGELOG.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 974b21ca..06ee7198 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,37 @@ ## 3.2 +### 3.2.2 - 2022-09-08 + +#### Bugs fixed + +- add allow-downloads and allow-modals to sandbox [#335](https://github.com/jupyterhub/jupyter-server-proxy/pull/335) ([@djangoliv](https://github.com/djangoliv)) +- allow empty PUT body [#331](https://github.com/jupyterhub/jupyter-server-proxy/pull/331) ([@pepijndevos](https://github.com/pepijndevos)) +- [bugfix] Hop by hop header handling [#328](https://github.com/jupyterhub/jupyter-server-proxy/pull/328) ([@mahnerak](https://github.com/mahnerak)) + +#### Documentation improvements + +- Yarn link malformed. [#320](https://github.com/jupyterhub/jupyter-server-proxy/pull/320) ([@matthew-brett](https://github.com/matthew-brett)) + +#### Continuous integration improvements + +- Install `notebook<7` for notebook test [#340](https://github.com/jupyterhub/jupyter-server-proxy/pull/340) ([@manics](https://github.com/manics)) +- Run publish workflow for tags [#318](https://github.com/jupyterhub/jupyter-server-proxy/pull/318) ([@manics](https://github.com/manics)) + +#### Dependency updates + +- Bump terser from 5.10.0 to 5.14.2 in /jupyterlab-server-proxy [#342](https://github.com/jupyterhub/jupyter-server-proxy/pull/342) ([@dependabot](https://github.com/dependabot)) +- Bump moment from 2.29.2 to 2.29.4 in /jupyterlab-server-proxy [#341](https://github.com/jupyterhub/jupyter-server-proxy/pull/341) ([@dependabot](https://github.com/dependabot)) +- Bump moment from 2.29.1 to 2.29.2 in /jupyterlab-server-proxy [#336](https://github.com/jupyterhub/jupyter-server-proxy/pull/336) ([@dependabot](https://github.com/dependabot)) +- Bump minimist from 1.2.5 to 1.2.6 in /jupyterlab-server-proxy [#334](https://github.com/jupyterhub/jupyter-server-proxy/pull/334) ([@dependabot](https://github.com/dependabot)) +- Bump url-parse from 1.5.3 to 1.5.7 in /jupyterlab-server-proxy [#327](https://github.com/jupyterhub/jupyter-server-proxy/pull/327) ([@dependabot](https://github.com/dependabot)) + +#### Contributors to this release + +([GitHub contributors page for this release](https://github.com/jupyterhub/jupyter-server-proxy/graphs/contributors?from=2022-01-24&to=2022-09-08&type=c)) + +[@austinmw](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyter-server-proxy+involves%3Aaustinmw+updated%3A2022-01-24..2022-09-08&type=Issues) | [@bollwyvl](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyter-server-proxy+involves%3Abollwyvl+updated%3A2022-01-24..2022-09-08&type=Issues) | [@consideRatio](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyter-server-proxy+involves%3AconsideRatio+updated%3A2022-01-24..2022-09-08&type=Issues) | [@dependabot](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyter-server-proxy+involves%3Adependabot+updated%3A2022-01-24..2022-09-08&type=Issues) | [@djangoliv](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyter-server-proxy+involves%3Adjangoliv+updated%3A2022-01-24..2022-09-08&type=Issues) | [@jhgoebbert](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyter-server-proxy+involves%3Ajhgoebbert+updated%3A2022-01-24..2022-09-08&type=Issues) | [@mahnerak](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyter-server-proxy+involves%3Amahnerak+updated%3A2022-01-24..2022-09-08&type=Issues) | [@manics](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyter-server-proxy+involves%3Amanics+updated%3A2022-01-24..2022-09-08&type=Issues) | [@matthew-brett](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyter-server-proxy+involves%3Amatthew-brett+updated%3A2022-01-24..2022-09-08&type=Issues) | [@meeseeksmachine](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyter-server-proxy+involves%3Ameeseeksmachine+updated%3A2022-01-24..2022-09-08&type=Issues) | [@pepijndevos](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyter-server-proxy+involves%3Apepijndevos+updated%3A2022-01-24..2022-09-08&type=Issues) | [@ryanlovett](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyter-server-proxy+involves%3Aryanlovett+updated%3A2022-01-24..2022-09-08&type=Issues) | [@ryshoooo](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyter-server-proxy+involves%3Aryshoooo+updated%3A2022-01-24..2022-09-08&type=Issues) | [@takluyver](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyter-server-proxy+involves%3Atakluyver+updated%3A2022-01-24..2022-09-08&type=Issues) | [@yuvipanda](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyter-server-proxy+involves%3Ayuvipanda+updated%3A2022-01-24..2022-09-08&type=Issues) + + ### 3.2.1 - 2022-01-24 3.2.1 is a security release, fixing a vulnerability [GHSA-gcv9-6737-pjqw](https://github.com/jupyterhub/jupyter-server-proxy/security/advisories/GHSA-gcv9-6737-pjqw) where `allowed_hosts` were not validated correctly.