@@ -130,6 +130,39 @@ def check_origin(self, origin=None):
130130 async def open (self , port , proxied_path ):
131131 raise NotImplementedError ("Subclasses of ProxyHandler should implement open" )
132132
133+ async def prepare (self , * args , ** kwargs ):
134+ """
135+ Enforce authentication on *all* requests.
136+
137+ This method is called *before* any other method for all requests.
138+ See https://www.tornadoweb.org/en/stable/web.html#tornado.web.RequestHandler.prepare.
139+ """
140+ # Due to https://github.com/jupyter-server/jupyter_server/issues/1012,
141+ # we can not decorate `prepare` with `@web.authenticated`.
142+ # `super().prepare`, which calls `JupyterHandler.prepare`, *must* be called
143+ # before `@web.authenticated` can work. Since `@web.authenticated` is a decorator
144+ # that relies on the decorated method to get access to request information, we can
145+ # not call it directly. Instead, we create an empty lambda that takes a request_handler,
146+ # decorate that with web.authenticated, and call the decorated function.
147+ # super().prepare became async with jupyter_server v2
148+ _prepared = super ().prepare (* args , ** kwargs )
149+ if _prepared is not None :
150+ await _prepared
151+
152+ # If this is a GET request that wants to be upgraded to a websocket, users not
153+ # already authenticated gets a straightforward 403. Everything else is dealt
154+ # with by `web.authenticated`, which does a 302 to the appropriate login url.
155+ # Websockets are purely API calls made by JS rather than a direct user facing page,
156+ # so redirects do not make sense for them.
157+ if (
158+ self .request .method == "GET"
159+ and self .request .headers .get ("Upgrade" , "" ).lower () == "websocket"
160+ ):
161+ if not self .current_user :
162+ raise web .HTTPError (403 )
163+ else :
164+ web .authenticated (lambda request_handler : None )(self )
165+
133166 async def http_get (self , host , port , proxy_path = "" ):
134167 """Our non-websocket GET."""
135168 raise NotImplementedError (
@@ -280,7 +313,6 @@ def _check_host_allowlist(self, host):
280313 else :
281314 return host in self .host_allowlist
282315
283- @web .authenticated
284316 async def proxy (self , host , port , proxied_path ):
285317 """
286318 This serverextension handles:
@@ -682,7 +714,6 @@ def _realize_rendered_template(self, attribute):
682714 attribute = call_with_asked_args (attribute , self .process_args )
683715 return self ._render_template (attribute )
684716
685- @web .authenticated
686717 async def proxy (self , port , path ):
687718 if not path .startswith ("/" ):
688719 path = "/" + path
@@ -866,7 +897,6 @@ async def ensure_process(self):
866897 del self .state ["proc" ]
867898 raise
868899
869- @web .authenticated
870900 async def proxy (self , port , path ):
871901 await self .ensure_process ()
872902 return await ensure_async (super ().proxy (port , path ))
0 commit comments