11import os
22import re
3- from urllib . parse import urlparse
3+ from logging import Logger
44
55from jupyterhub import __version__ as __jh_version__
66from jupyterhub .services .auth import HubOAuthCallbackHandler , HubOAuthenticated
77from jupyterhub .utils import make_ssl_context
88from tornado import httpclient , web
99from tornado .log import app_log
10- from tornado .web import Application , RedirectHandler
10+ from tornado .web import Application
1111from tornado .websocket import WebSocketHandler
1212
1313from ..handlers import SuperviseAndProxyHandler
1414
1515
16- def configure_http_client ():
17- keyfile = os .environ .get ("JUPYTERHUB_SSL_KEYFILE" , "" )
18- certfile = os .environ .get ("JUPYTERHUB_SSL_CERTFILE" , "" )
19- client_ca = os .environ .get ("JUPYTERHUB_SSL_CLIENT_CA" , "" )
20-
21- if keyfile == "" and certfile == "" and client_ca == "" :
22- return
23-
24- ssl_context = make_ssl_context (keyfile , certfile , cafile = client_ca )
25- httpclient .AsyncHTTPClient .configure (None , defaults = {"ssl_options" : ssl_context })
26-
27-
28- class StandaloneProxyHandler (SuperviseAndProxyHandler ):
16+ class StandaloneHubProxyHandler (HubOAuthenticated , SuperviseAndProxyHandler ):
2917 """
30- Base class for standalone proxies. Will not ensure any authentication!
18+ Base class for standalone proxies.
19+ Will restrict access to the application by authentication with the JupyterHub API.
3120 """
3221
3322 def __init__ (self , * args , ** kwargs ):
3423 super ().__init__ (* args , ** kwargs )
3524 self .environment = {}
3625 self .timeout = 60
26+ self .skip_authentication = False
27+
28+ @property
29+ def log (self ) -> Logger :
30+ return app_log
31+
32+ @property
33+ def hub_users (self ):
34+ if "hub_user" in self .settings :
35+ return {self .settings ["hub_user" ]}
36+ return set ()
37+
38+ @property
39+ def hub_groups (self ):
40+ if "hub_group" in self .settings :
41+ return {self .settings ["hub_group" ]}
42+ return set ()
43+
44+ def set_default_headers (self ):
45+ self .set_header ("X-JupyterHub-Version" , __jh_version__ )
3746
3847 def prepare (self , * args , ** kwargs ):
3948 pass
4049
50+ async def proxy (self , port , path ):
51+ if self .skip_authentication :
52+ return await super ().proxy (port , path )
53+ else :
54+ return await self .oauth_proxy (port , path )
55+
56+ @web .authenticated
57+ async def oauth_proxy (self , port , path ):
58+ return await super ().proxy (port , path )
59+
4160 def check_origin (self , origin : str = None ):
4261 # Skip JupyterHandler.check_origin
4362 return WebSocketHandler .check_origin (self , origin )
@@ -49,34 +68,21 @@ def get_timeout(self):
4968 return self .timeout
5069
5170
52- class StandaloneHubProxyHandler (HubOAuthenticated , StandaloneProxyHandler ):
53- """
54- Standalone Proxy used when spawned by a JupyterHub.
55- Will restrict access to the application by authentication with the JupyterHub API.
56- """
57-
58- @property
59- def hub_users (self ):
60- return {self .settings ["user" ]}
71+ def configure_ssl ():
72+ keyfile = os .environ .get ("JUPYTERHUB_SSL_KEYFILE" )
73+ certfile = os .environ .get ("JUPYTERHUB_SSL_CERTFILE" )
74+ cafile = os .environ .get ("JUPYTERHUB_SSL_CLIENT_CA" )
6175
62- @property
63- def hub_groups (self ):
64- if self .settings ["group" ]:
65- return {self .settings ["group" ]}
66- return set ()
76+ if not (keyfile and certfile and cafile ):
77+ app_log .warn ("Could not configure SSL" )
78+ return None
6779
68- @property
69- def allow_all (self ):
70- if "anyone" in self .settings :
71- return self .settings ["anyone" ] == "1"
72- return super ().allow_all
80+ ssl_context = make_ssl_context (keyfile , certfile , cafile )
7381
74- @web .authenticated
75- async def proxy (self , port , path ):
76- return await super ().proxy (port , path )
82+ # Configure HTTPClient to use SSL for Proxy Requests
83+ httpclient .AsyncHTTPClient .configure (None , defaults = {"ssl_options" : ssl_context })
7784
78- def set_default_headers (self ):
79- self .set_header ("X-JupyterHub-Version" , __jh_version__ )
85+ return ssl_context
8086
8187
8288def make_proxy_app (
@@ -92,16 +98,12 @@ def make_proxy_app(
9298 # progressive: bool,
9399 websocket_max_message_size : int ,
94100):
95- """
96- Create a StandaloneHubProxyHandler subclass with given parameters
97- """
98101 app_log .debug (f"Process will use { port = } " )
99102 app_log .debug (f"Process will use { unix_socket = } " )
100103 app_log .debug (f"Process environment: { environment } " )
101104 app_log .debug (f"Proxy mappath: { mappath } " )
102105
103- base = StandaloneProxyHandler if skip_authentication else StandaloneHubProxyHandler
104- class Proxy (base ):
106+ class Proxy (StandaloneHubProxyHandler ):
105107 def __init__ (self , * args , ** kwargs ):
106108 super ().__init__ (* args , ** kwargs )
107109 self .name = f"{ command [0 ]!r} Process"
@@ -112,10 +114,11 @@ def __init__(self, *args, **kwargs):
112114 self .command = command
113115 self .environment = environment
114116 self .timeout = timeout
117+ self .skip_authentication = skip_authentication
115118
116119 settings = dict (
117120 debug = debug ,
118- # Required for JupyterHub Authentication
121+ # Required for JupyterHub
119122 hub_user = os .environ .get ("JUPYTERHUB_USER" , "" ),
120123 hub_group = os .environ .get ("JUPYTERHUB_GROUP" , "" ),
121124 cookie_secret = os .urandom (32 ),
@@ -125,12 +128,8 @@ def __init__(self, *args, **kwargs):
125128 app_log .debug (f"Restricting WebSocket Messages to { websocket_max_message_size } " )
126129 settings ["websocket_max_message_size" ] = websocket_max_message_size
127130
128- return Application (
131+ app = Application (
129132 [
130- (
131- r"^" + re .escape (prefix ) + r"/oauth_callback" ,
132- HubOAuthCallbackHandler ,
133- ),
134133 (
135134 r"^" + re .escape (prefix ) + r"/(.*)" ,
136135 Proxy ,
@@ -140,57 +139,11 @@ def __init__(self, *args, **kwargs):
140139 ),
141140 ),
142141 (
143- r"^" + re .escape (prefix .replace ("@" , "%40" )) + r"/(.*)" ,
144- RedirectHandler ,
145- dict (url = prefix + "/{0}" ),
142+ r"^" + re .escape (prefix ) + r"/oauth_callback" ,
143+ HubOAuthCallbackHandler ,
146144 ),
147145 ],
148146 ** settings ,
149147 )
150148
151-
152- def get_ssl_options ():
153- ssl_options = {}
154- keyfile = os .environ .get ("JUPYTERHUB_SSL_KEYFILE" ) or ""
155- certfile = os .environ .get ("JUPYTERHUB_SSL_CERTFILE" ) or ""
156- client_ca = os .environ .get ("JUPYTERHUB_SSL_CLIENT_CA" ) or ""
157-
158- if keyfile :
159- ssl_options ["keyfile" ] = keyfile
160-
161- if certfile :
162- ssl_options ["certfile" ] = certfile
163-
164- if client_ca :
165- ssl_options ["ca_certs" ] = client_ca
166-
167- if not ssl_options :
168- # None indicates no SSL config
169- ssl_options = None
170- else :
171- # SSL may be missing, so only import it if it"s to be used
172- import ssl
173-
174- # PROTOCOL_TLS selects the highest ssl/tls protocol version that both the client and
175- # server support. When PROTOCOL_TLS is not available use PROTOCOL_SSLv23.
176- # PROTOCOL_TLS is new in version 2.7.13, 3.5.3 and 3.6
177- ssl_options .setdefault (
178- "ssl_version" , getattr (ssl , "PROTOCOL_TLS" , ssl .PROTOCOL_SSLv23 )
179- )
180- if ssl_options .get ("ca_certs" , False ):
181- ssl_options .setdefault ("cert_reqs" , ssl .CERT_REQUIRED )
182-
183- return ssl_options
184-
185-
186- # https://github.com/jupyterhub/jupyterhub/blob/2.0.0rc3/jupyterhub/singleuser/mixins.py#L340-L349
187- def get_port_from_env ():
188- if os .environ .get ("JUPYTERHUB_SERVICE_URL" ):
189- url = urlparse (os .environ ["JUPYTERHUB_SERVICE_URL" ])
190- if url .port :
191- return url .port
192- elif url .scheme == "http" :
193- return 80
194- elif url .scheme == "https" :
195- return 443
196- return 8888
149+ return app
0 commit comments