##// END OF EJS Templates
make CORS configurable...
MinRK -
Show More
@@ -153,6 +153,48 b' class IPythonHandler(AuthenticatedHandler):'
153 153 return self.notebook_manager.notebook_dir
154 154
155 155 #---------------------------------------------------------------
156 # CORS
157 #---------------------------------------------------------------
158
159 @property
160 def cors_origin(self):
161 """Normal Access-Control-Allow-Origin"""
162 return self.settings.get('cors_origin', '')
163
164 @property
165 def cors_origin_pat(self):
166 """Regular expression version of cors_origin"""
167 return self.settings.get('cors_origin_pat', None)
168
169 @property
170 def cors_credentials(self):
171 """Whether to set Access-Control-Allow-Credentials"""
172 return self.settings.get('cors_credentials', False)
173
174 def set_default_headers(self):
175 """Add CORS headers, if defined"""
176 super(IPythonHandler, self).set_default_headers()
177 if self.cors_origin:
178 self.set_header("Access-Control-Allow-Origin", self.cors_origin)
179 elif self.cors_origin_pat:
180 origin = self.get_origin()
181 if origin and self.cors_origin_pat.match(origin):
182 self.set_header("Access-Control-Allow-Origin", origin)
183 if self.cors_credentials:
184 self.set_header("Access-Control-Allow-Credentials", 'true')
185
186 def get_origin(self):
187 # Handle WebSocket Origin naming convention differences
188 # The difference between version 8 and 13 is that in 8 the
189 # client sends a "Sec-Websocket-Origin" header and in 13 it's
190 # simply "Origin".
191 if "Origin" in self.request.headers:
192 origin = self.request.headers.get("Origin")
193 else:
194 origin = self.request.headers.get("Sec-Websocket-Origin", None)
195 return origin
196
197 #---------------------------------------------------------------
156 198 # template rendering
157 199 #---------------------------------------------------------------
158 200
@@ -15,6 +15,8 b' try:'
15 15 except ImportError:
16 16 from Cookie import SimpleCookie # Py 2
17 17 import logging
18
19 import tornado
18 20 from tornado import web
19 21 from tornado import websocket
20 22
@@ -26,29 +28,35 b' from .handlers import IPythonHandler'
26 28
27 29
28 30 class ZMQStreamHandler(websocket.WebSocketHandler):
29
30 def same_origin(self):
31 """Check to see that origin and host match in the headers."""
32
33 # The difference between version 8 and 13 is that in 8 the
34 # client sends a "Sec-Websocket-Origin" header and in 13 it's
35 # simply "Origin".
36 if self.request.headers.get("Sec-WebSocket-Version") in ("7", "8"):
37 origin_header = self.request.headers.get("Sec-Websocket-Origin")
38 else:
39 origin_header = self.request.headers.get("Origin")
31
32 def check_origin(self, origin):
33 """Check Origin == Host or CORS origins."""
34 if self.cors_origin == '*':
35 return True
40 36
41 37 host = self.request.headers.get("Host")
42 38
43 39 # If no header is provided, assume we can't verify origin
44 if(origin_header is None or host is None):
40 if(origin is None or host is None):
41 return False
42
43 host_origin = "{0}://{1}".format(self.request.protocol, host)
44
45 # OK if origin matches host
46 if origin == host_origin:
47 return True
48
49 # Check CORS headers
50 if self.cors_origin:
51 if self.cors_origin == '*':
52 return True
53 else:
54 return self.cors_origin == origin
55 elif self.cors_origin_pat:
56 return bool(self.cors_origin_pat.match(origin))
57 else:
58 # No CORS headers, deny the request
45 59 return False
46
47 parsed_origin = urlparse(origin_header)
48 origin = parsed_origin.netloc
49
50 # Check to see that origin matches host directly, including ports
51 return origin == host
52 60
53 61 def clear_cookie(self, *args, **kwargs):
54 62 """meaningless for websockets"""
@@ -96,13 +104,21 b' class ZMQStreamHandler(websocket.WebSocketHandler):'
96 104
97 105
98 106 class AuthenticatedZMQStreamHandler(ZMQStreamHandler, IPythonHandler):
107 def set_default_headers(self):
108 """Undo the set_default_headers in IPythonHandler
109
110 which doesn't make sense for websockets
111 """
112 pass
99 113
100 114 def open(self, kernel_id):
101 115 self.kernel_id = cast_unicode(kernel_id, 'ascii')
102 116 # Check to see that origin matches host directly, including ports
103 if not self.same_origin():
104 self.log.warn("Cross Origin WebSocket Attempt.")
105 raise web.HTTPError(404)
117 # Tornado 4 already does CORS checking
118 if tornado.version_info[0] < 4:
119 if not self.check_origin(self.get_origin()):
120 self.log.warn("Cross Origin WebSocket Attempt.")
121 raise web.HTTPError(404)
106 122
107 123 self.session = Session(config=self.config)
108 124 self.save_on_message = self.on_message
@@ -12,6 +12,7 b' import json'
12 12 import logging
13 13 import os
14 14 import random
15 import re
15 16 import select
16 17 import signal
17 18 import socket
@@ -333,8 +334,34 b' class NotebookApp(BaseIPythonApplication):'
333 334 self.file_to_run = base
334 335 self.notebook_dir = path
335 336
336 # Network related information.
337
337 # Network related information
338
339 cors_origin = Unicode('', config=True,
340 help="""Set the Access-Control-Allow-Origin header
341
342 Use '*' to allow any origin to access your server.
343
344 Mutually exclusive with cors_origin_pat.
345 """
346 )
347
348 cors_origin_pat = Unicode('', config=True,
349 help="""Use a regular expression for the Access-Control-Allow-Origin header
350
351 Requests from an origin matching the expression will get replies with:
352
353 Access-Control-Allow-Origin: origin
354
355 where `origin` is the origin of the request.
356
357 Mutually exclusive with cors_origin.
358 """
359 )
360
361 cors_credentials = Bool(False, config=True,
362 help="Set the Access-Control-Allow-Credentials: true header"
363 )
364
338 365 ip = Unicode('localhost', config=True,
339 366 help="The IP address the notebook server will listen on."
340 367 )
@@ -622,6 +649,10 b' class NotebookApp(BaseIPythonApplication):'
622 649
623 650 def init_webapp(self):
624 651 """initialize tornado webapp and httpserver"""
652 self.webapp_settings['cors_origin'] = self.cors_origin
653 self.webapp_settings['cors_origin_pat'] = re.compile(self.cors_origin_pat)
654 self.webapp_settings['cors_credentials'] = self.cors_credentials
655
625 656 self.web_app = NotebookWebApplication(
626 657 self, self.kernel_manager, self.notebook_manager,
627 658 self.cluster_manager, self.session_manager, self.kernel_spec_manager,
General Comments 0
You need to be logged in to leave comments. Login now