@@ -43,6 +43,7 @@ class ProxyHandler(WebSocketHandlerMixin, IPythonHandler):
4343 def __init__ (self , * args , ** kwargs ):
4444 self .proxy_base = ''
4545 self .absolute_url = kwargs .pop ('absolute_url' , False )
46+ self .host_whitelist = kwargs .pop ('host_whitelist' , ['localhost' , '127.0.0.1' ])
4647 super ().__init__ (* args , ** kwargs )
4748
4849 # Support all the methods that tornado does by default except for GET which
@@ -167,6 +168,12 @@ def _build_proxy_request(self, host, port, proxied_path, body):
167168 headers = headers , ** self .proxy_request_options ())
168169 return req
169170
171+ def _check_host_whitelist (self , host ):
172+ if callable (self .host_whitelist ):
173+ return self .host_whitelist (self , host )
174+ else :
175+ return host in self .host_whitelist
176+
170177 @web .authenticated
171178 async def proxy (self , host , port , proxied_path ):
172179 '''
@@ -176,6 +183,12 @@ async def proxy(self, host, port, proxied_path):
176183 {base_url}/{proxy_base}/{proxied_path}
177184 '''
178185
186+ if not self ._check_host_whitelist (host ):
187+ self .set_status (403 )
188+ self .write ("Host '{host}' is not whitelisted. "
189+ "See https://jupyter-server-proxy.readthedocs.io/en/latest/arbitrary-ports-hosts.html for info." .format (host = host ))
190+ return
191+
179192 if 'Proxy-Connection' in self .request .headers :
180193 del self .request .headers ['Proxy-Connection' ]
181194
@@ -227,6 +240,14 @@ async def proxy_open(self, host, port, proxied_path=''):
227240 We establish a websocket connection to the proxied backend &
228241 set up a callback to relay messages through.
229242 """
243+
244+ if not self ._check_host_whitelist (host ):
245+ self .set_status (403 )
246+ self .log .info ("Host '{host}' is not whitelisted. "
247+ "See https://jupyter-server-proxy.readthedocs.io/en/latest/arbitrary-ports-hosts.html for info." .format (host = host ))
248+ self .close ()
249+ return
250+
230251 if not proxied_path .startswith ('/' ):
231252 proxied_path = '/' + proxied_path
232253
@@ -335,6 +356,40 @@ def proxy(self, port, proxied_path):
335356 return super ().proxy ('localhost' , port , proxied_path )
336357
337358
359+ class RemoteProxyHandler (ProxyHandler ):
360+ """
361+ A tornado request handler that proxies HTTP and websockets
362+ from a port on a specified remote system.
363+ """
364+
365+ async def http_get (self , host , port , proxied_path ):
366+ return await self .proxy (host , port , proxied_path )
367+
368+ def post (self , host , port , proxied_path ):
369+ return self .proxy (host , port , proxied_path )
370+
371+ def put (self , host , port , proxied_path ):
372+ return self .proxy (host , port , proxied_path )
373+
374+ def delete (self , host , port , proxied_path ):
375+ return self .proxy (host , port , proxied_path )
376+
377+ def head (self , host , port , proxied_path ):
378+ return self .proxy (host , port , proxied_path )
379+
380+ def patch (self , host , port , proxied_path ):
381+ return self .proxy (host , port , proxied_path )
382+
383+ def options (self , host , port , proxied_path ):
384+ return self .proxy (host , port , proxied_path )
385+
386+ async def open (self , host , port , proxied_path ):
387+ return await self .proxy_open (host , port , proxied_path )
388+
389+ def proxy (self , host , port , proxied_path ):
390+ return super ().proxy (host , port , proxied_path )
391+
392+
338393# FIXME: Move this to its own file. Too many packages now import this from nbrserverproxy.handlers
339394class SuperviseAndProxyHandler (LocalProxyHandler ):
340395 '''Manage a given process and requests to it '''
@@ -474,9 +529,13 @@ def options(self, path):
474529 return self .proxy (self .port , path )
475530
476531
477- def setup_handlers (web_app ):
532+ def setup_handlers (web_app , host_whitelist ):
478533 host_pattern = '.*$'
479534 web_app .add_handlers ('.*' , [
535+ (url_path_join (web_app .settings ['base_url' ], r'/proxy/(.*):(\d+)(.*)' ),
536+ RemoteProxyHandler , {'absolute_url' : False , 'host_whitelist' : host_whitelist }),
537+ (url_path_join (web_app .settings ['base_url' ], r'/proxy/absolute/(.*):(\d+)(.*)' ),
538+ RemoteProxyHandler , {'absolute_url' : True , 'host_whitelist' : host_whitelist }),
480539 (url_path_join (web_app .settings ['base_url' ], r'/proxy/(\d+)(.*)' ),
481540 LocalProxyHandler , {'absolute_url' : False }),
482541 (url_path_join (web_app .settings ['base_url' ], r'/proxy/absolute/(\d+)(.*)' ),
0 commit comments