mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-07 18:14:10 +00:00
Compare commits
10 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
b1111363fd | ||
![]() |
6c99b807c2 | ||
![]() |
8d650f594e | ||
![]() |
04a0a3a2e5 | ||
![]() |
9cebfd6367 | ||
![]() |
587cd70221 | ||
![]() |
e94f5e043a | ||
![]() |
5456fb6356 | ||
![]() |
fb75b9a392 | ||
![]() |
90d341e6f7 |
@@ -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:
|
||||||
|
@@ -9,6 +9,30 @@ 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
|
||||||
|
|
||||||
|
JupyterHub 0.9.3 contains small bugfixes and improvements
|
||||||
|
|
||||||
|
- Fix token page and model handling of `expires_at`.
|
||||||
|
This field was missing from the REST API model for tokens
|
||||||
|
and could cause the token page to not render
|
||||||
|
- Add keep-alive to progress event stream to avoid proxies dropping
|
||||||
|
the connection due to inactivity
|
||||||
|
- Documentation and example improvements
|
||||||
|
- Disable quit button when using notebook 5.6
|
||||||
|
- Prototype new feature (may change prior to 1.0):
|
||||||
|
pass requesting Handler to Spawners during start,
|
||||||
|
accessible as `self.handler`
|
||||||
|
|
||||||
### [0.9.2] 2018-08-10
|
### [0.9.2] 2018-08-10
|
||||||
|
|
||||||
JupyterHub 0.9.2 contains small bugfixes and improvements.
|
JupyterHub 0.9.2 contains small bugfixes and improvements.
|
||||||
@@ -402,7 +426,9 @@ 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.2...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.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
|
||||||
[0.9.0]: https://github.com/jupyterhub/jupyterhub/compare/0.8.1...0.9.0
|
[0.9.0]: https://github.com/jupyterhub/jupyterhub/compare/0.8.1...0.9.0
|
||||||
|
@@ -4,11 +4,11 @@
|
|||||||
# Distributed under the terms of the Modified BSD License.
|
# Distributed under the terms of the Modified BSD License.
|
||||||
|
|
||||||
version_info = (
|
version_info = (
|
||||||
1,
|
|
||||||
0,
|
|
||||||
0,
|
0,
|
||||||
|
9,
|
||||||
|
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
|
||||||
)
|
)
|
||||||
|
|
||||||
# pep 440 version: no dot before beta/rc, but before .dev
|
# pep 440 version: no dot before beta/rc, but before .dev
|
||||||
|
@@ -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)
|
||||||
|
@@ -429,7 +429,7 @@ class UserAdminAccessAPIHandler(APIHandler):
|
|||||||
class SpawnProgressAPIHandler(APIHandler):
|
class SpawnProgressAPIHandler(APIHandler):
|
||||||
"""EventStream handler for pending spawns"""
|
"""EventStream handler for pending spawns"""
|
||||||
|
|
||||||
keepalive_interval = 10
|
keepalive_interval = 8
|
||||||
|
|
||||||
def get_content_type(self):
|
def get_content_type(self):
|
||||||
return 'text/event-stream'
|
return 'text/event-stream'
|
||||||
@@ -445,7 +445,6 @@ class SpawnProgressAPIHandler(APIHandler):
|
|||||||
|
|
||||||
_finished = False
|
_finished = False
|
||||||
def on_finish(self):
|
def on_finish(self):
|
||||||
print("on finish")
|
|
||||||
self._finished = True
|
self._finished = True
|
||||||
|
|
||||||
async def keepalive(self):
|
async def keepalive(self):
|
||||||
@@ -456,7 +455,7 @@ class SpawnProgressAPIHandler(APIHandler):
|
|||||||
"""
|
"""
|
||||||
while not self._finished:
|
while not self._finished:
|
||||||
try:
|
try:
|
||||||
await self.write("\n\n")
|
self.write("\n\n")
|
||||||
except (StreamClosedError, RuntimeError):
|
except (StreamClosedError, RuntimeError):
|
||||||
return
|
return
|
||||||
await asyncio.sleep(self.keepalive_interval)
|
await asyncio.sleep(self.keepalive_interval)
|
||||||
|
@@ -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)):
|
||||||
|
@@ -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)
|
||||||
|
@@ -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
|
||||||
|
@@ -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
|
||||||
|
Reference in New Issue
Block a user