diff --git a/jupyterhub/auth.py b/jupyterhub/auth.py index 6408a84b..16e0c9ac 100644 --- a/jupyterhub/auth.py +++ b/jupyterhub/auth.py @@ -58,6 +58,18 @@ class Authenticator(LoggingConfigurable): and return None on failed authentication. """ + def pre_spawn_start(self, user, spawner): + """Hook called before spawning a user's server. + + Can be used to do auth-related startup, e.g. opening PAM sessions. + """ + + def post_spawn_stop(self, user, spawner): + """Hook called after stopping a user container. + + Can be used to do auth-related cleanup, e.g. closing PAM sessions. + """ + def check_whitelist(self, user): """ Return True if the whitelist is empty or user is in the whitelist. @@ -210,9 +222,22 @@ class PAMAuthenticator(LocalAuthenticator): return try: pamela.authenticate(username, data['password'], service=self.service) - pamela.open_session(username, service=self.service) except pamela.PAMError as e: self.log.warn("PAM Authentication failed: %s", e) else: return username + def pre_spawn_start(self, user, spawner): + """Open PAM session for user""" + try: + pamela.open_session(user.name, service=self.service) + except pamela.PAMError as e: + self.log.warn("Failed to open PAM session for %s: %s", user.name, e) + + def post_spawn_stop(self, user, spawner): + """Close PAM session for user""" + try: + pamela.close_session(user.name, service=self.service) + except pamela.PAMError as e: + self.log.warn("Failed to close PAM session for %s: %s", user.name, e) + diff --git a/jupyterhub/orm.py b/jupyterhub/orm.py index 5a5a848b..141857a6 100644 --- a/jupyterhub/orm.py +++ b/jupyterhub/orm.py @@ -363,6 +363,9 @@ class User(Base): spawner.clear_state() spawner.api_token = api_token + # trigger pre-spawn hook on authenticator + if (authenticator): + yield gen.maybe_future(authenticator.pre_spawn_start(self, spawner)) self.spawn_pending = True # wait for spawner.start to return try: @@ -429,21 +432,27 @@ class User(Base): and cleanup after it. """ self.spawn_pending = False - if self.spawner is None: + spawner = self.spawner + if spawner is None: return - self.spawner.stop_polling() + spawner.stop_polling() self.stop_pending = True try: - status = yield self.spawner.poll() + status = yield spawner.poll() if status is None: yield self.spawner.stop() - self.spawner.clear_state() - self.state = self.spawner.get_state() + spawner.clear_state() + self.state = spawner.get_state() self.server = None inspect(self).session.commit() finally: self.stop_pending = False - + # trigger post-spawner hook on authenticator + auth = spawner.authenticator + if auth: + yield gen.maybe_future( + auth.post_spawn_stop(self, spawner) + ) class APIToken(Base): """An API token"""