Merge branch 'jupyterhub:main' into contributing

This commit is contained in:
Allan Wasega
2023-02-16 21:15:10 -08:00
committed by GitHub
7 changed files with 94 additions and 28 deletions

View File

@@ -28,7 +28,6 @@ on:
env:
# UTF-8 content may be interpreted as ascii and causes errors without this.
LANG: C.UTF-8
PYTEST_ADDOPTS: "--verbose --color=yes"
SQLALCHEMY_WARN_20: "1"
permissions:
@@ -79,21 +78,30 @@ jobs:
oldest_dependencies: oldest_dependencies
legacy_notebook: legacy_notebook
- python: "3.8"
legacy_notebook: legacy_notebook
jupyter_server: "1.*"
subset: singleuser
- python: "3.9"
db: mysql
- python: "3.10"
db: postgres
- python: "3.11"
subdomain: subdomain
serverextension: serverextension
- python: "3.11"
ssl: ssl
serverextension: serverextension
- python: "3.11"
subdomain: subdomain
noextension: noextension
subset: singleuser
- python: "3.11"
ssl: ssl
noextension: noextension
subset: singleuser
- python: "3.11"
selenium: selenium
- python: "3.11"
main_dependencies: main_dependencies
- python: "3.10"
serverextension: serverextension
steps:
# NOTE: In GitHub workflows, environment variables are set by writing
@@ -119,6 +127,8 @@ jobs:
fi
if [ "${{ matrix.serverextension }}" != "" ]; then
echo "JUPYTERHUB_SINGLEUSER_EXTENSION=1" >> $GITHUB_ENV
elif [ "${{ matrix.noextension }}" != "" ]; then
echo "JUPYTERHUB_SINGLEUSER_EXTENSION=0" >> $GITHUB_ENV
fi
- uses: actions/checkout@v3
# NOTE: actions/setup-node@v3 make use of a cache within the GitHub base
@@ -161,6 +171,9 @@ jobs:
pip uninstall jupyter_server --yes
pip install 'notebook<7'
fi
if [ "${{ matrix.jupyter_server }}" != "" ]; then
pip install "jupyter_server==${{ matrix.jupyter_server }}"
fi
if [ "${{ matrix.db }}" == "mysql" ]; then
pip install mysql-connector-python
fi
@@ -220,7 +233,7 @@ jobs:
- name: Run pytest
run: |
pytest --maxfail=2 --cov=jupyterhub jupyterhub/tests
pytest -k "${{ matrix.subset }}" --maxfail=2 --cov=jupyterhub jupyterhub/tests
- uses: codecov/codecov-action@v3

View File

@@ -105,11 +105,16 @@ def _scrub_headers(headers):
auth_type = ''
headers['Authorization'] = f'{auth_type} [secret]'
if 'Cookie' in headers:
c = SimpleCookie(headers['Cookie'])
redacted = []
for name in c.keys():
redacted.append(f"{name}=[secret]")
headers['Cookie'] = '; '.join(redacted)
try:
c = SimpleCookie(headers['Cookie'])
except Exception as e:
# it's possible for browsers to send invalid cookies
headers['Cookie'] = f"Invalid Cookie: {e}"
else:
redacted = []
for name in c.keys():
redacted.append(f"{name}=[secret]")
headers['Cookie'] = '; '.join(redacted)
return headers

View File

@@ -1,40 +1,75 @@
"""JupyterHub single-user server entrypoints
Contains default notebook-app subclass and mixins
Defaults to:
- Jupyter server extension with Jupyter Server >=2
- Subclass with Jupyter Server <2 or clasic notebook
Application subclass can be controlled with environment variables:
- JUPYTERHUB_SINGLEUSER_EXTENSION=1 to opt-in to the extension (requires Jupyter Server 2)
- JUPYTERHUB_SINGLEUSER_APP=notebook (or jupyter-server) to opt-in
"""
import os
from .mixins import HubAuthenticatedHandler, make_singleuser_app
if os.environ.get("JUPYTERHUB_SINGLEUSER_EXTENSION", "") not in ("", "0"):
_as_extension = False
_extension_env = os.environ.get("JUPYTERHUB_SINGLEUSER_EXTENSION", "")
_app_env = os.environ.get("JUPYTERHUB_SINGLEUSER_APP", "")
if not _extension_env:
# extension env not set, check app env
if not _app_env or 'jupyter_server' in _app_env.replace("-", "_"):
# no app env set or using jupyter-server, this is the default branch
# default behavior:
# - extension, if jupyter server 2
# - older subclass app, otherwise
try:
import jupyter_server
_server_major = int(jupyter_server.__version__.split(".", 1)[0])
except Exception:
# don't have jupyter-server, assume classic notebook
_as_extension = False
else:
# default to extension if jupyter-server >=2
_as_extension = _server_major >= 2
elif _app_env == "extension":
_as_extension = True
else:
# app env set and not to jupyter-server, that opts out of extension
_as_extension = False
elif _extension_env == "0":
_as_extension = False
else:
# extension env set to anything non-empty other than '0' enables the extension
_as_extension = True
if _as_extension:
# check for conflict in singleuser entrypoint environment variables
if os.environ.get("JUPYTERHUB_SINGLEUSER_APP", "") not in {
if _app_env not in {
"",
"jupyter_server",
"jupyter-server",
"extension",
"jupyter_server.serverapp.ServerApp",
}:
ext = os.environ["JUPYTERHUB_SINGLEUSER_EXTENSION"]
app = os.environ["JUPYTERHUB_SINGLEUSER_APP"]
raise ValueError(
f"Cannot use JUPYTERHUB_SINGLEUSER_EXTENSION={ext} with JUPYTERHUB_SINGLEUSER_APP={app}."
f"Cannot use JUPYTERHUB_SINGLEUSER_EXTENSION={_extension_env} with JUPYTERHUB_SINGLEUSER_APP={_app_env}."
" Please pick one or the other."
)
from .extension import main
else:
_as_extension = False
try:
from .app import SingleUserNotebookApp, main
except ImportError:
# check for Jupyter Server 2.0 ?
from .extension import main
else:
# backward-compatibility
JupyterHubLoginHandler = SingleUserNotebookApp.login_handler_class
JupyterHubLogoutHandler = SingleUserNotebookApp.logout_handler_class
OAuthCallbackHandler = SingleUserNotebookApp.oauth_callback_handler_class
from .app import SingleUserNotebookApp, main
# backward-compatibility
JupyterHubLoginHandler = SingleUserNotebookApp.login_handler_class
JupyterHubLogoutHandler = SingleUserNotebookApp.logout_handler_class
OAuthCallbackHandler = SingleUserNotebookApp.oauth_callback_handler_class
__all__ = [

View File

@@ -475,6 +475,7 @@ class JupyterHubSingleUser(ExtensionApp):
cfg.identity_provider_class = JupyterHubIdentityProvider
# disable some single-user features
cfg.allow_remote_access = True
cfg.open_browser = False
cfg.trust_xheaders = True
cfg.quit_button = False
@@ -501,6 +502,13 @@ class JupyterHubSingleUser(ExtensionApp):
# to make sure it has the desired effect
cfg.default_url = self.default_url = self.get_default_url()
# load internal SSL configuration
cfg.keyfile = os.environ.get('JUPYTERHUB_SSL_KEYFILE') or ''
cfg.certfile = os.environ.get('JUPYTERHUB_SSL_CERTFILE') or ''
cfg.client_ca = os.environ.get('JUPYTERHUB_SSL_CLIENT_CA') or ''
if cfg.certfile:
self.serverapp.log.info(f"Using SSL cert {cfg.certfile}")
# Jupyter Server default: config files have higher priority than extensions,
# by:
# 1. load config files

View File

@@ -660,6 +660,7 @@ class SingleUserNotebookAppMixin(Configurable):
certfile=self.certfile,
client_ca=self.client_ca,
)
self.hub_host = self.hub_auth.hub_host
# smoke check
if not self.hub_auth.oauth_client_id:
raise ValueError("Missing OAuth client ID")

View File

@@ -283,4 +283,8 @@ async def test_nbclassic_control_panel(app, user, full_spawn):
page = BeautifulSoup(r.text, "html.parser")
link = page.find("a", id="jupyterhub-control-panel-link")
assert link, f"Missing jupyterhub-control-panel-link in {page}"
assert link["href"] == url_path_join(app.base_url, "hub/home")
if app.subdomain_host:
prefix = public_url(app)
else:
prefix = app.base_url
assert link["href"] == url_path_join(prefix, "hub/home")

View File

@@ -7,7 +7,7 @@
asyncio_mode = auto
# jupyter_server plugin is incompatible with notebook imports
addopts = -p no:jupyter_server -m 'not selenium'
addopts = -p no:jupyter_server -m 'not selenium' --color yes --durations 10 --verbose
python_files = test_*.py
markers =