##// END OF EJS Templates
make CORS configurable...
MinRK -
Show More
@@ -153,6 +153,48 b' class IPythonHandler(AuthenticatedHandler):'
153 return self.notebook_manager.notebook_dir
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 # template rendering
198 # template rendering
157 #---------------------------------------------------------------
199 #---------------------------------------------------------------
158
200
@@ -15,6 +15,8 b' try:'
15 except ImportError:
15 except ImportError:
16 from Cookie import SimpleCookie # Py 2
16 from Cookie import SimpleCookie # Py 2
17 import logging
17 import logging
18
19 import tornado
18 from tornado import web
20 from tornado import web
19 from tornado import websocket
21 from tornado import websocket
20
22
@@ -27,28 +29,34 b' from .handlers import IPythonHandler'
27
29
28 class ZMQStreamHandler(websocket.WebSocketHandler):
30 class ZMQStreamHandler(websocket.WebSocketHandler):
29
31
30 def same_origin(self):
32 def check_origin(self, origin):
31 """Check to see that origin and host match in the headers."""
33 """Check Origin == Host or CORS origins."""
32
34 if self.cors_origin == '*':
33 # The difference between version 8 and 13 is that in 8 the
35 return True
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")
40
36
41 host = self.request.headers.get("Host")
37 host = self.request.headers.get("Host")
42
38
43 # If no header is provided, assume we can't verify origin
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):
45 return False
41 return False
46
42
47 parsed_origin = urlparse(origin_header)
43 host_origin = "{0}://{1}".format(self.request.protocol, host)
48 origin = parsed_origin.netloc
49
44
50 # Check to see that origin matches host directly, including ports
45 # OK if origin matches host
51 return origin == 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
59 return False
52
60
53 def clear_cookie(self, *args, **kwargs):
61 def clear_cookie(self, *args, **kwargs):
54 """meaningless for websockets"""
62 """meaningless for websockets"""
@@ -96,11 +104,19 b' class ZMQStreamHandler(websocket.WebSocketHandler):'
96
104
97
105
98 class AuthenticatedZMQStreamHandler(ZMQStreamHandler, IPythonHandler):
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 def open(self, kernel_id):
114 def open(self, kernel_id):
101 self.kernel_id = cast_unicode(kernel_id, 'ascii')
115 self.kernel_id = cast_unicode(kernel_id, 'ascii')
102 # Check to see that origin matches host directly, including ports
116 # Check to see that origin matches host directly, including ports
103 if not self.same_origin():
117 # Tornado 4 already does CORS checking
118 if tornado.version_info[0] < 4:
119 if not self.check_origin(self.get_origin()):
104 self.log.warn("Cross Origin WebSocket Attempt.")
120 self.log.warn("Cross Origin WebSocket Attempt.")
105 raise web.HTTPError(404)
121 raise web.HTTPError(404)
106
122
@@ -12,6 +12,7 b' import json'
12 import logging
12 import logging
13 import os
13 import os
14 import random
14 import random
15 import re
15 import select
16 import select
16 import signal
17 import signal
17 import socket
18 import socket
@@ -333,7 +334,33 b' class NotebookApp(BaseIPythonApplication):'
333 self.file_to_run = base
334 self.file_to_run = base
334 self.notebook_dir = path
335 self.notebook_dir = path
335
336
336 # Network related information.
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 )
337
364
338 ip = Unicode('localhost', config=True,
365 ip = Unicode('localhost', config=True,
339 help="The IP address the notebook server will listen on."
366 help="The IP address the notebook server will listen on."
@@ -622,6 +649,10 b' class NotebookApp(BaseIPythonApplication):'
622
649
623 def init_webapp(self):
650 def init_webapp(self):
624 """initialize tornado webapp and httpserver"""
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 self.web_app = NotebookWebApplication(
656 self.web_app = NotebookWebApplication(
626 self, self.kernel_manager, self.notebook_manager,
657 self, self.kernel_manager, self.notebook_manager,
627 self.cluster_manager, self.session_manager, self.kernel_spec_manager,
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