diff --git a/jupyterhub/services/auth.py b/jupyterhub/services/auth.py index 34ffd0a0..476ac936 100644 --- a/jupyterhub/services/auth.py +++ b/jupyterhub/services/auth.py @@ -30,7 +30,10 @@ from tornado.httputil import url_concat from tornado.web import HTTPError, RequestHandler from traitlets.config import SingletonConfigurable -from traitlets import Unicode, Integer, Instance, default, observe, validate +from traitlets import ( + Unicode, Integer, Instance, Dict, + default, observe, validate, +) from ..utils import url_path_join @@ -175,7 +178,7 @@ class HubAuth(SingletonConfigurable): hub_prefix = Unicode('/hub/', help="""The URL prefix for the Hub itself. - + Typically /hub/ """ ).tag(config=True) @@ -185,7 +188,7 @@ class HubAuth(SingletonConfigurable): login_url = Unicode('/hub/login', help="""The login URL to use - + Typically /hub/login """ ).tag(config=True) @@ -197,6 +200,24 @@ class HubAuth(SingletonConfigurable): help="""The name of the cookie I should be looking for""" ).tag(config=True) + cookie_options = Dict( + help="""Additional options to pass when setting cookies. + + Can include things like `expires_days=None` for session-expiry + or `secure=True` if served on HTTPS and default HTTPS discovery fails + (e.g. behind some proxies). + """ + ).tag(config=True) + + @default('cookie_options') + def _default_cookie_options(self): + # load default from env + options_env = os.environ.get('JUPYTERHUB_COOKIE_OPTIONS') + if options_env: + return json.loads(options_env) + else: + return {} + cookie_cache_max_age = Integer(help="DEPRECATED. Use cache_max_age") @observe('cookie_cache_max_age') def _deprecated_cookie_cache(self, change): @@ -580,6 +601,8 @@ class HubOAuth(HubAuth): } if handler.request.protocol == 'https': kwargs['secure'] = True + # load user cookie overrides + kwargs.update(self.cookie_options) handler.set_secure_cookie( cookie_name, b64_state, @@ -627,6 +650,8 @@ class HubOAuth(HubAuth): } if handler.request.protocol == 'https': kwargs['secure'] = True + # load user cookie overrides + kwargs.update(self.cookie_options) app_log.debug("Setting oauth cookie for %s: %s, %s", handler.request.remote_ip, self.cookie_name, kwargs) handler.set_secure_cookie( diff --git a/jupyterhub/services/service.py b/jupyterhub/services/service.py index ae14c672..1200e1be 100644 --- a/jupyterhub/services/service.py +++ b/jupyterhub/services/service.py @@ -218,6 +218,7 @@ class Service(LoggingConfigurable): base_url = Unicode() db = Any() orm = Any() + cookie_options = Dict() oauth_provider = Any() @@ -299,6 +300,7 @@ class Service(LoggingConfigurable): environment=env, api_token=self.api_token, oauth_client_id=self.oauth_client_id, + cookie_options=self.cookie_options, cwd=self.cwd, hub=self.hub, user=_MockUser( diff --git a/jupyterhub/spawner.py b/jupyterhub/spawner.py index 2086bc3f..35b625ba 100644 --- a/jupyterhub/spawner.py +++ b/jupyterhub/spawner.py @@ -6,6 +6,7 @@ Contains base Spawner class & default implementation # Distributed under the terms of the Modified BSD License. import errno +import json import os import pipes import shutil @@ -99,11 +100,12 @@ class Spawner(LoggingConfigurable): """ return bool(self.pending or self.ready) - + # options passed by constructor authenticator = Any() hub = Any() orm_spawner = Any() db = Any() + cookie_options = Dict() @observe('orm_spawner') def _orm_spawner_changed(self, change): @@ -125,7 +127,7 @@ class Spawner(LoggingConfigurable): if missing: raise NotImplementedError("class `{}` needs to redefine the `start`," "`stop` and `poll` methods. `{}` not redefined.".format(cls.__name__, '`, `'.join(missing))) - + proxy_spec = Unicode() @property @@ -587,6 +589,8 @@ class Spawner(LoggingConfigurable): env['JUPYTERHUB_ADMIN_ACCESS'] = '1' # OAuth settings env['JUPYTERHUB_CLIENT_ID'] = self.oauth_client_id + if self.cookie_options: + env['JUPYTERHUB_COOKIE_OPTIONS'] = json.dumps(self.cookie_options) env['JUPYTERHUB_HOST'] = self.hub.public_host env['JUPYTERHUB_OAUTH_CALLBACK_URL'] = \ url_path_join(self.user.url, self.name, 'oauth_callback') diff --git a/jupyterhub/user.py b/jupyterhub/user.py index 6041ba9f..980131e1 100644 --- a/jupyterhub/user.py +++ b/jupyterhub/user.py @@ -215,6 +215,7 @@ class User: proxy_spec=url_path_join(self.proxy_spec, name, '/'), db=self.db, oauth_client_id=client_id, + cookie_options = self.settings.get('cookie_options', {}), ) # update with kwargs. Mainly for testing. spawn_kwargs.update(kwargs)