Compare commits

...

21 Commits
1.4.2 ... 1.5.0

Author SHA1 Message Date
Min RK
bf73e6f7b7 fix 1.5.0 link in changelog 2021-11-04 13:56:40 +01:00
Min RK
e2631b302a import commands from setuptools
importing build_py from distutils breaks in setuptools 58
2021-11-04 13:55:41 +01:00
Min RK
0d89241c9f release 1.5.0 2021-11-04 13:22:53 +01:00
Min RK
5ac9e7f73a Merge branch 'fix-set-cookie' into 1.4.x
Prepare to release 1.5.0

Fixes GHSA-cw7p-q79f-m2v7
2021-11-04 13:21:52 +01:00
Min RK
9672b534ec changelog for 1.5.0 2021-11-03 16:16:48 +01:00
Min RK
254365716d jupyterlab: don't use $JUPYTERHUB_API_TOKEN in PageConfig.token 2021-11-03 15:52:00 +01:00
Min RK
dcac8c4efe Revert "store tokens passed via url or header, not only url."
This reverts commit 53c3201c17.

Only tokens in URLs should be persisted in cookies.
Tokens in headers should not have any effect on cookies.
2021-11-03 15:52:00 +01:00
Min RK
0611169dea Merge pull request #3677 from minrk/doc-requirements-1x
1.4.x: update doc requirements
2021-11-02 10:38:16 +01:00
Min RK
a432fa3bb6 Merge pull request #3676 from manics/1-use_legacy_stopped_server_status_code
use_legacy_stopped_server_status_code: use 1.* language
2021-11-02 10:35:09 +01:00
Min RK
44141ae025 1.4.x: update doc requirements
pin down docutils, unpin autodoc-traits
2021-11-02 10:35:04 +01:00
Simon Li
04ae25d2c2 use_legacy_stopped_server_status_code: use 1.* language
Also fixes the JupyterHub 2.0 default: will be False not True
2021-11-01 22:14:59 +00:00
Min RK
69a1e97fbe Merge pull request #3639 from yuvipanda/404-1.4.x
Backport #3636 to 1.4.x
2021-10-06 16:08:00 +02:00
YuviPanda
eb0c6514af Set use_legacy_stopped_server_status_code to True for 1.4.x 2021-10-06 17:25:10 +05:30
YuviPanda
d03fc8c531 Update tests that were looking for 503s 2021-10-05 20:19:59 +05:30
YuviPanda
1c8dce533b Preserve older 503 behavior behind a flag 2021-10-05 20:19:59 +05:30
YuviPanda
bbfbc47bb3 Use 424 rather than 404 to indicate non-running server
404 is also used to identify that a particular resource
(like a kernel or terminal) is not present, maybe because
it is deleted. That comes from the notebook server, while
here we are responding from JupyterHub. Saying that the
user server they are trying to request the resource (kernel, etc)
from does not exist seems right.
2021-10-05 20:19:59 +05:30
YuviPanda
be6ec28dab Fail suspected API requests with 404, not 503
Non-running user servers making requests is a fairly
common occurance - user servers get culled while their
browser tabs are left open. So we now have a background level
of 503s responses on the hub *all* the time, making it
very difficult to detect *real* 503s, which should ideally
be closely monitored and alerted on.

I *think* 404 is a more appropriate response, as the resource
(API) being requested is no longer present.
2021-10-05 20:19:59 +05:30
Min RK
bd3a215c9e Merge pull request #3580 from meeseeksmachine/auto-backport-of-pr-3552-on-1.4.x
Backport PR #3552 on branch 1.4.x (Add expiration date dropdown to Token page)
2021-08-23 12:08:56 +02:00
Min RK
3783a1bc6c Backport PR #3552: Add expiration date dropdown to Token page 2021-08-23 09:42:15 +00:00
Min RK
7b0f29b340 Merge pull request #3579 from meeseeksmachine/auto-backport-of-pr-3488-on-1.4.x
Backport PR #3488 on branch 1.4.x (Support auto login when used as a OAuth2 provider)
2021-08-23 10:32:15 +02:00
Min RK
f63e810dfe Backport PR #3488: Support auto login when used as a OAuth2 provider 2021-08-20 08:30:04 +00:00
14 changed files with 165 additions and 29 deletions

View File

@@ -1,9 +1,8 @@
-r ../requirements.txt -r ../requirements.txt
alabaster_jupyterhub alabaster_jupyterhub
# Temporary fix of #3021. Revert back to released autodoc-traits when autodoc-traits
# 0.1.0 released. docutils<0.18
https://github.com/jupyterhub/autodoc-traits/archive/d22282c1c18c6865436e06d8b329c06fe12a07f8.zip
pydata-sphinx-theme pydata-sphinx-theme
pytablewriter>=0.56 pytablewriter>=0.56
recommonmark>=0.6 recommonmark>=0.6

View File

@@ -3,7 +3,7 @@ swagger: "2.0"
info: info:
title: JupyterHub title: JupyterHub
description: The REST API for JupyterHub description: The REST API for JupyterHub
version: 1.4.0 version: 1.5.0
license: license:
name: BSD-3-Clause name: BSD-3-Clause
schemes: [http, https] schemes: [http, https]

View File

@@ -6,6 +6,41 @@ command line for details.
## [Unreleased] ## [Unreleased]
## 1.5
JupyterHub 1.5 is a **security release**,
fixing a vulnerability [ghsa-cw7p-q79f-m2v7][] where JupyterLab users
with multiple tabs open could fail to logout completely,
leaving their browser with valid credentials until they logout again.
A few fully backward-compatible features have been backported from 2.0.
[ghsa-cw7p-q79f-m2v7]: https://github.com/jupyterhub/jupyterhub/security/advisories/GHSA-cw7p-q79f-m2v7
### [1.5.0] 2021-11-04
([full changelog](https://github.com/jupyterhub/jupyterhub/compare/1.4.2...1.5.0))
#### New features added
- Backport #3636 to 1.4.x (opt-in support for JupyterHub.use_legacy_stopped_server_status_code) [#3639](https://github.com/jupyterhub/jupyterhub/pull/3639) ([@yuvipanda](https://github.com/yuvipanda))
- Backport PR #3552 on branch 1.4.x (Add expiration date dropdown to Token page) [#3580](https://github.com/jupyterhub/jupyterhub/pull/3580) ([@meeseeksmachine](https://github.com/meeseeksmachine))
- Backport PR #3488 on branch 1.4.x (Support auto login when used as a OAuth2 provider) [#3579](https://github.com/jupyterhub/jupyterhub/pull/3579) ([@meeseeksmachine](https://github.com/meeseeksmachine))
#### Maintenance and upkeep improvements
- 1.4.x: update doc requirements [#3677](https://github.com/jupyterhub/jupyterhub/pull/3677) ([@minrk](https://github.com/minrk))
#### Documentation improvements
- use_legacy_stopped_server_status_code: use 1.\* language [#3676](https://github.com/jupyterhub/jupyterhub/pull/3676) ([@manics](https://github.com/manics))
#### Contributors to this release
([GitHub contributors page for this release](https://github.com/jupyterhub/jupyterhub/graphs/contributors?from=2021-07-16&to=2021-11-03&type=c))
[@choldgraf](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Acholdgraf+updated%3A2021-07-16..2021-11-03&type=Issues) | [@consideRatio](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3AconsideRatio+updated%3A2021-07-16..2021-11-03&type=Issues) | [@manics](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Amanics+updated%3A2021-07-16..2021-11-03&type=Issues) | [@meeseeksmachine](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Ameeseeksmachine+updated%3A2021-07-16..2021-11-03&type=Issues) | [@minrk](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aminrk+updated%3A2021-07-16..2021-11-03&type=Issues) | [@support](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Asupport+updated%3A2021-07-16..2021-11-03&type=Issues) | [@welcome](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Awelcome+updated%3A2021-07-16..2021-11-03&type=Issues) | [@yuvipanda](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Ayuvipanda+updated%3A2021-07-16..2021-11-03&type=Issues)
## 1.4 ## 1.4
JupyterHub 1.4 is a small release, with several enhancements, bug fixes, JupyterHub 1.4 is a small release, with several enhancements, bug fixes,
@@ -1097,7 +1132,8 @@ Fix removal of `/login` page in 0.4.0, breaking some OAuth providers.
First preview release First preview release
[unreleased]: https://github.com/jupyterhub/jupyterhub/compare/1.4.1...HEAD [unreleased]: https://github.com/jupyterhub/jupyterhub/compare/1.5.0...HEAD
[1.5.0]: https://github.com/jupyterhub/jupyterhub/compare/1.4.2...1.5.0
[1.4.2]: https://github.com/jupyterhub/jupyterhub/compare/1.4.1...1.4.2 [1.4.2]: https://github.com/jupyterhub/jupyterhub/compare/1.4.1...1.4.2
[1.4.1]: https://github.com/jupyterhub/jupyterhub/compare/1.4.0...1.4.1 [1.4.1]: https://github.com/jupyterhub/jupyterhub/compare/1.4.0...1.4.1
[1.4.0]: https://github.com/jupyterhub/jupyterhub/compare/1.3.0...1.4.0 [1.4.0]: https://github.com/jupyterhub/jupyterhub/compare/1.3.0...1.4.0

View File

@@ -4,8 +4,8 @@
version_info = ( version_info = (
1, 1,
4, 5,
2, 0,
"", # release (b1, rc1, or "" for final or dev) "", # release (b1, rc1, or "" for final or dev)
# "dev", # dev or nothing for beta/rc/stable releases # "dev", # dev or nothing for beta/rc/stable releases
) )

View File

@@ -222,6 +222,14 @@ class OAuthAuthorizeHandler(OAuthHandler, BaseHandler):
# default: require confirmation # default: require confirmation
return True return True
def get_login_url(self):
"""
Support automatically logging in when JupyterHub is used as auth provider
"""
if self.authenticator.auto_login_oauth2_authorize:
return self.authenticator.login_url(self.hub.base_url)
return super().get_login_url()
@web.authenticated @web.authenticated
async def get(self): async def get(self):
"""GET /oauth/authorization """GET /oauth/authorization

View File

@@ -1476,6 +1476,26 @@ class JupyterHub(Application):
""", """,
).tag(config=True) ).tag(config=True)
use_legacy_stopped_server_status_code = Bool(
True,
help="""
Return 503 rather than 424 when request comes in for a non-running server.
Prior to JupyterHub 2.0, this returns a 503 when any request comes in for
a user server that is currently not running. By default, JupyterHub 2.0
will return a 424 - this makes operational metric dashboards more useful.
JupyterLab < 3.2 expected the 503 to know if the user server is no longer
running, and prompted the user to start their server. Set this config to
true to retain the old behavior, so JupyterLab < 3.2 can continue to show
the appropriate UI when the user server is stopped.
This option will default to False in JupyterHub 2.0, and be removed in a
future release.
""",
config=True,
)
def init_handlers(self): def init_handlers(self):
h = [] h = []
# load handlers from the authenticator # load handlers from the authenticator

View File

@@ -646,6 +646,26 @@ class Authenticator(LoggingConfigurable):
""", """,
) )
auto_login_oauth2_authorize = Bool(
False,
config=True,
help="""
Automatically begin login process for OAuth2 authorization requests
When another application is using JupyterHub as OAuth2 provider, it
sends users to `/hub/api/oauth2/authorize`. If the user isn't logged
in already, and auto_login is not set, the user will be dumped on the
hub's home page, without any context on what to do next.
Setting this to true will automatically redirect users to login if
they aren't logged in *only* on the `/hub/api/oauth2/authorize`
endpoint.
.. versionadded:: 1.5
""",
)
def login_url(self, base_url): def login_url(self, base_url):
"""Override this when registering a custom login handler """Override this when registering a custom login handler

View File

@@ -1330,7 +1330,7 @@ class UserUrlHandler(BaseHandler):
**Changed Behavior as of 1.0** This handler no longer triggers a spawn. Instead, it checks if: **Changed Behavior as of 1.0** This handler no longer triggers a spawn. Instead, it checks if:
1. server is not active, serve page prompting for spawn (status: 503) 1. server is not active, serve page prompting for spawn (status: 424)
2. server is ready (This shouldn't happen! Proxy isn't updated yet. Wait a bit and redirect.) 2. server is ready (This shouldn't happen! Proxy isn't updated yet. Wait a bit and redirect.)
3. server is active, redirect to /hub/spawn-pending to monitor launch progress 3. server is active, redirect to /hub/spawn-pending to monitor launch progress
(will redirect back when finished) (will redirect back when finished)
@@ -1349,7 +1349,14 @@ class UserUrlHandler(BaseHandler):
self.log.warning( self.log.warning(
"Failing suspected API request to not-running server: %s", self.request.path "Failing suspected API request to not-running server: %s", self.request.path
) )
self.set_status(503)
# If we got here, the server is not running. To differentiate
# that the *server* itself is not running, rather than just the particular
# resource *in* the server is not found, we return a 424 instead of a 404.
# We allow retaining the old behavior to support older JupyterLab versions
self.set_status(
424 if not self.app.use_legacy_stopped_server_status_code else 503
)
self.set_header("Content-Type", "application/json") self.set_header("Content-Type", "application/json")
spawn_url = urlparse(self.request.full_url())._replace(query="") spawn_url = urlparse(self.request.full_url())._replace(query="")
@@ -1514,15 +1521,17 @@ class UserUrlHandler(BaseHandler):
self.redirect(pending_url, status=303) self.redirect(pending_url, status=303)
return return
# if we got here, the server is not running # If we got here, the server is not running. To differentiate
# serve a page prompting for spawn and 503 error # that the *server* itself is not running, rather than just the particular
# visiting /user/:name no longer triggers implicit spawn # page *in* the server is not found, we return a 424 instead of a 404.
# without explicit user action # We allow retaining the old behavior to support older JupyterLab versions
spawn_url = url_concat( spawn_url = url_concat(
url_path_join(self.hub.base_url, "spawn", user.escaped_name, server_name), url_path_join(self.hub.base_url, "spawn", user.escaped_name, server_name),
{"next": self.request.uri}, {"next": self.request.uri},
) )
self.set_status(503) self.set_status(
424 if not self.app.use_legacy_stopped_server_status_code else 503
)
auth_state = await user.get_auth_state() auth_state = await user.get_auth_state()
html = await self.render_template( html = await self.render_template(

View File

@@ -927,8 +927,8 @@ class HubAuthenticated(object):
self._hub_auth_user_cache = None self._hub_auth_user_cache = None
raise raise
# store tokens passed via url or header in a cookie for future requests # store ?token=... tokens passed via url in a cookie for future requests
url_token = self.hub_auth.get_token(self) url_token = self.get_argument('token', '')
if ( if (
user_model user_model
and url_token and url_token

View File

@@ -675,6 +675,18 @@ class SingleUserNotebookAppMixin(Configurable):
orig_loader = env.loader orig_loader = env.loader
env.loader = ChoiceLoader([FunctionLoader(get_page), orig_loader]) env.loader = ChoiceLoader([FunctionLoader(get_page), orig_loader])
def load_server_extensions(self):
# Loading LabApp sets $JUPYTERHUB_API_TOKEN on load, which is incorrect
r = super().load_server_extensions()
# clear the token in PageConfig at this step
# so that cookie auth is used
# FIXME: in the future,
# it would probably make sense to set page_config.token to the token
# from the current request.
if 'page_config_data' in self.web_app.settings:
self.web_app.settings['page_config_data']['token'] = ''
return r
def detect_base_package(App): def detect_base_package(App):
"""Detect the base package for an App class """Detect the base package for an App class

View File

@@ -1008,6 +1008,13 @@ async def test_server_not_running_api_request(app):
assert " /user/bees" in message assert " /user/bees" in message
async def test_server_not_running_api_request_legacy_status(app):
app.use_legacy_stopped_server_status_code = False
cookies = await app.login_user("bees")
r = await get_page("user/bees/api/status", app, hub=False, cookies=cookies)
assert r.status_code == 424
async def test_metrics_no_auth(app): async def test_metrics_no_auth(app):
r = await get_page("metrics", app) r = await get_page("metrics", app)
assert r.status_code == 403 assert r.status_code == 403

View File

@@ -12,8 +12,11 @@ import shutil
import sys import sys
from subprocess import check_call from subprocess import check_call
from setuptools import Command
from setuptools import setup from setuptools import setup
from setuptools.command.bdist_egg import bdist_egg from setuptools.command.bdist_egg import bdist_egg
from setuptools.command.build_py import build_py
from setuptools.command.sdist import sdist
v = sys.version_info v = sys.version_info
@@ -132,14 +135,9 @@ setup_args = dict(
) )
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# custom distutils commands # custom setuptools commands
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# imports here, so they are after setuptools import if there was one
from distutils.cmd import Command
from distutils.command.build_py import build_py
from distutils.command.sdist import sdist
def mtime(path): def mtime(path):
"""shorthand for mtime""" """shorthand for mtime"""

View File

@@ -20,9 +20,14 @@ require(["jquery", "jhapi", "moment"], function ($, JHAPI, moment) {
if (!note.length) { if (!note.length) {
note = "Requested via token page"; note = "Requested via token page";
} }
var expiration_seconds =
parseInt($("#token-expiration-seconds").val()) || null;
api.request_token( api.request_token(
user, user,
{ note: note }, {
note: note,
expires_in: expiration_seconds,
},
{ {
success: function (reply) { success: function (reply) {
$("#token-result").text(reply.token); $("#token-result").text(reply.token);

View File

@@ -19,6 +19,20 @@
<small id="note-note" class="form-text text-muted"> <small id="note-note" class="form-text text-muted">
This note will help you keep track of what your tokens are for. This note will help you keep track of what your tokens are for.
</small> </small>
<br><br>
<label for="token-expiration-seconds">Token expires</label>
{% block expiration_options %}
<select id="token-expiration-seconds"
class="form-control">
<option value="3600">1 Day</option>
<option value="86400">1 Week</option>
<option value="604800">1 Month</option>
<option value="" selected="selected">Never</option>
</select>
{% endblock expiration_options %}
<small id="note-expires-at" class="form-text text-muted">
You can configure when your token will be expired.
</small>
</div> </div>
</form> </form>
</div> </div>
@@ -56,6 +70,7 @@
<td>Note</td> <td>Note</td>
<td>Last used</td> <td>Last used</td>
<td>Created</td> <td>Created</td>
<td>Expires at</td>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -77,6 +92,13 @@
N/A N/A
{%- endif -%} {%- endif -%}
</td> </td>
<td class="time-col col-sm-3">
{%- if token.expires_at -%}
{{ token.expires_at.isoformat() + 'Z' }}
{%- else -%}
Never
{%- endif -%}
</td>
<td class="col-sm-1 text-center"> <td class="col-sm-1 text-center">
<button class="revoke-token-btn btn btn-xs btn-danger">revoke</button> <button class="revoke-token-btn btn btn-xs btn-danger">revoke</button>
</td> </td>