##// END OF EJS Templates
Set kernel_id before checking websocket...
Steven Anton -
Show More
@@ -1,134 +1,134 b''
1 """Tornado handlers for WebSocket <-> ZMQ sockets."""
1 """Tornado handlers for WebSocket <-> ZMQ sockets."""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 try:
6 try:
7 from urllib.parse import urlparse # Py 3
7 from urllib.parse import urlparse # Py 3
8 except ImportError:
8 except ImportError:
9 from urlparse import urlparse # Py 2
9 from urlparse import urlparse # Py 2
10
10
11 try:
11 try:
12 from http.cookies import SimpleCookie # Py 3
12 from http.cookies import SimpleCookie # Py 3
13 except ImportError:
13 except ImportError:
14 from Cookie import SimpleCookie # Py 2
14 from Cookie import SimpleCookie # Py 2
15 import logging
15 import logging
16 from tornado import web
16 from tornado import web
17 from tornado import websocket
17 from tornado import websocket
18
18
19 from zmq.utils import jsonapi
19 from zmq.utils import jsonapi
20
20
21 from IPython.kernel.zmq.session import Session
21 from IPython.kernel.zmq.session import Session
22 from IPython.utils.jsonutil import date_default
22 from IPython.utils.jsonutil import date_default
23 from IPython.utils.py3compat import PY3, cast_unicode
23 from IPython.utils.py3compat import PY3, cast_unicode
24
24
25 from .handlers import IPythonHandler
25 from .handlers import IPythonHandler
26
26
27
27
28 class ZMQStreamHandler(websocket.WebSocketHandler):
28 class ZMQStreamHandler(websocket.WebSocketHandler):
29
29
30 def same_origin(self):
30 def same_origin(self):
31 """Check to see that origin and host match in the headers."""
31 """Check to see that origin and host match in the headers."""
32
32
33 # The difference between version 8 and 13 is that in 8 the
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
34 # client sends a "Sec-Websocket-Origin" header and in 13 it's
35 # simply "Origin".
35 # simply "Origin".
36 if self.request.headers.get("Sec-WebSocket-Version") in ("7", "8"):
36 if self.request.headers.get("Sec-WebSocket-Version") in ("7", "8"):
37 origin_header = self.request.headers.get("Sec-Websocket-Origin")
37 origin_header = self.request.headers.get("Sec-Websocket-Origin")
38 else:
38 else:
39 origin_header = self.request.headers.get("Origin")
39 origin_header = self.request.headers.get("Origin")
40
40
41 host = self.request.headers.get("Host")
41 host = self.request.headers.get("Host")
42
42
43 # If no header is provided, assume we can't verify origin
43 # If no header is provided, assume we can't verify origin
44 if(origin_header is None or host is None):
44 if(origin_header is None or host is None):
45 return False
45 return False
46
46
47 parsed_origin = urlparse(origin_header)
47 parsed_origin = urlparse(origin_header)
48 origin = parsed_origin.netloc
48 origin = parsed_origin.netloc
49
49
50 # Check to see that origin matches host directly, including ports
50 # Check to see that origin matches host directly, including ports
51 return origin == host
51 return origin == host
52
52
53 def clear_cookie(self, *args, **kwargs):
53 def clear_cookie(self, *args, **kwargs):
54 """meaningless for websockets"""
54 """meaningless for websockets"""
55 pass
55 pass
56
56
57 def _reserialize_reply(self, msg_list):
57 def _reserialize_reply(self, msg_list):
58 """Reserialize a reply message using JSON.
58 """Reserialize a reply message using JSON.
59
59
60 This takes the msg list from the ZMQ socket, unserializes it using
60 This takes the msg list from the ZMQ socket, unserializes it using
61 self.session and then serializes the result using JSON. This method
61 self.session and then serializes the result using JSON. This method
62 should be used by self._on_zmq_reply to build messages that can
62 should be used by self._on_zmq_reply to build messages that can
63 be sent back to the browser.
63 be sent back to the browser.
64 """
64 """
65 idents, msg_list = self.session.feed_identities(msg_list)
65 idents, msg_list = self.session.feed_identities(msg_list)
66 msg = self.session.unserialize(msg_list)
66 msg = self.session.unserialize(msg_list)
67 try:
67 try:
68 msg['header'].pop('date')
68 msg['header'].pop('date')
69 except KeyError:
69 except KeyError:
70 pass
70 pass
71 try:
71 try:
72 msg['parent_header'].pop('date')
72 msg['parent_header'].pop('date')
73 except KeyError:
73 except KeyError:
74 pass
74 pass
75 msg.pop('buffers')
75 msg.pop('buffers')
76 return jsonapi.dumps(msg, default=date_default)
76 return jsonapi.dumps(msg, default=date_default)
77
77
78 def _on_zmq_reply(self, msg_list):
78 def _on_zmq_reply(self, msg_list):
79 # Sometimes this gets triggered when the on_close method is scheduled in the
79 # Sometimes this gets triggered when the on_close method is scheduled in the
80 # eventloop but hasn't been called.
80 # eventloop but hasn't been called.
81 if self.stream.closed(): return
81 if self.stream.closed(): return
82 try:
82 try:
83 msg = self._reserialize_reply(msg_list)
83 msg = self._reserialize_reply(msg_list)
84 except Exception:
84 except Exception:
85 self.log.critical("Malformed message: %r" % msg_list, exc_info=True)
85 self.log.critical("Malformed message: %r" % msg_list, exc_info=True)
86 else:
86 else:
87 self.write_message(msg)
87 self.write_message(msg)
88
88
89 def allow_draft76(self):
89 def allow_draft76(self):
90 """Allow draft 76, until browsers such as Safari update to RFC 6455.
90 """Allow draft 76, until browsers such as Safari update to RFC 6455.
91
91
92 This has been disabled by default in tornado in release 2.2.0, and
92 This has been disabled by default in tornado in release 2.2.0, and
93 support will be removed in later versions.
93 support will be removed in later versions.
94 """
94 """
95 return True
95 return True
96
96
97
97
98 class AuthenticatedZMQStreamHandler(ZMQStreamHandler, IPythonHandler):
98 class AuthenticatedZMQStreamHandler(ZMQStreamHandler, IPythonHandler):
99
99
100 def open(self, kernel_id):
100 def open(self, kernel_id):
101 self.kernel_id = cast_unicode(kernel_id, 'ascii')
101 # Check to see that origin matches host directly, including ports
102 # Check to see that origin matches host directly, including ports
102 if not self.same_origin():
103 if not self.same_origin():
103 self.log.warn("Cross Origin WebSocket Attempt.")
104 self.log.warn("Cross Origin WebSocket Attempt.")
104 raise web.HTTPError(404)
105 raise web.HTTPError(404)
105
106
106 self.kernel_id = cast_unicode(kernel_id, 'ascii')
107 self.session = Session(config=self.config)
107 self.session = Session(config=self.config)
108 self.save_on_message = self.on_message
108 self.save_on_message = self.on_message
109 self.on_message = self.on_first_message
109 self.on_message = self.on_first_message
110
110
111 def _inject_cookie_message(self, msg):
111 def _inject_cookie_message(self, msg):
112 """Inject the first message, which is the document cookie,
112 """Inject the first message, which is the document cookie,
113 for authentication."""
113 for authentication."""
114 if not PY3 and isinstance(msg, unicode):
114 if not PY3 and isinstance(msg, unicode):
115 # Cookie constructor doesn't accept unicode strings
115 # Cookie constructor doesn't accept unicode strings
116 # under Python 2.x for some reason
116 # under Python 2.x for some reason
117 msg = msg.encode('utf8', 'replace')
117 msg = msg.encode('utf8', 'replace')
118 try:
118 try:
119 identity, msg = msg.split(':', 1)
119 identity, msg = msg.split(':', 1)
120 self.session.session = cast_unicode(identity, 'ascii')
120 self.session.session = cast_unicode(identity, 'ascii')
121 except Exception:
121 except Exception:
122 logging.error("First ws message didn't have the form 'identity:[cookie]' - %r", msg)
122 logging.error("First ws message didn't have the form 'identity:[cookie]' - %r", msg)
123
123
124 try:
124 try:
125 self.request._cookies = SimpleCookie(msg)
125 self.request._cookies = SimpleCookie(msg)
126 except:
126 except:
127 self.log.warn("couldn't parse cookie string: %s",msg, exc_info=True)
127 self.log.warn("couldn't parse cookie string: %s",msg, exc_info=True)
128
128
129 def on_first_message(self, msg):
129 def on_first_message(self, msg):
130 self._inject_cookie_message(msg)
130 self._inject_cookie_message(msg)
131 if self.get_current_user() is None:
131 if self.get_current_user() is None:
132 self.log.warn("Couldn't authenticate WebSocket connection")
132 self.log.warn("Couldn't authenticate WebSocket connection")
133 raise web.HTTPError(403)
133 raise web.HTTPError(403)
134 self.on_message = self.save_on_message
134 self.on_message = self.save_on_message
General Comments 0
You need to be logged in to leave comments. Login now