mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-07 18:14:10 +00:00
169 lines
5.8 KiB
Python
169 lines
5.8 KiB
Python
"""HTTP Handlers for the hub server"""
|
|
# Copyright (c) Jupyter Development Team.
|
|
# Distributed under the terms of the Modified BSD License.
|
|
import asyncio
|
|
|
|
from jinja2 import Template
|
|
from tornado import web
|
|
from tornado.escape import url_escape
|
|
from tornado.httputil import url_concat
|
|
|
|
from ..utils import maybe_future
|
|
from .base import BaseHandler
|
|
|
|
|
|
class LogoutHandler(BaseHandler):
|
|
"""Log a user out by clearing their login cookie."""
|
|
|
|
@property
|
|
def shutdown_on_logout(self):
|
|
return self.settings.get('shutdown_on_logout', False)
|
|
|
|
async def _shutdown_servers(self, user):
|
|
"""Shutdown servers for logout
|
|
|
|
Get all active servers for the provided user, stop them.
|
|
"""
|
|
active_servers = [
|
|
name
|
|
for (name, spawner) in user.spawners.items()
|
|
if spawner.active and not spawner.pending
|
|
]
|
|
if active_servers:
|
|
self.log.info("Shutting down %s's servers", user.name)
|
|
futures = []
|
|
for server_name in active_servers:
|
|
futures.append(maybe_future(self.stop_single_user(user, server_name)))
|
|
await asyncio.gather(*futures)
|
|
|
|
def _backend_logout_cleanup(self, name):
|
|
"""Default backend logout actions
|
|
|
|
Send a log message, clear some cookies, increment the logout counter.
|
|
"""
|
|
self.log.info("User logged out: %s", name)
|
|
self.clear_login_cookie()
|
|
self.statsd.incr('logout')
|
|
|
|
async def default_handle_logout(self):
|
|
"""The default logout action
|
|
|
|
Optionally cleans up servers, clears cookies, increments logout counter
|
|
Cleaning up servers can be prevented by setting shutdown_on_logout to
|
|
False.
|
|
"""
|
|
user = self.current_user
|
|
if user:
|
|
if self.shutdown_on_logout:
|
|
await self._shutdown_servers(user)
|
|
|
|
self._backend_logout_cleanup(user.name)
|
|
|
|
async def handle_logout(self):
|
|
"""Custom user action during logout
|
|
|
|
By default a no-op, this function should be overridden in subclasses
|
|
to have JupyterHub take a custom action on logout.
|
|
"""
|
|
return
|
|
|
|
async def render_logout_page(self):
|
|
"""Render the logout page, if any
|
|
|
|
Override this function to set a custom logout page.
|
|
"""
|
|
if self.authenticator.auto_login:
|
|
html = await self.render_template('logout.html')
|
|
self.finish(html)
|
|
else:
|
|
self.redirect(self.settings['login_url'], permanent=False)
|
|
|
|
async def get(self):
|
|
"""Log the user out, call the custom action, forward the user
|
|
to the logout page
|
|
"""
|
|
await self.default_handle_logout()
|
|
await self.handle_logout()
|
|
await self.render_logout_page()
|
|
|
|
|
|
class LoginHandler(BaseHandler):
|
|
"""Render the login page."""
|
|
|
|
def _render(self, login_error=None, username=None):
|
|
context = {
|
|
"next": url_escape(self.get_argument('next', default='')),
|
|
"username": username,
|
|
"login_error": login_error,
|
|
"login_url": self.settings['login_url'],
|
|
"authenticator_login_url": url_concat(
|
|
self.authenticator.login_url(self.hub.base_url),
|
|
{'next': self.get_argument('next', '')},
|
|
),
|
|
}
|
|
custom_html = Template(
|
|
self.authenticator.get_custom_html(self.hub.base_url)
|
|
).render(**context)
|
|
return self.render_template(
|
|
'login.html',
|
|
**context,
|
|
custom_html=custom_html,
|
|
)
|
|
|
|
async def get(self):
|
|
self.statsd.incr('login.request')
|
|
user = self.current_user
|
|
if user:
|
|
# set new login cookie
|
|
# because single-user cookie may have been cleared or incorrect
|
|
self.set_login_cookie(user)
|
|
self.redirect(self.get_next_url(user), permanent=False)
|
|
else:
|
|
if self.authenticator.auto_login:
|
|
auto_login_url = self.authenticator.login_url(self.hub.base_url)
|
|
if auto_login_url == self.settings['login_url']:
|
|
# auto_login without a custom login handler
|
|
# means that auth info is already in the request
|
|
# (e.g. REMOTE_USER header)
|
|
user = await self.login_user()
|
|
if user is None:
|
|
# auto_login failed, just 403
|
|
raise web.HTTPError(403)
|
|
else:
|
|
self.redirect(self.get_next_url(user))
|
|
else:
|
|
if self.get_argument('next', default=False):
|
|
auto_login_url = url_concat(
|
|
auto_login_url, {'next': self.get_next_url()}
|
|
)
|
|
self.redirect(auto_login_url)
|
|
return
|
|
username = self.get_argument('username', default='')
|
|
self.finish(await self._render(username=username))
|
|
|
|
async def post(self):
|
|
# parse the arguments dict
|
|
data = {}
|
|
for arg in self.request.arguments:
|
|
data[arg] = self.get_argument(arg, strip=False)
|
|
|
|
auth_timer = self.statsd.timer('login.authenticate').start()
|
|
user = await self.login_user(data)
|
|
auth_timer.stop(send=False)
|
|
|
|
if user:
|
|
# register current user for subsequent requests to user (e.g. logging the request)
|
|
self._jupyterhub_user = user
|
|
self.redirect(self.get_next_url(user))
|
|
else:
|
|
html = await self._render(
|
|
login_error='Invalid username or password', username=data['username']
|
|
)
|
|
self.finish(html)
|
|
|
|
|
|
# /login renders the login page or the "Login with..." link,
|
|
# so it should always be registered.
|
|
# /logout clears cookies.
|
|
default_handlers = [(r"/login", LoginHandler), (r"/logout", LogoutHandler)]
|