Mercurial > libervia-web
annotate libervia/server/websockets.py @ 1301:ff44f822bfdd
browser (photos): albums can now be deleted:
when albums are deleted, there are removed from list of interest, and all photos inside
are also deleted.
Doesn't check permissions before deletion yet, so the button is always present even when it
may fail.
author | Goffi <goffi@goffi.org> |
---|---|
date | Fri, 19 Jun 2020 16:47:51 +0200 |
parents | f511f8fbbf8a |
children | 822bd0139769 |
rev | line source |
---|---|
1239 | 1 #!/usr/bin/env python3 |
2 | |
995 | 3 |
4 # Libervia: a Salut à Toi frontend | |
1237 | 5 # Copyright (C) 2011-2020 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(): |
1216 | 49 cookie_name = "TWISTED_SECURE_SESSION" |
1203
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
50 else: |
1216 | 51 cookie_name = "TWISTED_SESSION" |
52 cookie_value = server_request.getCookie(cookie_name.encode('utf-8')) | |
1203
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: |
1216 | 56 raise ValueError("missing expected cookie header") |
1203
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(';'))} |
1216 | 58 if self.cookies[cookie_name] != cookie_value.decode('utf-8'): |
1203
251eba911d4d
server (websockets): fixed websocket handling on HTTPS connections:
Goffi <goffi@goffi.org>
parents:
1144
diff
changeset
|
59 raise exceptions.PermissionError( |
1216 | 60 "Bad cookie value, this should never happen.\n" |
61 "headers: {headers}".format(headers=ws_protocol.http_headers)) | |
1203
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): | |
1216 | 97 prefix = LIBERVIA_PROTOCOL + "_" |
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( |
1216 | 105 types.ConnectionDeny.NOT_IMPLEMENTED, "Can't use this subprotocol" |
1113
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: | |
1216 | 109 log.warning(_("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( |
1216 | 111 types.ConnectionDeny.FORBIDDEN, "Bad token, please reload page" |
1113
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 _( |
1216 | 122 "Websocket opened for {page} (token: {token})".format( |
1113
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( |
1216 | 134 _("Not valid JSON, ignoring data: {msg}\n{data}").format( |
1113
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 _( |
1216 | 147 'No "on_data" method set on dynamic page, ignoring data:\n{data}' |
1113
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): | |
1206
d2e2bf14f2e2
server (websocket): don't crash if self.page doesn't exist while onClose is called
Goffi <goffi@goffi.org>
parents:
1203
diff
changeset
|
154 try: |
d2e2bf14f2e2
server (websocket): don't crash if self.page doesn't exist while onClose is called
Goffi <goffi@goffi.org>
parents:
1203
diff
changeset
|
155 page = self.page |
d2e2bf14f2e2
server (websocket): don't crash if self.page doesn't exist while onClose is called
Goffi <goffi@goffi.org>
parents:
1203
diff
changeset
|
156 except AttributeError: |
d2e2bf14f2e2
server (websocket): don't crash if self.page doesn't exist while onClose is called
Goffi <goffi@goffi.org>
parents:
1203
diff
changeset
|
157 log.debug( |
1216 | 158 "page is not available, the socket was probably not opened cleanly.\n" |
159 "reason: {reason}".format(reason=reason)) | |
1206
d2e2bf14f2e2
server (websocket): don't crash if self.page doesn't exist while onClose is called
Goffi <goffi@goffi.org>
parents:
1203
diff
changeset
|
160 return |
d2e2bf14f2e2
server (websocket): don't crash if self.page doesn't exist while onClose is called
Goffi <goffi@goffi.org>
parents:
1203
diff
changeset
|
161 page.onSocketClose(self.request) |
995 | 162 |
1113
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
163 log.debug( |
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
164 _( |
1216 | 165 "Websocket closed for {page} (token: {token}). {reason}".format( |
1113
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
166 page=self.page, |
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
167 token=self.token, |
1216 | 168 reason="" |
1113
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
169 if wasClean |
1216 | 170 else _("Reason: {reason}").format(reason=reason), |
1113
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
171 ) |
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
172 ) |
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
173 ) |
995 | 174 |
175 @classmethod | |
176 def getBaseURL(cls, host, secure): | |
1216 | 177 return "ws{sec}://localhost:{port}".format( |
1113
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
178 sec="s" if secure else "", |
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
179 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
|
180 ) |
995 | 181 |
182 @classmethod | |
183 def getResource(cls, host, secure): | |
184 if cls.host is None: | |
185 cls.host = host | |
186 factory = websocket.WebSocketServerFactory(cls.getBaseURL(host, secure)) | |
187 factory.protocol = cls | |
188 return resource.WebSocketResource(factory) | |
189 | |
190 @classmethod | |
191 def registerToken(cls, token, page, request): | |
192 if token in cls.tokens_map: | |
1216 | 193 raise exceptions.ConflictError(_("This token is already registered")) |
1113
cdd389ef97bc
server: code style reformatting using black
Goffi <goffi@goffi.org>
parents:
1054
diff
changeset
|
194 cls.tokens_map[token] = {"page": page, "request": request} |