##// END OF EJS Templates
Backport PR #6061: make CORS configurable...
MinRK -
Show More
@@ -163,6 +163,48 b' class IPythonHandler(AuthenticatedHandler):'
163 163 return self.notebook_manager.notebook_dir
164 164
165 165 #---------------------------------------------------------------
166 # CORS
167 #---------------------------------------------------------------
168
169 @property
170 def allow_origin(self):
171 """Normal Access-Control-Allow-Origin"""
172 return self.settings.get('allow_origin', '')
173
174 @property
175 def allow_origin_pat(self):
176 """Regular expression version of allow_origin"""
177 return self.settings.get('allow_origin_pat', None)
178
179 @property
180 def allow_credentials(self):
181 """Whether to set Access-Control-Allow-Credentials"""
182 return self.settings.get('allow_credentials', False)
183
184 def set_default_headers(self):
185 """Add CORS headers, if defined"""
186 super(IPythonHandler, self).set_default_headers()
187 if self.allow_origin:
188 self.set_header("Access-Control-Allow-Origin", self.allow_origin)
189 elif self.allow_origin_pat:
190 origin = self.get_origin()
191 if origin and self.allow_origin_pat.match(origin):
192 self.set_header("Access-Control-Allow-Origin", origin)
193 if self.allow_credentials:
194 self.set_header("Access-Control-Allow-Credentials", 'true')
195
196 def get_origin(self):
197 # Handle WebSocket Origin naming convention differences
198 # The difference between version 8 and 13 is that in 8 the
199 # client sends a "Sec-Websocket-Origin" header and in 13 it's
200 # simply "Origin".
201 if "Origin" in self.request.headers:
202 origin = self.request.headers.get("Origin")
203 else:
204 origin = self.request.headers.get("Sec-Websocket-Origin", None)
205 return origin
206
207 #---------------------------------------------------------------
166 208 # template rendering
167 209 #---------------------------------------------------------------
168 210
@@ -26,6 +26,8 b' try:'
26 26 except ImportError:
27 27 from Cookie import SimpleCookie # Py 2
28 28 import logging
29
30 import tornado
29 31 from tornado import web
30 32 from tornado import websocket
31 33
@@ -43,28 +45,35 b' from .handlers import IPythonHandler'
43 45
44 46 class ZMQStreamHandler(websocket.WebSocketHandler):
45 47
46 def same_origin(self):
47 """Check to see that origin and host match in the headers."""
48 def check_origin(self, origin):
49 """Check Origin == Host or Access-Control-Allow-Origin.
48 50
49 # The difference between version 8 and 13 is that in 8 the
50 # client sends a "Sec-Websocket-Origin" header and in 13 it's
51 # simply "Origin".
52 if self.request.headers.get("Sec-WebSocket-Version") in ("7", "8"):
53 origin_header = self.request.headers.get("Sec-Websocket-Origin")
54 else:
55 origin_header = self.request.headers.get("Origin")
51 Tornado >= 4 calls this method automatically, raising 403 if it returns False.
52 We call it explicitly in `open` on Tornado < 4.
53 """
54 if self.allow_origin == '*':
55 return True
56 56
57 57 host = self.request.headers.get("Host")
58 58
59 59 # If no header is provided, assume we can't verify origin
60 if(origin_header is None or host is None):
60 if(origin is None or host is None):
61 61 return False
62 62
63 parsed_origin = urlparse(origin_header)
64 origin = parsed_origin.netloc
63 host_origin = "{0}://{1}".format(self.request.protocol, host)
65 64
66 # Check to see that origin matches host directly, including ports
67 return origin == host
65 # OK if origin matches host
66 if origin == host_origin:
67 return True
68
69 # Check CORS headers
70 if self.allow_origin:
71 return self.allow_origin == origin
72 elif self.allow_origin_pat:
73 return bool(self.allow_origin_pat.match(origin))
74 else:
75 # No CORS headers deny the request
76 return False
68 77
69 78 def clear_cookie(self, *args, **kwargs):
70 79 """meaningless for websockets"""
@@ -112,13 +121,21 b' class ZMQStreamHandler(websocket.WebSocketHandler):'
112 121
113 122
114 123 class AuthenticatedZMQStreamHandler(ZMQStreamHandler, IPythonHandler):
124 def set_default_headers(self):
125 """Undo the set_default_headers in IPythonHandler
126
127 which doesn't make sense for websockets
128 """
129 pass
115 130
116 131 def open(self, kernel_id):
117 132 self.kernel_id = cast_unicode(kernel_id, 'ascii')
118 133 # Check to see that origin matches host directly, including ports
119 if not self.same_origin():
120 self.log.warn("Cross Origin WebSocket Attempt.")
121 raise web.HTTPError(404)
134 # Tornado 4 already does CORS checking
135 if tornado.version_info[0] < 4:
136 if not self.check_origin(self.get_origin()):
137 self.log.warn("Cross Origin WebSocket Attempt from %s", self.get_origin())
138 raise web.HTTPError(403)
122 139
123 140 self.session = Session(config=self.config)
124 141 self.save_on_message = self.on_message
@@ -24,6 +24,7 b' import json'
24 24 import logging
25 25 import os
26 26 import random
27 import re
27 28 import select
28 29 import signal
29 30 import socket
@@ -340,7 +341,33 b' class NotebookApp(BaseIPythonApplication):'
340 341 self.file_to_run = base
341 342 self.notebook_dir = path
342 343
343 # Network related information.
344 # Network related information
345
346 allow_origin = Unicode('', config=True,
347 help="""Set the Access-Control-Allow-Origin header
348
349 Use '*' to allow any origin to access your server.
350
351 Takes precedence over allow_origin_pat.
352 """
353 )
354
355 allow_origin_pat = Unicode('', config=True,
356 help="""Use a regular expression for the Access-Control-Allow-Origin header
357
358 Requests from an origin matching the expression will get replies with:
359
360 Access-Control-Allow-Origin: origin
361
362 where `origin` is the origin of the request.
363
364 Ignored if allow_origin is set.
365 """
366 )
367
368 allow_credentials = Bool(False, config=True,
369 help="Set the Access-Control-Allow-Credentials: true header"
370 )
344 371
345 372 ip = Unicode('localhost', config=True,
346 373 help="The IP address the notebook server will listen on."
@@ -603,6 +630,10 b' class NotebookApp(BaseIPythonApplication):'
603 630
604 631 def init_webapp(self):
605 632 """initialize tornado webapp and httpserver"""
633 self.webapp_settings['allow_origin'] = self.allow_origin
634 self.webapp_settings['allow_origin_pat'] = re.compile(self.allow_origin_pat)
635 self.webapp_settings['allow_credentials'] = self.allow_credentials
636
606 637 self.web_app = NotebookWebApplication(
607 638 self, self.kernel_manager, self.notebook_manager,
608 639 self.cluster_manager, self.session_manager,
General Comments 0
You need to be logged in to leave comments. Login now