diff --git a/jupyterhub/user.py b/jupyterhub/user.py index c23796a1..e467db99 100644 --- a/jupyterhub/user.py +++ b/jupyterhub/user.py @@ -8,7 +8,9 @@ import warnings from sqlalchemy import inspect from tornado import gen +from tornado.httputil import urlencode from tornado.log import app_log +from tornado import web from .utils import maybe_future, url_path_join, make_ssl_context @@ -381,6 +383,59 @@ class User: url_parts.extend(['server/progress']) return url_path_join(*url_parts) + async def refresh_auth(self, handler): + """Refresh authentication if needed + + Checks authentication expiry and refresh it if needed. + See Spawner. + + If the auth is expired and cannot be refreshed + without forcing a new login, a few things can happen: + + 1. if this is a normal user spawn, + the user should be redirected to login + and back to spawn after login. + 2. if this is a spawn via API or other user, + spawn will fail until the user logs in again. + + Args: + handler (RequestHandler): + The handler for the request triggering the spawn. + May be None + """ + authenticator = self.authenticator + if authenticator is None or not authenticator.refresh_pre_spawn: + # nothing to do + return + + # refresh auth + auth_user = await handler.refresh_auth(self, force=True) + + if auth_user: + # auth refreshed, all done + return + + # if we got to here, auth is expired and couldn't be refreshed + self.log.error( + "Auth expired for %s; cannot spawn until they login again", + self.name, + ) + # auth expired, cannot spawn without a fresh login + # it's the current user *and* spawn via GET, trigger login redirect + if handler.request.method == 'GET' and handler.current_user is self: + self.log.info("Redirecting %s to login to refresh auth", self.name) + url = self.get_login_url() + next_url = self.request.uri + sep = '&' if '?' in url else '?' + url += sep + urlencode(dict(next=next_url)) + self.redirect(url) + raise web.Finish() + else: + # spawn via POST or on behalf of another user. + # nothing we can do here but fail + raise web.HTTPError(400, "{}'s authentication has expired".format(self.name)) + + async def spawn(self, server_name='', options=None, handler=None): """Start the user's spawner @@ -396,10 +451,8 @@ class User: """ db = self.db - authenticator = self.authenticator - if authenticator and handler and authenticator.pre_spawn_start: - auth_user = await handler.refresh_pre_spawn(self, force=True) - + if handler: + await self.refresh_auth(handler) base_url = url_path_join(self.base_url, server_name) + '/' @@ -441,7 +494,8 @@ class User: db.commit() # trigger pre-spawn hook on authenticator - if (authenticator): + authenticator = self.authenticator + if authenticator: await maybe_future(authenticator.pre_spawn_start(self, spawner)) spawner._start_pending = True