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