Compare commits

...

6 Commits
0.9.3 ... 0.9.4

Author SHA1 Message Date
Min RK
b1111363fd release 0.9.4 2018-09-24 13:02:36 +02:00
Min RK
6c99b807c2 update changelog for 0.9.4 2018-09-24 13:00:27 +02:00
Min RK
8d650f594e changelog for 0.9.4 2018-09-24 12:58:16 +02:00
Min RK
04a0a3a2e5 fix oauth client cleanup
- delete oauth clients for servers when they shutdown
- avoid deleting oauth clients for servers still running across an 0.8 -> 0.9 upgrade, when the oauth client ids changed from `user-NAME` to `jupyterhub-user-NAME`
2018-09-24 12:58:10 +02:00
Min RK
9cebfd6367 Fix content-type on API endpoints
and includes content-type header checks in tests to catch regressions
2018-09-24 12:57:26 +02:00
Min RK
587cd70221 omit pdf builds on rtd due to bug in sphinx 2018-09-24 12:57:01 +02:00
8 changed files with 59 additions and 4 deletions

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: 0.9.0dev version: 0.9.4
license: license:
name: BSD-3-Clause name: BSD-3-Clause
schemes: schemes:

View File

@@ -9,6 +9,15 @@ command line for details.
## 0.9 ## 0.9
### [0.9.4] 2018-09-24
JupyterHub 0.9.4 is a small bugfix release.
- Fixes an issue that required all running user servers to be restarted
when performing an upgrade from 0.8 to 0.9.
- Fixes content-type for API endpoints back to `application/json`.
It was `text/html` in 0.9.0-0.9.3.
### [0.9.3] 2018-09-12 ### [0.9.3] 2018-09-12
JupyterHub 0.9.3 contains small bugfixes and improvements JupyterHub 0.9.3 contains small bugfixes and improvements
@@ -417,7 +426,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/0.9.3...HEAD [Unreleased]: https://github.com/jupyterhub/jupyterhub/compare/0.9.4...HEAD
[0.9.4]: https://github.com/jupyterhub/jupyterhub/compare/0.9.3...0.9.4
[0.9.3]: https://github.com/jupyterhub/jupyterhub/compare/0.9.2...0.9.3 [0.9.3]: https://github.com/jupyterhub/jupyterhub/compare/0.9.2...0.9.3
[0.9.2]: https://github.com/jupyterhub/jupyterhub/compare/0.9.1...0.9.2 [0.9.2]: https://github.com/jupyterhub/jupyterhub/compare/0.9.1...0.9.2
[0.9.1]: https://github.com/jupyterhub/jupyterhub/compare/0.9.0...0.9.1 [0.9.1]: https://github.com/jupyterhub/jupyterhub/compare/0.9.0...0.9.1

View File

@@ -6,7 +6,7 @@
version_info = ( version_info = (
0, 0,
9, 9,
3, 4,
"", # release (b1, rc1, or "" for final or dev) "", # release (b1, rc1, or "" for final or dev)
# "dev", # dev or nothing # "dev", # dev or nothing
) )

View File

@@ -30,6 +30,9 @@ class APIHandler(BaseHandler):
def content_security_policy(self): def content_security_policy(self):
return '; '.join([super().content_security_policy, "default-src 'none'"]) return '; '.join([super().content_security_policy, "default-src 'none'"])
def get_content_type(self):
return 'application/json'
def check_referer(self): def check_referer(self):
"""Check Origin for cross-site API requests. """Check Origin for cross-site API requests.
@@ -265,3 +268,13 @@ class APIHandler(BaseHandler):
def options(self, *args, **kwargs): def options(self, *args, **kwargs):
self.finish() self.finish()
class API404(APIHandler):
"""404 for API requests
Ensures JSON 404 errors for malformed URLs
"""
async def prepare(self):
await super().prepare()
raise web.HTTPError(404)

View File

@@ -973,6 +973,8 @@ class JupyterHub(Application):
h.extend(self.extra_handlers) h.extend(self.extra_handlers)
h.append((r'/logo', LogoHandler, {'path': self.logo_file})) h.append((r'/logo', LogoHandler, {'path': self.logo_file}))
h.append((r'/api/(.*)', apihandlers.base.API404))
self.handlers = self.add_url_prefix(self.hub_prefix, h) self.handlers = self.add_url_prefix(self.hub_prefix, h)
# some extra handlers, outside hub_prefix # some extra handlers, outside hub_prefix
self.handlers.extend([ self.handlers.extend([
@@ -1506,6 +1508,10 @@ class JupyterHub(Application):
for user in self.users.values(): for user in self.users.values():
for spawner in user.spawners.values(): for spawner in user.spawners.values():
oauth_client_ids.add(spawner.oauth_client_id) oauth_client_ids.add(spawner.oauth_client_id)
# avoid deleting clients created by 0.8
# 0.9 uses `jupyterhub-user-...` for the client id, while
# 0.8 uses just `user-...`
oauth_client_ids.add(spawner.oauth_client_id.split('-', 1)[1])
client_store = self.oauth_provider.client_authenticator.client_store client_store = self.oauth_provider.client_authenticator.client_store
for i, oauth_client in enumerate(self.db.query(orm.OAuthClient)): for i, oauth_client in enumerate(self.db.query(orm.OAuthClient)):

View File

@@ -100,6 +100,8 @@ def api_request(app, *api_path, **kwargs):
assert "frame-ancestors 'self'" in resp.headers['Content-Security-Policy'] assert "frame-ancestors 'self'" in resp.headers['Content-Security-Policy']
assert ujoin(app.hub.base_url, "security/csp-report") in resp.headers['Content-Security-Policy'] assert ujoin(app.hub.base_url, "security/csp-report") in resp.headers['Content-Security-Policy']
assert 'http' not in resp.headers['Content-Security-Policy'] assert 'http' not in resp.headers['Content-Security-Policy']
if not kwargs.get('stream', False) and resp.content:
assert resp.headers.get('content-type') == 'application/json'
return resp return resp
@@ -746,6 +748,8 @@ def test_progress(request, app, no_patience, slow_spawn):
r = yield api_request(app, 'users', name, 'server/progress', stream=True) r = yield api_request(app, 'users', name, 'server/progress', stream=True)
r.raise_for_status() r.raise_for_status()
request.addfinalizer(r.close) request.addfinalizer(r.close)
assert r.headers['content-type'] == 'text/event-stream'
ex = async_requests.executor ex = async_requests.executor
line_iter = iter(r.iter_lines(decode_unicode=True)) line_iter = iter(r.iter_lines(decode_unicode=True))
evt = yield ex.submit(next_event, line_iter) evt = yield ex.submit(next_event, line_iter)
@@ -807,6 +811,7 @@ def test_progress_ready(request, app):
r = yield api_request(app, 'users', name, 'server/progress', stream=True) r = yield api_request(app, 'users', name, 'server/progress', stream=True)
r.raise_for_status() r.raise_for_status()
request.addfinalizer(r.close) request.addfinalizer(r.close)
assert r.headers['content-type'] == 'text/event-stream'
ex = async_requests.executor ex = async_requests.executor
line_iter = iter(r.iter_lines(decode_unicode=True)) line_iter = iter(r.iter_lines(decode_unicode=True))
evt = yield ex.submit(next_event, line_iter) evt = yield ex.submit(next_event, line_iter)
@@ -826,6 +831,7 @@ def test_progress_bad(request, app, no_patience, bad_spawn):
r = yield api_request(app, 'users', name, 'server/progress', stream=True) r = yield api_request(app, 'users', name, 'server/progress', stream=True)
r.raise_for_status() r.raise_for_status()
request.addfinalizer(r.close) request.addfinalizer(r.close)
assert r.headers['content-type'] == 'text/event-stream'
ex = async_requests.executor ex = async_requests.executor
line_iter = iter(r.iter_lines(decode_unicode=True)) line_iter = iter(r.iter_lines(decode_unicode=True))
evt = yield ex.submit(next_event, line_iter) evt = yield ex.submit(next_event, line_iter)
@@ -847,6 +853,7 @@ def test_progress_bad_slow(request, app, no_patience, slow_bad_spawn):
r = yield api_request(app, 'users', name, 'server/progress', stream=True) r = yield api_request(app, 'users', name, 'server/progress', stream=True)
r.raise_for_status() r.raise_for_status()
request.addfinalizer(r.close) request.addfinalizer(r.close)
assert r.headers['content-type'] == 'text/event-stream'
ex = async_requests.executor ex = async_requests.executor
line_iter = iter(r.iter_lines(decode_unicode=True)) line_iter = iter(r.iter_lines(decode_unicode=True))
evt = yield ex.submit(next_event, line_iter) evt = yield ex.submit(next_event, line_iter)

View File

@@ -558,11 +558,25 @@ class User:
# remove server entry from db # remove server entry from db
spawner.server = None spawner.server = None
if not spawner.will_resume: if not spawner.will_resume:
# find and remove the API token if the spawner isn't # find and remove the API token and oauth client if the spawner isn't
# going to re-use it next time # going to re-use it next time
orm_token = orm.APIToken.find(self.db, api_token) orm_token = orm.APIToken.find(self.db, api_token)
if orm_token: if orm_token:
self.db.delete(orm_token) self.db.delete(orm_token)
# remove oauth client as well
# handle upgrades from 0.8, where client id will be `user-USERNAME`,
# not just `jupyterhub-user-USERNAME`
client_ids = (
spawner.oauth_client_id,
spawner.oauth_client_id.split('-', 1)[1],
)
for oauth_client in (
self.db
.query(orm.OAuthClient)
.filter(orm.OAuthClient.identifier.in_(client_ids))
):
self.log.debug("Deleting oauth client %s", oauth_client.identifier)
self.db.delete(oauth_client)
self.db.commit() self.db.commit()
finally: finally:
spawner.orm_spawner.started = None spawner.orm_spawner.started = None

View File

@@ -4,3 +4,8 @@ conda:
file: docs/environment.yml file: docs/environment.yml
python: python:
version: 3 version: 3
formats:
- htmlzip
- epub
# pdf disabled due to bug in sphinx 1.8 + recommonmark
# - pdf