diff --git a/pantalaimon/client.py b/pantalaimon/client.py index b1abada..2396d1a 100644 --- a/pantalaimon/client.py +++ b/pantalaimon/client.py @@ -1,6 +1,6 @@ import asyncio from pprint import pformat -from typing import Any, Dict +from typing import Any, Dict, Optional from nio import (AsyncClient, ClientConfig, EncryptionError, GroupEncryptionError, KeysQueryResponse, MegolmEvent, @@ -110,6 +110,71 @@ class PanClient(AsyncClient): content ) + def pan_decrypt_event(self, event_dict, room_id=None): + # type: (Dict[Any, Any], Optional[str]) -> () + event = RoomEncryptedEvent.parse_event(event_dict) + + if not event.room_id: + event.room_id = room_id + + if not isinstance(event, MegolmEvent): + logger.warn("Encrypted event is not a megolm event:" + "\n{}".format(pformat(event_dict))) + return None + + try: + decrypted_event = self.decrypt_event(event) + logger.info("Decrypted event: {}".format(decrypted_event)) + event_dict["type"] = "m.room.message" + + # TODO support other event types + # This should be best done in nio, modify events so they + # keep the dictionary from which they are built in a source + # attribute. + event_dict["content"] = { + "msgtype": "m.text", + "body": decrypted_event.body + } + + if decrypted_event.formatted_body: + event_dict["content"]["formatted_body"] = ( + decrypted_event.formatted_body) + event_dict["content"]["format"] = decrypted_event.format + + event_dict["decrypted"] = True + event_dict["verified"] = decrypted_event.verified + + except EncryptionError as error: + logger.warn(error) + return + + def decrypt_messages_body(self, body): + # type: (Dict[Any, Any]) -> Dict[Any, Any] + """Go through a messages response and decrypt megolm encrypted events. + + Args: + body (Dict[Any, Any]): The dictionary of a Sync response. + + Returns the json response with decrypted events. + """ + if "chunk" not in body: + return body + + logger.info("Decrypting room messages") + + for event in body["chunk"]: + if "type" not in event: + continue + + if event["type"] != "m.room.encrypted": + logger.debug("Event is not encrypted: " + "\n{}".format(pformat(event))) + continue + + self.pan_decrypt_event(event) + + return body + def decrypt_sync_body(self, body): # type: (Dict[Any, Any]) -> Dict[Any, Any] """Go through a json sync response and decrypt megolm encrypted events. @@ -119,6 +184,7 @@ class PanClient(AsyncClient): Returns the json response with decrypted events. """ + logger.info("Decrypting sync") for room_id, room_dict in body["rooms"]["join"].items(): try: if not self.rooms[room_id].encrypted: @@ -131,43 +197,6 @@ class PanClient(AsyncClient): continue for event in room_dict["timeline"]["events"]: - if event["type"] != "m.room.encrypted": - logger.info("Event is not encrypted: " - "\n{}".format(pformat(event))) - continue - - parsed_event = RoomEncryptedEvent.parse_event(event) - parsed_event.room_id = room_id - - if not isinstance(parsed_event, MegolmEvent): - logger.warn("Encrypted event is not a megolm event:" - "\n{}".format(pformat(event))) - continue - - try: - decrypted_event = self.decrypt_event(parsed_event) - logger.info("Decrypted event: {}".format(decrypted_event)) - event["type"] = "m.room.message" - - # TODO support other event types - # This should be best done in nio, modify events so they - # keep the dictionary from which they are built in a source - # attribute. - event["content"] = { - "msgtype": "m.text", - "body": decrypted_event.body - } - - if decrypted_event.formatted_body: - event["content"]["formatted_body"] = ( - decrypted_event.formatted_body) - event["content"]["format"] = decrypted_event.format - - event["decrypted"] = True - event["verified"] = decrypted_event.verified - - except EncryptionError as error: - logger.warn(error) - continue + self.pan_decrypt_event(event, room_id) return body diff --git a/pantalaimon/daemon.py b/pantalaimon/daemon.py index 37dce79..7722d1d 100755 --- a/pantalaimon/daemon.py +++ b/pantalaimon/daemon.py @@ -319,6 +319,34 @@ class ProxyDaemon: text=await response.text() ) + async def messages(self, request): + access_token = self.get_access_token(request) + + if not access_token: + return self._missing_token + + try: + client_info = self.client_info[access_token] + client = self.pan_clients[client_info.user_id] + except KeyError: + return self._unknown_token + + response = await self.forward_request(request) + + if response.status == 200: + json_response = await response.json() + json_response = client.decrypt_messages_body(json_response) + + return web.Response( + status=response.status, + text=json.dumps(json_response) + ) + else: + return web.Response( + status=response.status, + text=await response.text() + ) + async def to_web_response(self, response): return web.Response(status=response.status, text=await response.text()) @@ -396,6 +424,7 @@ async def init(homeserver, http_proxy, ssl): app.add_routes([ web.post("/_matrix/client/r0/login", proxy.login), web.get("/_matrix/client/r0/sync", proxy.sync), + web.get("/_matrix/client/r0/rooms/{room_id}/messages", proxy.messages), web.put( r"/_matrix/client/r0/rooms/{room_id}/send/{event_type}/{txnid}", proxy.send_message