mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-09 11:03:00 +00:00
Compare commits
12 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
bda14b487a | ||
![]() |
fd5cf8c360 | ||
![]() |
03758e5b46 | ||
![]() |
e540d143bb | ||
![]() |
b2c5ad40c5 | ||
![]() |
edfdf672d8 | ||
![]() |
39f19aef49 | ||
![]() |
8813bb63d4 | ||
![]() |
7c18d6fe14 | ||
![]() |
d1fe17d3cb | ||
![]() |
b8965c2017 | ||
![]() |
733d7bc158 |
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -114,7 +114,9 @@ class ScopeTableGenerator:
|
|||||||
if doc_description:
|
if doc_description:
|
||||||
description = doc_description
|
description = doc_description
|
||||||
scope_dict[scope] = description
|
scope_dict[scope] = description
|
||||||
content['securityDefinitions']['oauth2']['scopes'] = scope_dict
|
content['components']['securitySchemes']['oauth2']['flows'][
|
||||||
|
'authorizationCode'
|
||||||
|
]['scopes'] = scope_dict
|
||||||
|
|
||||||
with open(filename, 'w') as f:
|
with open(filename, 'w') as f:
|
||||||
yaml.dump(content, f)
|
yaml.dump(content, f)
|
||||||
|
@@ -5,7 +5,7 @@ Below is an interactive view of JupyterHub's OpenAPI specification.
|
|||||||
<!-- client-rendered openapi UI copied from FastAPI -->
|
<!-- client-rendered openapi UI copied from FastAPI -->
|
||||||
|
|
||||||
<link type="text/css" rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swagger-ui-dist@3/swagger-ui.css">
|
<link type="text/css" rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swagger-ui-dist@3/swagger-ui.css">
|
||||||
<script src="https://cdn.jsdelivr.net/npm/swagger-ui-dist@3/swagger-ui-bundle.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/swagger-ui-dist@4.1/swagger-ui-bundle.js"></script>
|
||||||
<!-- `SwaggerUIBundle` is now available on the page -->
|
<!-- `SwaggerUIBundle` is now available on the page -->
|
||||||
|
|
||||||
<!-- render the ui here -->
|
<!-- render the ui here -->
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
# Copyright (c) Jupyter Development Team.
|
# Copyright (c) Jupyter Development Team.
|
||||||
# Distributed under the terms of the Modified BSD License.
|
# Distributed under the terms of the Modified BSD License.
|
||||||
# version_info updated by running `tbump`
|
# version_info updated by running `tbump`
|
||||||
version_info = (2, 0, 0, "rc3", "")
|
version_info = (2, 0, 0, "rc4", "")
|
||||||
|
|
||||||
# pep 440 version: no dot before beta/rc, but before .dev
|
# pep 440 version: no dot before beta/rc, but before .dev
|
||||||
# 0.1.0rc1
|
# 0.1.0rc1
|
||||||
|
@@ -31,6 +31,9 @@ class APIHandler(BaseHandler):
|
|||||||
- methods for REST API models
|
- methods for REST API models
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# accept token-based authentication for API requests
|
||||||
|
_accept_token_auth = True
|
||||||
|
|
||||||
@property
|
@property
|
||||||
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'"])
|
||||||
@@ -210,6 +213,7 @@ class APIHandler(BaseHandler):
|
|||||||
'last_activity': isoformat(token.last_activity),
|
'last_activity': isoformat(token.last_activity),
|
||||||
'expires_at': isoformat(token.expires_at),
|
'expires_at': isoformat(token.expires_at),
|
||||||
'note': token.note,
|
'note': token.note,
|
||||||
|
'session_id': token.session_id,
|
||||||
'oauth_client': token.oauth_client.description
|
'oauth_client': token.oauth_client.description
|
||||||
or token.oauth_client.identifier,
|
or token.oauth_client.identifier,
|
||||||
}
|
}
|
||||||
|
@@ -58,6 +58,14 @@ class SelfAPIHandler(APIHandler):
|
|||||||
|
|
||||||
model = get_model(user)
|
model = get_model(user)
|
||||||
|
|
||||||
|
# add session_id associated with token
|
||||||
|
# added in 2.0
|
||||||
|
token = self.get_token()
|
||||||
|
if token:
|
||||||
|
model["session_id"] = token.session_id
|
||||||
|
else:
|
||||||
|
model["session_id"] = None
|
||||||
|
|
||||||
# add scopes to identify model,
|
# add scopes to identify model,
|
||||||
# but not the scopes we added to ensure we could read our own model
|
# but not the scopes we added to ensure we could read our own model
|
||||||
model["scopes"] = sorted(self.expanded_scopes.difference(_added_scopes))
|
model["scopes"] = sorted(self.expanded_scopes.difference(_added_scopes))
|
||||||
|
@@ -71,6 +71,12 @@ SESSION_COOKIE_NAME = 'jupyterhub-session-id'
|
|||||||
class BaseHandler(RequestHandler):
|
class BaseHandler(RequestHandler):
|
||||||
"""Base Handler class with access to common methods and properties."""
|
"""Base Handler class with access to common methods and properties."""
|
||||||
|
|
||||||
|
# by default, only accept cookie-based authentication
|
||||||
|
# The APIHandler base class enables token auth
|
||||||
|
# versionadded: 2.0
|
||||||
|
_accept_cookie_auth = True
|
||||||
|
_accept_token_auth = False
|
||||||
|
|
||||||
async def prepare(self):
|
async def prepare(self):
|
||||||
"""Identify the user during the prepare stage of each request
|
"""Identify the user during the prepare stage of each request
|
||||||
|
|
||||||
@@ -340,6 +346,7 @@ class BaseHandler(RequestHandler):
|
|||||||
auth_info['auth_state'] = await user.get_auth_state()
|
auth_info['auth_state'] = await user.get_auth_state()
|
||||||
return await self.auth_to_user(auth_info, user)
|
return await self.auth_to_user(auth_info, user)
|
||||||
|
|
||||||
|
@functools.lru_cache()
|
||||||
def get_token(self):
|
def get_token(self):
|
||||||
"""get token from authorization header"""
|
"""get token from authorization header"""
|
||||||
token = self.get_auth_token()
|
token = self.get_auth_token()
|
||||||
@@ -410,9 +417,11 @@ class BaseHandler(RequestHandler):
|
|||||||
async def get_current_user(self):
|
async def get_current_user(self):
|
||||||
"""get current username"""
|
"""get current username"""
|
||||||
if not hasattr(self, '_jupyterhub_user'):
|
if not hasattr(self, '_jupyterhub_user'):
|
||||||
|
user = None
|
||||||
try:
|
try:
|
||||||
user = self.get_current_user_token()
|
if self._accept_token_auth:
|
||||||
if user is None:
|
user = self.get_current_user_token()
|
||||||
|
if user is None and self._accept_cookie_auth:
|
||||||
user = self.get_current_user_cookie()
|
user = self.get_current_user_cookie()
|
||||||
if user and isinstance(user, User):
|
if user and isinstance(user, User):
|
||||||
user = await self.refresh_auth(user)
|
user = await self.refresh_auth(user)
|
||||||
|
@@ -295,7 +295,7 @@ def get_scopes_for(orm_object):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if isinstance(orm_object, orm.APIToken):
|
if isinstance(orm_object, orm.APIToken):
|
||||||
app_log.warning(f"Authenticated with token {orm_object}")
|
app_log.debug(f"Authenticated with token {orm_object}")
|
||||||
owner = orm_object.user or orm_object.service
|
owner = orm_object.user or orm_object.service
|
||||||
token_scopes = roles.expand_roles_to_scopes(orm_object)
|
token_scopes = roles.expand_roles_to_scopes(orm_object)
|
||||||
if orm_object.client_id != "jupyterhub":
|
if orm_object.client_id != "jupyterhub":
|
||||||
|
@@ -18,6 +18,7 @@ import sys
|
|||||||
import warnings
|
import warnings
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from datetime import timezone
|
from datetime import timezone
|
||||||
|
from importlib import import_module
|
||||||
from textwrap import dedent
|
from textwrap import dedent
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
@@ -606,10 +607,34 @@ class SingleUserNotebookAppMixin(Configurable):
|
|||||||
t = self.hub_activity_interval * (1 + 0.2 * (random.random() - 0.5))
|
t = self.hub_activity_interval * (1 + 0.2 * (random.random() - 0.5))
|
||||||
await asyncio.sleep(t)
|
await asyncio.sleep(t)
|
||||||
|
|
||||||
|
def _log_app_versions(self):
|
||||||
|
"""Log application versions at startup
|
||||||
|
|
||||||
|
Logs versions of jupyterhub and singleuser-server base versions (jupyterlab, jupyter_server, notebook)
|
||||||
|
"""
|
||||||
|
self.log.info(f"Starting jupyterhub single-user server version {__version__}")
|
||||||
|
|
||||||
|
# don't log these package versions
|
||||||
|
seen = {"jupyterhub", "traitlets", "jupyter_core", "builtins"}
|
||||||
|
|
||||||
|
for cls in self.__class__.mro():
|
||||||
|
module_name = cls.__module__.partition(".")[0]
|
||||||
|
if module_name not in seen:
|
||||||
|
seen.add(module_name)
|
||||||
|
try:
|
||||||
|
mod = import_module(module_name)
|
||||||
|
mod_version = getattr(mod, "__version__")
|
||||||
|
except Exception:
|
||||||
|
mod_version = ""
|
||||||
|
self.log.info(
|
||||||
|
f"Extending {cls.__module__}.{cls.__name__} from {module_name} {mod_version}"
|
||||||
|
)
|
||||||
|
|
||||||
def initialize(self, argv=None):
|
def initialize(self, argv=None):
|
||||||
# disable trash by default
|
# disable trash by default
|
||||||
# this can be re-enabled by config
|
# this can be re-enabled by config
|
||||||
self.config.FileContentsManager.delete_to_trash = False
|
self.config.FileContentsManager.delete_to_trash = False
|
||||||
|
self._log_app_versions()
|
||||||
return super().initialize(argv)
|
return super().initialize(argv)
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
|
@@ -578,6 +578,41 @@ async def test_login_page(app, url, params, redirected_url, form_action):
|
|||||||
assert action.endswith(form_action)
|
assert action.endswith(form_action)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"url, token_in",
|
||||||
|
[
|
||||||
|
("/home", "url"),
|
||||||
|
("/home", "header"),
|
||||||
|
("/login", "url"),
|
||||||
|
("/login", "header"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_page_with_token(app, user, url, token_in):
|
||||||
|
cookies = await app.login_user(user.name)
|
||||||
|
token = user.new_api_token()
|
||||||
|
if token_in == "url":
|
||||||
|
url = url_concat(url, {"token": token})
|
||||||
|
headers = None
|
||||||
|
elif token_in == "header":
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"token {token}",
|
||||||
|
}
|
||||||
|
|
||||||
|
# request a page with ?token= in URL shouldn't be allowed
|
||||||
|
r = await get_page(
|
||||||
|
url,
|
||||||
|
app,
|
||||||
|
headers=headers,
|
||||||
|
allow_redirects=False,
|
||||||
|
)
|
||||||
|
if "/hub/login" in r.url:
|
||||||
|
assert r.status_code == 200
|
||||||
|
else:
|
||||||
|
assert r.status_code == 302
|
||||||
|
assert r.headers["location"].partition("?")[0].endswith("/hub/login")
|
||||||
|
assert not r.cookies
|
||||||
|
|
||||||
|
|
||||||
async def test_login_fail(app):
|
async def test_login_fail(app):
|
||||||
name = 'wash'
|
name = 'wash'
|
||||||
base_url = public_url(app)
|
base_url = public_url(app)
|
||||||
|
@@ -11,7 +11,7 @@ target_version = [
|
|||||||
github_url = "https://github.com/jupyterhub/jupyterhub"
|
github_url = "https://github.com/jupyterhub/jupyterhub"
|
||||||
|
|
||||||
[tool.tbump.version]
|
[tool.tbump.version]
|
||||||
current = "2.0.0rc3"
|
current = "2.0.0rc4"
|
||||||
|
|
||||||
# Example of a semver regexp.
|
# Example of a semver regexp.
|
||||||
# Make sure this matches current_version before
|
# Make sure this matches current_version before
|
||||||
|
5
setup.py
5
setup.py
@@ -46,10 +46,9 @@ def get_data_files():
|
|||||||
"""Get data files in share/jupyter"""
|
"""Get data files in share/jupyter"""
|
||||||
|
|
||||||
data_files = []
|
data_files = []
|
||||||
ntrim = len(here + os.path.sep)
|
|
||||||
|
|
||||||
for (d, dirs, filenames) in os.walk(share_jupyterhub):
|
for (d, dirs, filenames) in os.walk(share_jupyterhub):
|
||||||
data_files.append((d[ntrim:], [pjoin(d, f) for f in filenames]))
|
rel_d = os.path.relpath(d, here)
|
||||||
|
data_files.append((rel_d, [os.path.join(rel_d, f) for f in filenames]))
|
||||||
return data_files
|
return data_files
|
||||||
|
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user