Merge branch 'master' into end-to-end-ssl

This commit is contained in:
Min RK
2018-10-02 11:15:53 +02:00
committed by GitHub
90 changed files with 3685 additions and 1391 deletions

View File

@@ -6,7 +6,6 @@ from datetime import datetime, timedelta
from urllib.parse import quote, urlparse
import warnings
from oauth2.error import ClientNotFoundError
from sqlalchemy import inspect
from tornado import gen
from tornado.log import app_log
@@ -183,19 +182,41 @@ class User:
await self.save_auth_state(auth_state)
return auth_state
def _new_spawner(self, name, spawner_class=None, **kwargs):
def all_spawners(self, include_default=True):
"""Generator yielding all my spawners
including those that are not running.
Spawners that aren't running will be low-level orm.Spawner objects,
while those that are will be higher-level Spawner wrapper objects.
"""
for name, orm_spawner in sorted(self.orm_user.orm_spawners.items()):
if name == '' and not include_default:
continue
if name and not self.allow_named_servers:
continue
if name in self.spawners:
# yield wrapper if it exists (server may be active)
yield self.spawners[name]
else:
# otherwise, yield low-level ORM object (server is not active)
yield orm_spawner
def _new_spawner(self, server_name, spawner_class=None, **kwargs):
"""Create a new spawner"""
if spawner_class is None:
spawner_class = self.spawner_class
self.log.debug("Creating %s for %s:%s", spawner_class, self.name, name)
self.log.debug("Creating %s for %s:%s", spawner_class, self.name, server_name)
orm_spawner = self.orm_spawners.get(name)
orm_spawner = self.orm_spawners.get(server_name)
if orm_spawner is None:
orm_spawner = orm.Spawner(user=self.orm_user, name=name)
orm_spawner = orm.Spawner(user=self.orm_user, name=server_name)
self.db.add(orm_spawner)
self.db.commit()
assert name in self.orm_spawners
if name == '' and self.state:
assert server_name in self.orm_spawners
if server_name == '' and self.state:
# migrate user.state to spawner.state
orm_spawner.state = self.state
self.state = None
@@ -203,15 +224,15 @@ class User:
# use fully quoted name for client_id because it will be used in cookie-name
# self.escaped_name may contain @ which is legal in URLs but not cookie keys
client_id = 'jupyterhub-user-%s' % quote(self.name)
if name:
client_id = '%s-%s' % (client_id, quote(name))
if server_name:
client_id = '%s-%s' % (client_id, quote(server_name))
spawn_kwargs = dict(
user=self,
orm_spawner=orm_spawner,
hub=self.settings.get('hub'),
authenticator=self.authenticator,
config=self.settings.get('config'),
proxy_spec=url_path_join(self.proxy_spec, name, '/'),
proxy_spec=url_path_join(self.proxy_spec, server_name, '/'),
db=self.db,
oauth_client_id=client_id,
cookie_options = self.settings.get('cookie_options', {}),
@@ -334,6 +355,13 @@ class User:
else:
return self.base_url
def server_url(self, server_name=''):
"""Get the url for a server with a given name"""
if not server_name:
return self.url
else:
return url_path_join(self.url, server_name)
def progress_url(self, server_name=''):
"""API URL for progress endpoint for a server with a given name"""
url_parts = [self.settings['hub'].base_url, 'api/users', self.escaped_name]
@@ -343,7 +371,7 @@ class User:
url_parts.extend(['server/progress'])
return url_path_join(*url_parts)
async def spawn(self, server_name='', options=None):
async def spawn(self, server_name='', options=None, handler=None):
"""Start the user's spawner
depending from the value of JupyterHub.allow_named_servers
@@ -373,6 +401,9 @@ class User:
spawner.server = server = Server(orm_server=orm_server)
assert spawner.orm_spawner.server is orm_server
# pass requesting handler to the spawner
# e.g. for processing GET params
spawner.handler = handler
# Passing user_options to the spawner
spawner.user_options = options or {}
# we are starting a new server, make sure it doesn't restore state
@@ -384,17 +415,14 @@ class User:
client_id = spawner.oauth_client_id
oauth_provider = self.settings.get('oauth_provider')
if oauth_provider:
client_store = oauth_provider.client_authenticator.client_store
try:
oauth_client = client_store.fetch_by_client_id(client_id)
except ClientNotFoundError:
oauth_client = None
oauth_client = oauth_provider.fetch_by_client_id(client_id)
# create a new OAuth client + secret on every launch
# containers that resume will be updated below
client_store.add_client(client_id, api_token,
url_path_join(self.url, server_name, 'oauth_callback'),
description="Server at %s" % (url_path_join(self.base_url, server_name) + '/'),
)
oauth_provider.add_client(
client_id, api_token,
url_path_join(self.url, server_name, 'oauth_callback'),
description="Server at %s" % (url_path_join(self.base_url, server_name) + '/'),
)
db.commit()
# trigger pre-spawn hook on authenticator
@@ -469,10 +497,10 @@ class User:
)
# update OAuth client secret with updated API token
if oauth_provider:
client_store = oauth_provider.client_authenticator.client_store
client_store.add_client(client_id, spawner.api_token,
url_path_join(self.url, server_name, 'oauth_callback'),
)
oauth_provider.add_client(
client_id, spawner.api_token,
url_path_join(self.url, server_name, 'oauth_callback'),
)
db.commit()
except Exception as e:
@@ -497,6 +525,9 @@ class User:
# raise original exception
spawner._start_pending = False
raise e
finally:
# clear reference to handler after start finishes
spawner.handler = None
spawner.start_polling()
# store state
@@ -572,11 +603,25 @@ class User:
# remove server entry from db
spawner.server = None
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
orm_token = orm.APIToken.find(self.db, api_token)
if 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()
finally:
spawner.orm_spawner.started = None