Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

development/jupyter_server: Backport bug fixes from jupyter_server 2.3.0 #2430

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions development/jupyter_server/fix_get_loader.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
--- a/jupyter_server/extension/utils.py
+++ b/jupyter_server/extension/utils.py
@@ -36,20 +36,24 @@
underscore prefix.
"""
try:
- func = getattr(obj, "_load_jupyter_server_extension") # noqa B009
+ return getattr(obj, "_load_jupyter_server_extension") # noqa B009
+ except AttributeError:
+ pass
+
+ try:
+ func = getattr(obj, "load_jupyter_server_extension") # noqa B009
except AttributeError:
- func = getattr(obj, "load_jupyter_server_extension", None)
- warnings.warn(
- "A `_load_jupyter_server_extension` function was not "
- "found in {name!s}. Instead, a `load_jupyter_server_extension` "
- "function was found and will be used for now. This function "
- "name will be deprecated in future releases "
- "of Jupyter Server.".format(name=obj),
- DeprecationWarning,
- )
- except Exception:
msg = "_load_jupyter_server_extension function was not found."
raise ExtensionLoadingError(msg) from None
+
+ warnings.warn(
+ "A `_load_jupyter_server_extension` function was not "
+ "found in {name!s}. Instead, a `load_jupyter_server_extension` "
+ "function was found and will be used for now. This function "
+ "name will be deprecated in future releases "
+ "of Jupyter Server.".format(name=obj),
+ DeprecationWarning,
+ )
return func


--- a/tests/extension/mockextensions/mockext_deprecated.py
+++ b/tests/extension/mockextensions/mockext_deprecated.py
@@ -0,0 +1,12 @@
+"""A mock extension named `mockext_py` for testing purposes.
+"""
+# Function that makes these extensions discoverable
+# by the test functions.
+
+
+def _jupyter_server_extension_paths():
+ return [{"module": "tests.extension.mockextensions.mockext_deprecated"}]
+
+
+def load_jupyter_server_extension(serverapp):
+ pass
--- a/tests/extension/test_utils.py
+++ b/tests/extension/test_utils.py
@@ -1,10 +1,14 @@
import logging
-import warnings

import pytest

-from jupyter_server.extension.utils import get_loader, get_metadata, validate_extension
-from tests.extension.mockextensions import mockext_sys
+from jupyter_server.extension.utils import (
+ ExtensionLoadingError,
+ get_loader,
+ get_metadata,
+ validate_extension,
+)
+from tests.extension.mockextensions import mockext_deprecated, mockext_sys

# Use ServerApps environment because it monkeypatches
# jupyter_core.paths and provides a config directory
@@ -24,10 +28,11 @@


def test_get_loader():
- get_loader(mockext_sys)
- with warnings.catch_warnings():
- warnings.simplefilter("ignore")
- assert get_loader(object()) is None
+ assert get_loader(mockext_sys) == mockext_sys._load_jupyter_server_extension
+ with pytest.deprecated_call():
+ assert get_loader(mockext_deprecated) == mockext_deprecated.load_jupyter_server_extension
+ with pytest.raises(ExtensionLoadingError):
+ get_loader(object())


def test_get_metadata():
6 changes: 5 additions & 1 deletion development/jupyter_server/jupyter_server.SlackBuild
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ cd $(dirname $0) ; CWD=$(pwd)

PRGNAM=jupyter_server
VERSION=${VERSION:-2.2.1}
BUILD=${BUILD:-1}
BUILD=${BUILD:-2}
TAG=${TAG:-_SBo}
PKGTYPE=${PKGTYPE:-tgz}

Expand Down Expand Up @@ -76,6 +76,10 @@ find -L . \
\( -perm 666 -o -perm 664 -o -perm 640 -o -perm 600 -o -perm 444 \
-o -perm 440 -o -perm 400 \) -exec chmod 644 {} \;

# Backports from jupyter-server 2.3.0
patch -p1 < $CWD/redact_tokens_from_logs.patch # Redact tokens in url parameters from request logs. See upstream PR #1212
patch -p1 < $CWD/fix_get_loader.patch # See upstream PR #1193

python3 -m build --no-isolation
python3 -m installer -d "$PKG" dist/*.whl

Expand Down
60 changes: 60 additions & 0 deletions development/jupyter_server/redact_tokens_from_logs.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
--- a/jupyter_server/log.py
+++ b/jupyter_server/log.py
@@ -6,12 +6,39 @@
# the file COPYING, distributed as part of this software.
# -----------------------------------------------------------------------------
import json
+from urllib.parse import urlparse, urlunparse

from tornado.log import access_log

from .auth import User
from .prometheus.log_functions import prometheus_log_method

+# url params to be scrubbed if seen
+# any url param that *contains* one of these
+# will be scrubbed from logs
+_SCRUB_PARAM_KEYS = {"token", "auth", "key", "code", "state", "xsrf"}
+
+
+def _scrub_uri(uri: str) -> str:
+ """scrub auth info from uri"""
+ parsed = urlparse(uri)
+ if parsed.query:
+ # check for potentially sensitive url params
+ # use manual list + split rather than parsing
+ # to minimally perturb original
+ parts = parsed.query.split("&")
+ changed = False
+ for i, s in enumerate(parts):
+ key, sep, value = s.partition("=")
+ for substring in _SCRUB_PARAM_KEYS:
+ if substring in key:
+ parts[i] = f"{key}{sep}[secret]"
+ changed = True
+ if changed:
+ parsed = parsed._replace(query="&".join(parts))
+ return urlunparse(parsed)
+ return uri
+

def log_request(handler):
"""log a bit more information about each request than tornado's default
@@ -43,7 +70,7 @@
"status": status,
"method": request.method,
"ip": request.remote_ip,
- "uri": request.uri,
+ "uri": _scrub_uri(request.uri),
"request_time": request_time,
}
# log username
@@ -59,7 +86,7 @@
msg = "{status} {method} {uri} ({username}@{ip}) {request_time:.2f}ms"
if status >= 400: # noqa[PLR2004]
# log bad referers
- ns["referer"] = request.headers.get("Referer", "None")
+ ns["referer"] = _scrub_uri(request.headers.get("Referer", "None"))
msg = msg + " referer={referer}"
if status >= 500 and status != 502: # noqa[PLR2004]
# Log a subset of the headers if it caused an error.