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