diff --git a/jupyterhub/handlers/base.py b/jupyterhub/handlers/base.py index b7977ea4..cb037dde 100644 --- a/jupyterhub/handlers/base.py +++ b/jupyterhub/handlers/base.py @@ -46,35 +46,35 @@ class BaseHandler(RequestHandler): @property def base_url(self): return self.settings.get('base_url', '/') - + @property def version_hash(self): return self.settings.get('version_hash', '') - + @property def subdomain_host(self): return self.settings.get('subdomain_host', '') - + @property def domain(self): return self.settings['domain'] - + @property def db(self): return self.settings['db'] - + @property def users(self): return self.settings.setdefault('users', {}) - + @property def hub(self): return self.settings['hub'] - + @property def proxy(self): return self.settings['proxy'] - + @property def authenticator(self): return self.settings.get('authenticator', None) @@ -83,28 +83,28 @@ class BaseHandler(RequestHandler): """Roll back any uncommitted transactions from the handler.""" self.db.rollback() super().finish(*args, **kwargs) - + #--------------------------------------------------------------- # Security policies #--------------------------------------------------------------- - + @property def csp_report_uri(self): return self.settings.get('csp_report_uri', url_path_join(self.hub.server.base_url, 'security/csp-report') ) - + @property def content_security_policy(self): """The default Content-Security-Policy header - + Can be overridden by defining Content-Security-Policy in settings['headers'] """ return '; '.join([ "frame-ancestors 'self'", "report-uri " + self.csp_report_uri, ]) - + def set_default_headers(self): """ Set any headers passed as tornado_settings['headers']. @@ -113,7 +113,7 @@ class BaseHandler(RequestHandler): """ headers = self.settings.get('headers', {}) headers.setdefault("Content-Security-Policy", self.content_security_policy) - + for header_name, header_content in headers.items(): self.set_header(header_name, header_content) @@ -124,7 +124,7 @@ class BaseHandler(RequestHandler): @property def admin_users(self): return self.settings.setdefault('admin_users', set()) - + @property def cookie_max_age_days(self): return self.settings.get('cookie_max_age_days', None) @@ -141,7 +141,7 @@ class BaseHandler(RequestHandler): return None else: return orm_token.user - + def _user_for_cookie(self, cookie_name, cookie_value=None): """Get the User for a given cookie, if there is one""" cookie_id = self.get_secure_cookie( @@ -151,7 +151,7 @@ class BaseHandler(RequestHandler): ) def clear(): self.clear_cookie(cookie_name, path=self.hub.server.base_url) - + if cookie_id is None: if self.get_cookie(cookie_name): self.log.warn("Invalid or expired cookie token") @@ -165,27 +165,27 @@ class BaseHandler(RequestHandler): # have cookie, but it's not valid. Clear it and start over. clear() return user - + def _user_from_orm(self, orm_user): """return User wrapper from orm.User object""" if orm_user is None: return return self.users[orm_user] - + def get_current_user_cookie(self): """get_current_user from a cookie token""" return self._user_for_cookie(self.hub.server.cookie_name) - + def get_current_user(self): """get current username""" user = self.get_current_user_token() if user is not None: return user return self.get_current_user_cookie() - + def find_user(self, name): """Get a user by name - + return None if no such user """ orm_user = orm.User.find(db=self.db, name=name) @@ -201,7 +201,7 @@ class BaseHandler(RequestHandler): self.db.commit() user = self._user_from_orm(u) return user - + def clear_login_cookie(self, name=None): if name is None: user = self.get_current_user() @@ -213,7 +213,7 @@ class BaseHandler(RequestHandler): if user and user.server: self.clear_cookie(user.server.cookie_name, path=user.server.base_url, **kwargs) self.clear_cookie(self.hub.server.cookie_name, path=self.hub.server.base_url, **kwargs) - + def _set_user_cookie(self, user, server): # tornado <4.2 have a bug that consider secure==True as soon as # 'secure' kwarg is passed to set_secure_cookie @@ -230,15 +230,15 @@ class BaseHandler(RequestHandler): path=server.base_url, **kwargs ) - + def set_server_cookie(self, user): """set the login cookie for the single-user server""" self._set_user_cookie(user, user.server) - + def set_hub_cookie(self, user): """set the login cookie for the Hub""" self._set_user_cookie(user, self.hub.server) - + def set_login_cookie(self, user): """Set login cookies for the Hub and single-user server.""" if self.subdomain_host and not self.request.host.startswith(self.domain): @@ -248,11 +248,11 @@ class BaseHandler(RequestHandler): # create and set a new cookie token for the single-user server if user.server: self.set_server_cookie(user) - + # create and set a new cookie token for the hub if not self.get_current_user_cookie(): self.set_hub_cookie(user) - + @gen.coroutine def authenticate(self, data): auth = self.authenticator @@ -278,7 +278,7 @@ class BaseHandler(RequestHandler): @property def spawner_class(self): return self.settings.get('spawner_class', LocalProcessSpawner) - + @gen.coroutine def spawn_single_user(self, user, options=None): if user.spawn_pending: @@ -290,7 +290,7 @@ class BaseHandler(RequestHandler): @gen.coroutine def finish_user_spawn(f=None): """Finish the user spawn by registering listeners and notifying the proxy. - + If the spawner is slow to start, this is passed as an async callback, otherwise it is called immediately. """ @@ -301,7 +301,7 @@ class BaseHandler(RequestHandler): self.log.info("User %s server took %.3f seconds to start", user.name, toc-tic) yield self.proxy.add_user(user) user.spawner.add_poll_callback(self.user_stopped, user) - + try: yield gen.with_timeout(timedelta(seconds=self.slow_spawn_timeout), f) except gen.TimeoutError: @@ -325,7 +325,7 @@ class BaseHandler(RequestHandler): raise web.HTTPError(500, "Spawner failed to start [status=%s]" % status) else: yield finish_user_spawn() - + @gen.coroutine def user_stopped(self, user): """Callback that fires when the spawner has stopped""" @@ -337,7 +337,7 @@ class BaseHandler(RequestHandler): ) yield self.proxy.delete_user(user) yield user.stop() - + @gen.coroutine def stop_single_user(self, user): if user.stop_pending: @@ -348,7 +348,7 @@ class BaseHandler(RequestHandler): @gen.coroutine def finish_stop(f=None): """Finish the stop action by noticing that the user is stopped. - + If the spawner is slow to stop, this is passed as an async callback, otherwise it is called immediately. """ @@ -357,7 +357,7 @@ class BaseHandler(RequestHandler): return toc = IOLoop.current().time() self.log.info("User %s server took %.3f seconds to stop", user.name, toc-tic) - + try: yield gen.with_timeout(timedelta(seconds=self.slow_stop_timeout), f) except gen.TimeoutError: @@ -443,7 +443,7 @@ class Template404(BaseHandler): class PrefixRedirectHandler(BaseHandler): """Redirect anything outside a prefix inside. - + Redirects /foo to /prefix/foo, etc. """ def get(self): diff --git a/jupyterhub/handlers/pages.py b/jupyterhub/handlers/pages.py index e82490c4..4ad6913a 100644 --- a/jupyterhub/handlers/pages.py +++ b/jupyterhub/handlers/pages.py @@ -8,17 +8,17 @@ from tornado import web, gen from .. import orm from ..utils import admin_only, url_path_join from .base import BaseHandler -from .login import LoginHandler +from urllib.parse import quote class RootHandler(BaseHandler): """Render the Hub root page. - + If logged in, redirects to: - + - single-user server if running - hub home, otherwise - + Otherwise, renders login page. """ def get(self): @@ -49,9 +49,9 @@ class HomeHandler(BaseHandler): class SpawnHandler(BaseHandler): """Handle spawning of single-user servers via form. - + GET renders the form, POST handles form submission. - + Only enabled when Spawner.options_form is defined. """ def _render_form(self, message=''): @@ -75,9 +75,11 @@ class SpawnHandler(BaseHandler): self.finish(self._render_form()) else: # not running, no form. Trigger spawn. - url = url_path_join(self.base_url, 'user', user.name) + # Creating the URL manually since the server does not + # exist yet + url = url_path_join(self.base_url, 'user', quote(user.name)) self.redirect(url) - + @web.authenticated @gen.coroutine def post(self): @@ -122,14 +124,14 @@ class AdminHandler(BaseHandler): } sorts = self.get_arguments('sort') or default_sort orders = self.get_arguments('order') - + for bad in set(sorts).difference(available): self.log.warn("ignoring invalid sort: %r", bad) sorts.remove(bad) for bad in set(orders).difference({'asc', 'desc'}): self.log.warn("ignoring invalid order: %r", bad) orders.remove(bad) - + # add default sort as secondary for s in default_sort: if s not in sorts: @@ -139,17 +141,17 @@ class AdminHandler(BaseHandler): orders.append(default_order[col]) else: orders = orders[:len(sorts)] - + # this could be one incomprehensible nested list comprehension # get User columns cols = [ getattr(orm.User, mapping.get(c, c)) for c in sorts ] # get User.col.desc() order objects ordered = [ getattr(c, o)() for c, o in zip(cols, orders) ] - + users = self.db.query(orm.User).order_by(*ordered) users = [ self._user_from_orm(u) for u in users ] running = [ u for u in users if u.running ] - + html = self.render_template('admin.html', user=self.get_current_user(), admin_access=self.settings.get('admin_access', False), diff --git a/share/jupyter/hub/templates/home.html b/share/jupyter/hub/templates/home.html index 77813b67..1a7f9ab7 100644 --- a/share/jupyter/hub/templates/home.html +++ b/share/jupyter/hub/templates/home.html @@ -10,7 +10,7 @@ {% endif %}