mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-18 15:33:02 +00:00
allow auth to expire
adds Authenticator.auth_refresh_age and Authenticator.refresh_pre_spawn config - auth_refresh_age allows auth to expire (default: 5 minutes) before calling Authenticator.refresh_user. - refresh_pre_spawn forces refresh prior to spawn (in case of auth tokens, etc.)
This commit is contained in:
@@ -19,7 +19,7 @@ except Exception as e:
|
|||||||
from tornado.concurrent import run_on_executor
|
from tornado.concurrent import run_on_executor
|
||||||
|
|
||||||
from traitlets.config import LoggingConfigurable
|
from traitlets.config import LoggingConfigurable
|
||||||
from traitlets import Bool, Set, Unicode, Dict, Any, default, observe
|
from traitlets import Bool, Integer, Set, Unicode, Dict, Any, default, observe
|
||||||
|
|
||||||
from .handlers.login import LoginHandler
|
from .handlers.login import LoginHandler
|
||||||
from .utils import maybe_future, url_path_join
|
from .utils import maybe_future, url_path_join
|
||||||
@@ -50,6 +50,35 @@ class Authenticator(LoggingConfigurable):
|
|||||||
""",
|
""",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
auth_refresh_age = Integer(
|
||||||
|
300,
|
||||||
|
config=True,
|
||||||
|
help="""The max age (in seconds) of authentication info
|
||||||
|
before forcing a refresh of user auth info.
|
||||||
|
|
||||||
|
Refreshing auth info allows, e.g. requesting/re-validating auth tokens.
|
||||||
|
|
||||||
|
See :meth:`.refresh_user` for what happens when user auth info is refreshed
|
||||||
|
(nothing by default).
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
refresh_pre_spawn = Bool(
|
||||||
|
False,
|
||||||
|
config=True,
|
||||||
|
help="""Force refresh of auth prior to spawn.
|
||||||
|
|
||||||
|
This forces :meth:`.refresh_user` to be called prior to launching
|
||||||
|
a server, to ensure that auth state is up-to-date.
|
||||||
|
|
||||||
|
This can be important when e.g. auth tokens that may have expired
|
||||||
|
are passed to the spawner via environment variables from auth_state.
|
||||||
|
|
||||||
|
If refresh_user cannot refresh the user auth data,
|
||||||
|
launch will fail until the user logs in again.
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
admin_users = Set(
|
admin_users = Set(
|
||||||
help="""
|
help="""
|
||||||
Set of users that will have admin rights on this JupyterHub.
|
Set of users that will have admin rights on this JupyterHub.
|
||||||
|
@@ -250,8 +250,14 @@ class BaseHandler(RequestHandler):
|
|||||||
user (User): the user having been refreshed,
|
user (User): the user having been refreshed,
|
||||||
or None if the user must login again to refresh auth info.
|
or None if the user must login again to refresh auth info.
|
||||||
"""
|
"""
|
||||||
if not force: # TODO: and it's sufficiently recent
|
refresh_age = self.settings.get('auth_refresh_age', 0)
|
||||||
|
if not refresh_age:
|
||||||
return user
|
return user
|
||||||
|
now = time.monotonic()
|
||||||
|
if not force and user._auth_refreshed and (now - user._auth_refreshed < refresh_age):
|
||||||
|
# auth up-to-date
|
||||||
|
return user
|
||||||
|
user._auth_refreshed = now
|
||||||
|
|
||||||
# refresh a user at most once per request
|
# refresh a user at most once per request
|
||||||
if not hasattr(self, '_refreshed_users'):
|
if not hasattr(self, '_refreshed_users'):
|
||||||
@@ -639,6 +645,11 @@ class BaseHandler(RequestHandler):
|
|||||||
|
|
||||||
async def spawn_single_user(self, user, server_name='', options=None):
|
async def spawn_single_user(self, user, server_name='', options=None):
|
||||||
# in case of error, include 'try again from /hub/home' message
|
# in case of error, include 'try again from /hub/home' message
|
||||||
|
if self.authenticator.refresh_pre_spawn:
|
||||||
|
auth_user = await self.refresh_user(user, force=True)
|
||||||
|
if auth_user is None:
|
||||||
|
raise web.HTTPError(403, "auth has expired for %s, login again", auth_user.name)
|
||||||
|
|
||||||
spawn_start_time = time.perf_counter()
|
spawn_start_time = time.perf_counter()
|
||||||
self.extra_error_html = self.spawn_home_error
|
self.extra_error_html = self.spawn_home_error
|
||||||
|
|
||||||
|
@@ -136,6 +136,7 @@ class User:
|
|||||||
orm_user = None
|
orm_user = None
|
||||||
log = app_log
|
log = app_log
|
||||||
settings = None
|
settings = None
|
||||||
|
auth_refreshed = None
|
||||||
|
|
||||||
def __init__(self, orm_user, settings=None, db=None):
|
def __init__(self, orm_user, settings=None, db=None):
|
||||||
self.db = db or inspect(orm_user).session
|
self.db = db or inspect(orm_user).session
|
||||||
@@ -395,6 +396,11 @@ class User:
|
|||||||
"""
|
"""
|
||||||
db = self.db
|
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)
|
||||||
|
|
||||||
|
|
||||||
base_url = url_path_join(self.base_url, server_name) + '/'
|
base_url = url_path_join(self.base_url, server_name) + '/'
|
||||||
|
|
||||||
orm_server = orm.Server(
|
orm_server = orm.Server(
|
||||||
@@ -435,7 +441,6 @@ class User:
|
|||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
# trigger pre-spawn hook on authenticator
|
# trigger pre-spawn hook on authenticator
|
||||||
authenticator = self.authenticator
|
|
||||||
if (authenticator):
|
if (authenticator):
|
||||||
await maybe_future(authenticator.pre_spawn_start(self, spawner))
|
await maybe_future(authenticator.pre_spawn_start(self, spawner))
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user