From 6f2e409fb9bb4959310f26f2316bc4d4d228acc0 Mon Sep 17 00:00:00 2001 From: Thijs Walcarius Date: Mon, 6 Apr 2020 16:26:16 +0200 Subject: [PATCH] Allow bypassing of spawn form by calling options in query arguments of /spawn --- jupyterhub/handlers/pages.py | 26 ++++++++++++++++++++++++++ jupyterhub/tests/test_pages.py | 22 ++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/jupyterhub/handlers/pages.py b/jupyterhub/handlers/pages.py index 5cd1f4a7..6f1a43f2 100644 --- a/jupyterhub/handlers/pages.py +++ b/jupyterhub/handlers/pages.py @@ -173,6 +173,32 @@ class SpawnHandler(BaseHandler): auth_state = await user.get_auth_state() await spawner.run_auth_state_hook(auth_state) + # Try to start server directly when query arguments are passed. + form_options = {} + for key, byte_list in self.request.query_arguments.items(): + form_options[key] = [bs.decode('utf8') for bs in byte_list] + + # 'next' is reserved argument for redirect after spawn + form_options.pop('next', None) + + if len(form_options) > 0: + try: + self.log.debug( + "Triggering spawn with supplied query arguments for %s", + spawner._log_name, + ) + options = await maybe_future(spawner.options_from_form(form_options)) + pending_url = self._get_pending_url(user, server_name) + return await self._wrap_spawn_single_user( + user, server_name, spawner, pending_url, options + ) + except Exception as e: + self.log.error( + "Failed to spawn single-user server with query arguments", + exc_info=True, + ) + # fallback to behavior without failing query arguments + spawner_options_form = await spawner.get_options_form() if spawner_options_form: self.log.debug("Serving options form for %s", spawner._log_name) diff --git a/jupyterhub/tests/test_pages.py b/jupyterhub/tests/test_pages.py index 20f3ea45..7ca4cb22 100644 --- a/jupyterhub/tests/test_pages.py +++ b/jupyterhub/tests/test_pages.py @@ -255,6 +255,28 @@ async def test_spawn_page_admin(app, admin_access): assert "Spawning server for {}".format(u.name) in r.text +async def test_spawn_with_query_arguments(app): + with mock.patch.dict(app.users.settings, {'spawner_class': FormSpawner}): + base_url = ujoin(public_host(app), app.hub.base_url) + cookies = await app.login_user('jones') + orm_u = orm.User.find(app.db, 'jones') + u = app.users[orm_u] + await u.stop() + next_url = ujoin(app.base_url, 'user/jones/tree') + r = await async_requests.get( + url_concat( + ujoin(base_url, 'spawn'), {'next': next_url, 'energy': '510keV'}, + ), + cookies=cookies, + ) + r.raise_for_status() + assert r.history + assert u.spawner.user_options == { + 'energy': '510keV', + 'notspecified': 5, + } + + async def test_spawn_form(app): with mock.patch.dict(app.users.settings, {'spawner_class': FormSpawner}): base_url = ujoin(public_host(app), app.hub.base_url)