Mercurial > libervia-web
diff 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 |
line wrap: on
line diff
--- a/libervia/server/websockets.py Fri Jul 12 14:58:11 2019 +0200 +++ b/libervia/server/websockets.py Sun Jul 14 14:45:51 2019 +0200 @@ -17,21 +17,78 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. + +import json +from twisted.internet import error +from autobahn.twisted import websocket +from autobahn.twisted import resource as resource +from autobahn.websocket import types +from sat.core import exceptions from sat.core.i18n import _ from sat.core.log import getLogger log = getLogger(__name__) -from sat.core import exceptions - -from autobahn.twisted import websocket -from autobahn.twisted import resource as resource -from autobahn.websocket import types - -import json LIBERVIA_PROTOCOL = "libervia_page" +class WebsocketRequest(object): + """Wrapper around autobahn's ConnectionRequest and Twisted's server.Request + + This is used to have a common interface in Libervia page with request object + """ + + def __init__(self, ws_protocol, connection_request, server_request): + """ + @param connection_request: websocket request + @param serveur_request: original request of the page + """ + self.ws_protocol = ws_protocol + self.ws_request = connection_request + if self.isSecure(): + cookie_string = "TWISTED_SECURE_SESSION" + else: + cookie_string = "TWISTED_SESSION" + cookie_value = server_request.getCookie(cookie_string) + try: + raw_cookies = ws_protocol.http_headers['cookie'] + except KeyError: + raise ValueError(u"missing expected cookie header") + self.cookies = {k:v for k,v in (c.split('=') for c in raw_cookies.split(';'))} + if self.cookies[cookie_string] != cookie_value: + raise exceptions.PermissionError( + u"Bad cookie value, this should never happen.\n" + u"headers: {headers}".format(headers=ws_protocol.http_headers)) + + self.template_data = server_request.template_data + self.data = server_request.data + self.session = server_request.getSession() + self._signals_registered = server_request._signals_registered + self._signals_cache = server_request._signals_cache + # signal id is needed to link original request with signal handler + self.signal_id = server_request._signal_id + + def isSecure(self): + return self.ws_protocol.factory.isSecure + + def getSession(self, sessionInterface=None): + try: + self.session.touch() + except (error.AlreadyCalled, error.AlreadyCancelled): + # Session has already expired. + self.session = None + + if sessionInterface: + return self.session.getComponent(sessionInterface) + + return self.session + + def sendData(self, type_, **data): + assert "type" not in data + data["type"] = type_ + self.ws_protocol.sendMessage(json.dumps(data, ensure_ascii=False).encode("utf8")) + + class LiberviaPageWSProtocol(websocket.WebSocketServerProtocol): host = None tokens_map = {} @@ -54,8 +111,9 @@ types.ConnectionDeny.FORBIDDEN, u"Bad token, please reload page" ) self.token = token - self.page = self.tokens_map[token]["page"] - self.request = self.tokens_map[token]["request"] + token_map = self.tokens_map.pop(token) + self.page = token_map["page"] + self.request = WebsocketRequest(self, request, token_map["request"]) return protocol def onOpen(self): @@ -66,7 +124,6 @@ ) ) ) - self.request.sendData = self.sendJSONData self.page.onSocketOpen(self.request) def onMessage(self, payload, isBinary): @@ -94,21 +151,8 @@ cb(page, self.request, data_json) def onClose(self, wasClean, code, reason): - try: - token = self.token - except AttributeError: - log.warning(_(u"Websocket closed but no token is associated")) - return - self.page.onSocketClose(self.request) - try: - del self.tokens_map[token] - del self.request.sendData - except (KeyError, AttributeError): - raise exceptions.InternalError( - _(u"Token or sendData doesn't exist, this should never happen!") - ) log.debug( _( u"Websocket closed for {page} (token: {token}). {reason}".format( @@ -121,11 +165,6 @@ ) ) - def sendJSONData(self, type_, **data): - assert "type" not in data - data["type"] = type_ - self.sendMessage(json.dumps(data, ensure_ascii=False).encode("utf8")) - @classmethod def getBaseURL(cls, host, secure): return u"ws{sec}://localhost:{port}".format(