Compare commits

..

19 Commits

Author SHA1 Message Date
Min RK
61b0e8bef5 2.0.0b3 2021-10-14 12:49:20 +02:00
Erik Sundell
64f3938528 Merge pull request #3649 from minrk/cl-beta-3
add 424 status code change to changelog
2021-10-13 09:52:33 +02:00
Min RK
85bc92d88e Merge pull request #3646 from joegasewicz/joegasewicz-New-user-token-returns-200-instead-of-201-1
new user token returns 200 instead of 201
2021-10-13 09:24:30 +02:00
Min RK
7bcda18564 add 424 status code change to changelog 2021-10-13 09:17:47 +02:00
Erik Sundell
86da36857e Merge pull request #3647 from jupyterhub/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2021-10-12 00:13:22 +02:00
pre-commit-ci[bot]
530833e930 [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/PyCQA/flake8: 3.9.2 → 4.0.1](https://github.com/PyCQA/flake8/compare/3.9.2...4.0.1)
2021-10-11 19:42:10 +00:00
Joe Gasewicz
3b0850fa9b Fixed test_roles 2021-10-07 23:14:10 +01:00
josefgasewicz
1366911be6 Fixed tests & set status after writing json 2021-10-07 22:21:16 +01:00
Joe Gasewicz
fe276eac64 Update users.py
New user token returns 200 instead of 201 Fixes #3642
2021-10-07 16:31:23 +01:00
Min RK
9209ccd0de Merge pull request #3636 from yuvipanda/404
Fail suspected API requests with 424, not 503
2021-10-05 15:16:18 +02:00
YuviPanda
3b2a1a37f9 Update tests that were looking for 503s 2021-10-05 18:10:52 +05:30
YuviPanda
6007ba78b0 Preserve older 503 behavior behind a flag 2021-10-05 17:56:51 +05:30
YuviPanda
9cb19cc342 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 17:44:17 +05:30
YuviPanda
0f471f4e12 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 03:00:16 +05:30
Erik Sundell
68db740998 Merge pull request #3635 from jupyterhub/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2021-10-04 22:38:05 +02:00
pre-commit-ci[bot]
9c0c6f25b7 [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/asottile/pyupgrade: v2.28.0 → v2.29.0](https://github.com/asottile/pyupgrade/compare/v2.28.0...v2.29.0)
2021-10-04 19:48:13 +00:00
Min RK
5f0077cb5b Merge pull request #3445 from rpwagner/patch-1
Initial SECURITY.md
2021-09-29 09:42:59 +02:00
Rick Wagner
3610454a12 adding initial security policy 2021-06-01 09:51:20 -07:00
Rick Wagner
abc4bbebe4 Initial SECURITY.md
Proposing a basic security policy, similar to the README or contributors guide, based on the [GitHub documentation](https://docs.github.com/en/code-security/security-advisories/adding-a-security-policy-to-your-repository) and current Project Jupyter recommendations. This may be better as a [default file for the organization](https://docs.github.com/en/communities/setting-up-your-project-for-healthy-contributions/creating-a-default-community-health-file).
2021-04-23 23:12:51 -07:00
10 changed files with 78 additions and 27 deletions

View File

@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/asottile/pyupgrade
rev: v2.28.0
rev: v2.29.0
hooks:
- id: pyupgrade
args:
@@ -18,7 +18,7 @@ repos:
hooks:
- id: prettier
- repo: https://github.com/PyCQA/flake8
rev: "3.9.2"
rev: "4.0.1"
hooks:
- id: flake8
- repo: https://github.com/pre-commit/pre-commit-hooks

5
SECURITY.md Normal file
View File

@@ -0,0 +1,5 @@
# Reporting a Vulnerability
If you believe youve found a security vulnerability in a Jupyter
project, please report it to security@ipython.org. If you prefer to
encrypt your security reports, you can use [this PGP public key](https://jupyter-notebook.readthedocs.io/en/stable/_downloads/1d303a645f2505a8fd283826fafc9908/ipython_security.asc).

View File

@@ -72,6 +72,16 @@ and major bug fixes:
- Improve database rollback recovery on broken connections
and other changes:
- Requests to a not-running server (e.g. visiting `/user/someuser/`)
will return an HTTP 424 error instead of 503,
making it easier to monitor for real deployment problems.
JupyterLab in the user environment should be at least version 3.1.16
to recognize this error code as a stopped server.
You can temporarily opt-in to the older behavior (e.g. if older JupyterLab is required)
by setting `c.JupyterHub.use_legacy_stopped_server_status_code = True`.
Plus lots of little fixes along the way.
## 1.4

View File

@@ -6,7 +6,7 @@ version_info = (
2,
0,
0,
"b2", # release (b1, rc1, or "" for final or dev)
"b3", # release (b1, rc1, or "" for final or dev)
# "dev", # dev or nothing for beta/rc/stable releases
)

View File

@@ -421,6 +421,7 @@ class UserTokenListAPIHandler(APIHandler):
token_model = self.token_model(orm.APIToken.find(self.db, api_token))
token_model['token'] = api_token
self.write(json.dumps(token_model))
self.set_status(201)
class UserTokenAPIHandler(APIHandler):

View File

@@ -1518,6 +1518,25 @@ class JupyterHub(Application):
""",
).tag(config=True)
use_legacy_stopped_server_status_code = Bool(
False,
help="""
Return 503 rather than 424 when request comes in for a non-running server.
Prior to JupyterHub 2.0, we returned a 503 when any request came in for
a user server that was 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 be removed in a future release.
""",
config=True,
)
def init_handlers(self):
h = []
# load handlers from the authenticator

View File

@@ -1357,7 +1357,7 @@ class UserUrlHandler(BaseHandler):
**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.)
3. server is active, redirect to /hub/spawn-pending to monitor launch progress
(will redirect back when finished)
@@ -1376,7 +1376,14 @@ class UserUrlHandler(BaseHandler):
self.log.warning(
"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")
spawn_url = urlparse(self.request.full_url())._replace(query="")
@@ -1541,15 +1548,17 @@ class UserUrlHandler(BaseHandler):
self.redirect(pending_url, status=303)
return
# if we got here, the server is not running
# serve a page prompting for spawn and 503 error
# visiting /user/:name no longer triggers implicit spawn
# without explicit user action
# If we got here, the server is not running. To differentiate
# that the *server* itself is not running, rather than just the particular
# page *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
spawn_url = url_concat(
url_path_join(self.hub.base_url, "spawn", user.escaped_name, server_name),
{"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()
html = await self.render_template(

View File

@@ -1366,8 +1366,8 @@ async def test_get_new_token_deprecated(app, headers, status):
@mark.parametrize(
"headers, status, note, expires_in",
[
({}, 200, 'test note', None),
({}, 200, '', 100),
({}, 201, 'test note', None),
({}, 201, '', 100),
({'Authorization': 'token bad'}, 403, '', None),
],
)
@@ -1386,7 +1386,7 @@ async def test_get_new_token(app, headers, status, note, expires_in):
app, 'users/admin/tokens', method='post', headers=headers, data=body
)
assert r.status_code == status
if status != 200:
if status != 201:
return
# check the new-token reply
reply = r.json()
@@ -1424,10 +1424,10 @@ async def test_get_new_token(app, headers, status, note, expires_in):
@mark.parametrize(
"as_user, for_user, status",
[
('admin', 'other', 200),
('admin', 'other', 201),
('admin', 'missing', 403),
('user', 'other', 403),
('user', 'user', 200),
('user', 'user', 201),
],
)
async def test_token_for_user(app, as_user, for_user, status):
@@ -1448,7 +1448,7 @@ async def test_token_for_user(app, as_user, for_user, status):
)
assert r.status_code == status
reply = r.json()
if status != 200:
if status != 201:
return
assert 'token' in reply
@@ -1486,7 +1486,7 @@ async def test_token_authenticator_noauth(app):
data=json.dumps(data) if data else None,
noauth=True,
)
assert r.status_code == 200
assert r.status_code == 201
reply = r.json()
assert 'token' in reply
r = await api_request(app, 'authorizations', 'token', reply['token'])
@@ -1509,7 +1509,7 @@ async def test_token_authenticator_dict_noauth(app):
data=json.dumps(data) if data else None,
noauth=True,
)
assert r.status_code == 200
assert r.status_code == 201
reply = r.json()
assert 'token' in reply
r = await api_request(app, 'authorizations', 'token', reply['token'])

View File

@@ -56,8 +56,8 @@ async def test_root_redirect(app):
r = await get_page(url, app, cookies=cookies)
path = urlparse(r.url).path
assert path == ujoin(app.base_url, 'hub/user/%s/test.ipynb' % name)
# serve "server not running" page, which has status 503
assert r.status_code == 503
# serve "server not running" page, which has status 424
assert r.status_code == 424
async def test_root_default_url_noauth(app):
@@ -172,7 +172,7 @@ async def test_spawn_redirect(app):
r = await get_page('user/' + name, app, hub=False, cookies=cookies)
path = urlparse(r.url).path
assert path == ujoin(app.base_url, 'hub/user/%s/' % name)
assert r.status_code == 503
assert r.status_code == 424
async def test_spawn_handler_access(app):
@@ -507,13 +507,13 @@ async def test_user_redirect_deprecated(app, username):
print(urlparse(r.url))
path = urlparse(r.url).path
assert path == ujoin(app.base_url, 'hub/user/%s/' % name)
assert r.status_code == 503
assert r.status_code == 424
r = await get_page('/user/baduser/test.ipynb', app, cookies=cookies, hub=False)
print(urlparse(r.url))
path = urlparse(r.url).path
assert path == ujoin(app.base_url, 'hub/user/%s/test.ipynb' % name)
assert r.status_code == 503
assert r.status_code == 424
r = await get_page('/user/baduser/test.ipynb', app, hub=False)
r.raise_for_status()
@@ -1061,13 +1061,20 @@ async def test_token_page(app):
async def test_server_not_running_api_request(app):
cookies = await app.login_user("bees")
r = await get_page("user/bees/api/status", app, hub=False, cookies=cookies)
assert r.status_code == 503
assert r.status_code == 424
assert r.headers["content-type"] == "application/json"
message = r.json()['message']
assert ujoin(app.base_url, "hub/spawn/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 = True
cookies = await app.login_user("bees")
r = await get_page("user/bees/api/status", app, hub=False, cookies=cookies)
assert r.status_code == 503
async def test_metrics_no_auth(app):
r = await get_page("metrics", app)
assert r.status_code == 403

View File

@@ -661,11 +661,11 @@ async def test_load_roles_user_tokens(tmpdir, request):
"headers, rolename, scopes, status",
[
# no role requested - gets default 'token' role
({}, None, None, 200),
({}, None, None, 201),
# role scopes within the user's default 'user' role
({}, 'self-reader', ['read:users'], 200),
({}, 'self-reader', ['read:users'], 201),
# role scopes outside of the user's role but within the group's role scopes of which the user is a member
({}, 'groups-reader', ['read:groups'], 200),
({}, 'groups-reader', ['read:groups'], 201),
# non-existing role request
({}, 'non-existing', [], 404),
# role scopes outside of both user's role and group's role scopes