Skip to content

Commit 0d4e701

Browse files
committed
Fix Authentication with JupyterHub
1 parent 087c11d commit 0d4e701

File tree

2 files changed

+80
-51
lines changed

2 files changed

+80
-51
lines changed

jupyter_server_proxy/standalone/__init__.py

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,32 +4,32 @@
44

55
from tornado import ioloop
66
from tornado.httpserver import HTTPServer
7-
from tornado.log import app_log
7+
from tornado.log import app_log as log
88

99
from .activity import start_activity_update
1010
from .proxy import configure_http_client, get_port_from_env, get_ssl_options, make_app
1111

1212

1313
def run(
14-
command: list[str],
15-
port=None,
16-
destport=0,
17-
ip="localhost",
18-
debug=False,
19-
logs=True,
20-
authtype="oauth",
21-
timeout=60,
22-
activity_interval=300,
23-
progressive=False,
24-
websocket_max_message_size=0,
14+
command,
15+
port,
16+
destport,
17+
ip,
18+
debug,
19+
logs,
20+
overwrite_authentication,
21+
timeout,
22+
activity_interval,
23+
progressive,
24+
websocket_max_message_size,
2525
):
2626
if port is None:
2727
get_port_from_env()
2828

2929
if debug:
30-
app_log.setLevel(logging.DEBUG)
30+
log.setLevel(logging.DEBUG)
3131
elif logs:
32-
app_log.setLevel(logging.INFO)
32+
log.setLevel(logging.INFO)
3333

3434
prefix = os.environ.get("JUPYTERHUB_SERVICE_PREFIX", "/")
3535

@@ -42,7 +42,7 @@ def run(
4242
destport,
4343
prefix,
4444
list(command),
45-
authtype,
45+
overwrite_authentication,
4646
timeout,
4747
debug,
4848
logs,
@@ -56,12 +56,11 @@ def run(
5656

5757
http_server.listen(port or get_port_from_env(), ip)
5858

59-
print(
60-
f"Starting jhsingle-native-proxy server on address {ip} port {port}, proxying to port {destport}"
59+
log.info(
60+
f"Starting standaloneproxy on {ip}:{port}, server is started on Port {destport}"
6161
)
62-
print(f"URL Prefix: {prefix}")
63-
print(f"Auth Type: {authtype}")
64-
print(f"Command: {command}")
62+
log.info(f"URL Prefix: {prefix}")
63+
log.info(f"Command: {command}")
6564

6665
if activity_interval > 0:
6766
start_activity_update(activity_interval)
@@ -99,10 +98,9 @@ def main():
9998
help="Display logs generated by the subprocess.",
10099
)
101100
parser.add_argument(
102-
"--authtype",
103-
choices=["oauth", "none"],
104-
default="oauth",
105-
help="Authentication Metod.",
101+
"--overwrite-authentication",
102+
default=None,
103+
help="Forcefully enable/disable authentication with JupyterHub.",
106104
)
107105
parser.add_argument(
108106
"--timeout",
@@ -133,6 +131,8 @@ def main():
133131
)
134132

135133
args = parser.parse_args()
134+
log.debug(args)
135+
136136
run(**vars(args))
137137

138138

jupyter_server_proxy/standalone/proxy.py

Lines changed: 56 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@
33
from urllib.parse import urlparse
44

55
from jupyterhub import __version__ as __jh_version__
6-
from jupyterhub.services.auth import HubOAuthCallbackHandler
6+
from jupyterhub.services.auth import HubOAuthCallbackHandler, HubOAuthenticated
77
from jupyterhub.utils import make_ssl_context
88
from tornado import httpclient
9-
from tornado.web import Application, RedirectHandler, RequestHandler
9+
from tornado.web import Application, RedirectHandler
1010
from tornado.websocket import WebSocketHandler
1111

12-
from ..handlers import SuperviseAndProxyHandler
12+
from ..handlers import ProxyHandler, SuperviseAndProxyHandler
1313

1414

1515
def configure_http_client():
@@ -24,19 +24,18 @@ def configure_http_client():
2424
httpclient.AsyncHTTPClient.configure(None, defaults={"ssl_options": ssl_context})
2525

2626

27-
class StandaloneHubProxyHandler(SuperviseAndProxyHandler):
27+
class StandaloneProxyHandler(SuperviseAndProxyHandler):
28+
"""
29+
Base class for standalone proxies. Will not ensure any authentication!
30+
"""
31+
2832
def __init__(self, *args, **kwargs):
2933
super().__init__(*args, **kwargs)
30-
self.authtype = "oauth"
3134
self.environment = {}
3235
self.timeout = 60
3336

3437
def prepare(self, *args, **kwargs):
35-
# ToDo: Automatically disable if not spawned by JupyterHub
36-
if self.authtype == "oauth":
37-
return super().prepare(*args, **kwargs)
38-
else:
39-
pass
38+
pass
4039

4140
def check_origin(self, origin: str = None):
4241
# Skip JupyterHandler.check_origin
@@ -49,38 +48,70 @@ def get_timeout(self):
4948
return self.timeout
5049

5150

52-
def _make_native_proxy_handler(command, port, mappath, authtype, environment, timeout):
51+
class StandaloneHubProxyHandler(StandaloneProxyHandler, HubOAuthenticated):
52+
"""
53+
Standalone Proxy used when spawned by a JupyterHub.
54+
Will restrict access to the application by authentication with the JupyterHub API.
55+
"""
56+
57+
@property
58+
def hub_users(self):
59+
return {self.settings["user"]}
60+
61+
@property
62+
def hub_groups(self):
63+
if self.settings["group"]:
64+
return {self.settings["group"]}
65+
return set()
66+
67+
@property
68+
def allow_all(self):
69+
if "anyone" in self.settings:
70+
return self.settings["anyone"] == "1"
71+
return super().allow_all
72+
73+
def prepare(self, *args, **kwargs):
74+
# Enable Authentication Check
75+
return ProxyHandler.prepare(self, *args, **kwargs)
76+
77+
def set_default_headers(self):
78+
self.set_header("X-JupyterHub-Version", __jh_version__)
79+
80+
81+
def _make_native_proxy_handler(
82+
command, port, mappath, overwrite_authentication, environment, timeout
83+
):
5384
"""
5485
Create a StandaloneHubProxyHandler subclass with given parameters
5586
"""
5687

57-
class _Proxy(StandaloneHubProxyHandler):
88+
# Try to determine if we are launched by a JupyterHub via environment variables.
89+
# See jupyterhub/spawner.py#L1035
90+
if overwrite_authentication is not None:
91+
base = (
92+
StandaloneHubProxyHandler
93+
if overwrite_authentication is True
94+
else StandaloneProxyHandler
95+
)
96+
elif "JUPYTERHUB_API_TOKEN" in os.environ and "JUPYTERHUB_API_URL" in os.environ:
97+
base = StandaloneHubProxyHandler
98+
else:
99+
base = StandaloneProxyHandler
100+
101+
class _Proxy(base):
58102
def __init__(self, *args, **kwargs):
59103
super().__init__(*args, **kwargs)
60104
self.name = command[0]
61105
self.proxy_base = command[0]
62106
self.requested_port = port
63107
self.mappath = mappath
64108
self.command = command
65-
self.authtype = authtype
66109
self.environment = environment
67110
self.timeout = timeout
68111

69112
return _Proxy
70113

71114

72-
def patch_default_headers():
73-
if hasattr(RequestHandler, "_orig_set_default_headers"):
74-
return
75-
RequestHandler._orig_set_default_headers = RequestHandler.set_default_headers
76-
77-
def set_jupyterhub_header(self):
78-
self._orig_set_default_headers()
79-
self.set_header("X-JupyterHub-Version", __jh_version__)
80-
81-
RequestHandler.set_default_headers = set_jupyterhub_header
82-
83-
84115
def make_app(
85116
destport,
86117
prefix,
@@ -92,8 +123,6 @@ def make_app(
92123
progressive,
93124
websocket_max_message_size,
94125
):
95-
patch_default_headers()
96-
97126
# ToDo: Environment
98127
proxy_handler = _make_native_proxy_handler(
99128
command, destport, {}, authtype, {}, timeout

0 commit comments

Comments
 (0)