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:
Min RK
2018-12-03 14:09:03 +01:00
parent b6d8db5259
commit 563106c0d2
3 changed files with 48 additions and 3 deletions

View File

@@ -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.

View File

@@ -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

View File

@@ -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))