diff --git a/jupyterhub/apihandlers/auth.py b/jupyterhub/apihandlers/auth.py index cf1eba14..15a4d783 100644 --- a/jupyterhub/apihandlers/auth.py +++ b/jupyterhub/apihandlers/auth.py @@ -223,7 +223,7 @@ class OAuthAuthorizeHandler(OAuthHandler, BaseHandler): return True @web.authenticated - def get(self): + async def get(self): """GET /oauth/authorization Render oauth confirmation page: @@ -251,8 +251,14 @@ class OAuthAuthorizeHandler(OAuthHandler, BaseHandler): return # Render oauth 'Authorize application...' page + auth_state = await self.current_user.get_auth_state() self.write( - self.render_template("oauth.html", scopes=scopes, oauth_client=client) + self.render_template( + "oauth.html", + auth_state=auth_state, + scopes=scopes, + oauth_client=client, + ) ) # Errors that should be shown to the user on the provider website diff --git a/jupyterhub/handlers/base.py b/jupyterhub/handlers/base.py index 4669a49c..77c2ae27 100644 --- a/jupyterhub/handlers/base.py +++ b/jupyterhub/handlers/base.py @@ -1431,8 +1431,13 @@ class UserUrlHandler(BaseHandler): url_path_join(self.hub.base_url, "spawn", user.escaped_name, server_name), {"next": self.request.uri}, ) + auth_state = await user.get_auth_state() html = self.render_template( - "not_running.html", user=user, server_name=server_name, spawn_url=spawn_url + "not_running.html", + user=user, + server_name=server_name, + spawn_url=spawn_url, + auth_state=auth_state, ) self.finish(html) diff --git a/jupyterhub/handlers/pages.py b/jupyterhub/handlers/pages.py index 734c257d..3ca03653 100644 --- a/jupyterhub/handlers/pages.py +++ b/jupyterhub/handlers/pages.py @@ -68,10 +68,9 @@ class HomeHandler(BaseHandler): url = url_path_join(self.hub.base_url, 'spawn', user.escaped_name) auth_state = await user.get_auth_state() - user.spawner.run_auth_state_hook(auth_state) - html = self.render_template( 'home.html', + auth_state=auth_state, user=user, url=url, allow_named_servers=self.allow_named_servers, @@ -94,10 +93,12 @@ class SpawnHandler(BaseHandler): default_url = None - def _render_form(self, for_user, spawner_options_form, message=''): + async def _render_form(self, for_user, spawner_options_form, message=''): + auth_state = await for_user.get_auth_state() return self.render_template( 'spawn.html', for_user=for_user, + auth_state=auth_state, spawner_options_form=spawner_options_form, error_message=message, url=self.request.uri, @@ -149,6 +150,7 @@ class SpawnHandler(BaseHandler): server_name = '' spawner = user.spawners[server_name] + # resolve `?next=...`, falling back on the spawn-pending url # must not be /user/server for named servers, # which may get handled by the default server if they aren't ready yet @@ -175,10 +177,16 @@ class SpawnHandler(BaseHandler): # Add handler to spawner here so you can access query params in form rendering. spawner.handler = self + + # auth_state may be an input to options form, + # so resolve the auth state hook here + auth_state = await user.get_auth_state() + await spawner.run_auth_state_hook(auth_state) + spawner_options_form = await spawner.get_options_form() if spawner_options_form: self.log.debug("Serving options form for %s", spawner._log_name) - form = self._render_form( + form = await self._render_form( for_user=user, spawner_options_form=spawner_options_form ) self.finish(form) @@ -240,7 +248,7 @@ class SpawnHandler(BaseHandler): "Failed to spawn single-user server with form", exc_info=True ) spawner_options_form = await user.spawner.get_options_form() - form = self._render_form( + form = await self._render_form( for_user=user, spawner_options_form=spawner_options_form, message=str(e) ) self.finish(form) @@ -301,6 +309,8 @@ class SpawnPendingHandler(BaseHandler): # if spawning fails for any reason, point users to /hub/home to retry self.extra_error_html = self.spawn_home_error + auth_state = await user.get_auth_state() + # First, check for previous failure. if ( not spawner.active @@ -320,6 +330,7 @@ class SpawnPendingHandler(BaseHandler): html = self.render_template( "not_running.html", user=user, + auth_state=auth_state, server_name=server_name, spawn_url=spawn_url, failed=True, @@ -341,7 +352,11 @@ class SpawnPendingHandler(BaseHandler): else: page = "spawn_pending.html" html = self.render_template( - page, user=user, spawner=spawner, progress_url=spawner._progress_url + page, + user=user, + spawner=spawner, + progress_url=spawner._progress_url, + auth_state=auth_state, ) self.finish(html) return @@ -366,6 +381,7 @@ class SpawnPendingHandler(BaseHandler): html = self.render_template( "not_running.html", user=user, + auth_state=auth_state, server_name=server_name, spawn_url=spawn_url, ) @@ -385,7 +401,7 @@ class AdminHandler(BaseHandler): @web.authenticated @admin_only - def get(self): + async def get(self): available = {'name', 'admin', 'running', 'last_activity'} default_sort = ['admin', 'name'] mapping = {'running': orm.Spawner.server_id} @@ -434,9 +450,11 @@ class AdminHandler(BaseHandler): for u in users: running.extend(s for s in u.spawners.values() if s.active) + auth_state = await self.current_user.get_auth_state() html = self.render_template( 'admin.html', current_user=self.current_user, + auth_state=auth_state, admin_access=self.settings.get('admin_access', False), users=users, running=running, @@ -452,7 +470,7 @@ class TokenPageHandler(BaseHandler): """Handler for page requesting new API tokens""" @web.authenticated - def get(self): + async def get(self): never = datetime(1900, 1, 1) user = self.current_user @@ -521,8 +539,12 @@ class TokenPageHandler(BaseHandler): oauth_clients = sorted(oauth_clients, key=sort_key, reverse=True) + auth_state = await self.current_user.get_auth_state() html = self.render_template( - 'token.html', api_tokens=api_tokens, oauth_clients=oauth_clients + 'token.html', + api_tokens=api_tokens, + oauth_clients=oauth_clients, + auth_state=auth_state, ) self.finish(html) diff --git a/jupyterhub/spawner.py b/jupyterhub/spawner.py index 431544c6..4abc355b 100644 --- a/jupyterhub/spawner.py +++ b/jupyterhub/spawner.py @@ -974,11 +974,11 @@ class Spawner(LoggingConfigurable): except Exception: self.log.exception("post_stop_hook failed with exception: %s", self) - def run_auth_state_hook(self, auth_state): + async def run_auth_state_hook(self, auth_state): """Run the auth_state_hook if defined""" if self.auth_state_hook is not None: try: - return self.auth_state_hook(self, auth_state) + await maybe_future(self.auth_state_hook(self, auth_state)) except Exception: self.log.exception("auth_stop_hook failed with exception: %s", self) diff --git a/jupyterhub/user.py b/jupyterhub/user.py index 4a3d6596..71ceac4e 100644 --- a/jupyterhub/user.py +++ b/jupyterhub/user.py @@ -530,12 +530,17 @@ class User: # trigger pre-spawn hook on authenticator authenticator = self.authenticator try: + spawner._start_pending = True + if authenticator: # pre_spawn_start can thow errors that can lead to a redirect loop # if left uncaught (see https://github.com/jupyterhub/jupyterhub/issues/2683) await maybe_future(authenticator.pre_spawn_start(self, spawner)) - spawner._start_pending = True + # trigger auth_state hook + auth_state = await self.get_auth_state() + await spawner.run_auth_state_hook(auth_state) + # update spawner start time, and activity for both spawner and user self.last_activity = ( spawner.orm_spawner.started