Mercurial > libervia-web
annotate libervia/server/websockets.py @ 1203:251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Original request used to retrieve a page was stored on dynamic pages, but after the end of
it, the channel was deleted, resulting in a isSecure() always returning False, and
troubles in chain leading to the the use of the wrong session object. This patch fixes
this by reworking the way original request is used, and creating a new wrapping class
allowing to keep an API similar to iweb.IRequest, with data coming from both the original
request and the websocket request.
fix 327
author | Goffi <goffi@goffi.org> |
---|---|
date | Sun, 14 Jul 2019 14:45:51 +0200 |
parents | 2af117bfe6cc |
children | d2e2bf14f2e2 |
rev | line source |
---|---|
995 | 1 #!/usr/bin/python |
2 # -*- coding: utf-8 -*- | |
3 | |
4 # Libervia: a Salut à Toi frontend | |
1144 | 5 # Copyright (C) 2011-2019 Jérôme Poisson <goffi@goffi.org> |
995 | 6 |
7 # This program is free software: you can redistribute it and/or modify | |
8 # it under the terms of the GNU Affero General Public License as published by | |
9 # the Free Software Foundation, either version 3 of the License, or | |
10 # (at your option) any later version. | |
11 | |
12 # This program is distributed in the hope that it will be useful, | |
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 # GNU Affero General Public License for more details. | |
16 | |
17 # You should have received a copy of the GNU Affero General Public License | |
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
19 | |
1203
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
20 |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
21 import json |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
22 from twisted.internet import error |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
23 from autobahn.twisted import websocket |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
24 from autobahn.twisted import resource as resource |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
25 from autobahn.websocket import types |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
26 from sat.core import exceptions |
995 | 27 from sat.core.i18n import _ |
28 from sat.core.log import getLogger | |
1113
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
29 |
995 | 30 log = getLogger(__name__) |
31 | |
1113
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
32 LIBERVIA_PROTOCOL = "libervia_page" |
995 | 33 |
34 | |
1203
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
35 class WebsocketRequest(object): |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
36 """Wrapper around autobahn's ConnectionRequest and Twisted's server.Request |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
37 |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
38 This is used to have a common interface in Libervia page with request object |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
39 """ |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
40 |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
41 def __init__(self, ws_protocol, connection_request, server_request): |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
42 """ |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
43 @param connection_request: websocket request |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
44 @param serveur_request: original request of the page |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
45 """ |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
46 self.ws_protocol = ws_protocol |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
47 self.ws_request = connection_request |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
48 if self.isSecure(): |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
49 cookie_string = "TWISTED_SECURE_SESSION" |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
50 else: |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
51 cookie_string = "TWISTED_SESSION" |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
52 cookie_value = server_request.getCookie(cookie_string) |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
53 try: |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
54 raw_cookies = ws_protocol.http_headers['cookie'] |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
55 except KeyError: |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
56 raise ValueError(u"missing expected cookie header") |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
57 self.cookies = {k:v for k,v in (c.split('=') for c in raw_cookies.split(';'))} |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
58 if self.cookies[cookie_string] != cookie_value: |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
59 raise exceptions.PermissionError( |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
60 u"Bad cookie value, this should never happen.\n" |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
61 u"headers: {headers}".format(headers=ws_protocol.http_headers)) |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
62 |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
63 self.template_data = server_request.template_data |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
64 self.data = server_request.data |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
65 self.session = server_request.getSession() |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
66 self._signals_registered = server_request._signals_registered |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
67 self._signals_cache = server_request._signals_cache |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
68 # signal id is needed to link original request with signal handler |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
69 self.signal_id = server_request._signal_id |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
70 |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
71 def isSecure(self): |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
72 return self.ws_protocol.factory.isSecure |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
73 |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
74 def getSession(self, sessionInterface=None): |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
75 try: |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
76 self.session.touch() |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
77 except (error.AlreadyCalled, error.AlreadyCancelled): |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
78 # Session has already expired. |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
79 self.session = None |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
80 |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
81 if sessionInterface: |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
82 return self.session.getComponent(sessionInterface) |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
83 |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
84 return self.session |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
85 |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
86 def sendData(self, type_, **data): |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
87 assert "type" not in data |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
88 data["type"] = type_ |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
89 self.ws_protocol.sendMessage(json.dumps(data, ensure_ascii=False).encode("utf8")) |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
90 |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
91 |
995 | 92 class LiberviaPageWSProtocol(websocket.WebSocketServerProtocol): |
93 host = None | |
94 tokens_map = {} | |
95 | |
96 def onConnect(self, request): | |
1113
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
97 prefix = LIBERVIA_PROTOCOL + u"_" |
995 | 98 for protocol in request.protocols: |
99 if protocol.startswith(prefix): | |
1113
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
100 token = protocol[len(prefix) :].strip() |
995 | 101 if token: |
102 break | |
103 else: | |
1113
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
104 raise types.ConnectionDeny( |
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
105 types.ConnectionDeny.NOT_IMPLEMENTED, u"Can't use this subprotocol" |
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
106 ) |
995 | 107 |
108 if token not in self.tokens_map: | |
109 log.warning(_(u"Can't activate page socket: unknown token")) | |
1113
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
110 raise types.ConnectionDeny( |
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
111 types.ConnectionDeny.FORBIDDEN, u"Bad token, please reload page" |
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
112 ) |
995 | 113 self.token = token |
1203
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
114 token_map = self.tokens_map.pop(token) |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
115 self.page = token_map["page"] |
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
116 self.request = WebsocketRequest(self, request, token_map["request"]) |
995 | 117 return protocol |
118 | |
119 def onOpen(self): | |
1113
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
120 log.debug( |
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
121 _( |
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
122 u"Websocket opened for {page} (token: {token})".format( |
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
123 page=self.page, token=self.token |
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
124 ) |
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
125 ) |
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
126 ) |
995 | 127 self.page.onSocketOpen(self.request) |
128 | |
129 def onMessage(self, payload, isBinary): | |
130 try: | |
1113
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
131 data_json = json.loads(payload.decode("utf8")) |
995 | 132 except ValueError as e: |
1113
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
133 log.warning( |
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
134 _(u"Not valid JSON, ignoring data: {msg}\n{data}").format( |
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
135 msg=e, data=payload |
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
136 ) |
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
137 ) |
995 | 138 return |
1113
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
139 # we request page first, to raise an AttributeError |
995 | 140 # if it is not set (which should never happen) |
141 page = self.page | |
142 try: | |
143 cb = page.on_data | |
144 except AttributeError: | |
1113
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
145 log.warning( |
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
146 _( |
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
147 u'No "on_data" method set on dynamic page, ignoring data:\n{data}' |
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
148 ).format(data=data_json) |
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
149 ) |
995 | 150 else: |
151 cb(page, self.request, data_json) | |
152 | |
153 def onClose(self, wasClean, code, reason): | |
154 self.page.onSocketClose(self.request) | |
155 | |
1113
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
156 log.debug( |
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
157 _( |
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
158 u"Websocket closed for {page} (token: {token}). {reason}".format( |
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
159 page=self.page, |
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
160 token=self.token, |
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
161 reason=u"" |
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
162 if wasClean |
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
163 else _(u"Reason: {reason}").format(reason=reason), |
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
164 ) |
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
165 ) |
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
166 ) |
995 | 167 |
168 @classmethod | |
169 def getBaseURL(cls, host, secure): | |
170 return u"ws{sec}://localhost:{port}".format( | |
1113
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
171 sec="s" if secure else "", |
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
172 port=cls.host.options["port_https" if secure else "port"], |
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
173 ) |
995 | 174 |
175 @classmethod | |
176 def getResource(cls, host, secure): | |
177 if cls.host is None: | |
178 cls.host = host | |
179 factory = websocket.WebSocketServerFactory(cls.getBaseURL(host, secure)) | |
180 factory.protocol = cls | |
181 return resource.WebSocketResource(factory) | |
182 | |
183 @classmethod | |
184 def registerToken(cls, token, page, request): | |
185 if token in cls.tokens_map: | |
1113
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
186 raise exceptions.ConflictError(_(u"This token is already registered")) |
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
187 cls.tokens_map[token] = {"page": page, "request": request} |