def matrix_all_chat_message(self, room: MatrixRoom, event: Dict): """ Handle a message sent to Matrix all-chat room. Allows manual sending of xmpp messages: "/m target_jid your message here". Sends a notice with the expected format if it isn't there by default. :param room: Matrix room object representing the all-chat room :param event: The Matrix event that was received. Assumed to be an m.room.message . """ # Always ignore our own messages if event['sender'] == self.bot_id: return logger.debug('matrix_all_chat_message: {} {}'.format(room.room_id, str(event))) if event['content']['msgtype'] == 'm.text': message_body = event['content']['body'] message_parts = message_body.split() if message_parts[0] == '/m': jid = message_parts[1] payload = message_body[message_body.find(jid) + len(jid) + 1:] logger.info('sending manual message to '+ jid + ' : ' + payload) self.xmpp.send_message(mto=jid, mbody=payload, mtype='chat') else: room.send_notice('Expected message format: "/m DEST_JID your message here"')
def is_room_member(self, room_id, user_id): try: r = Room(self.client, room_id) return user_id in list(r.get_joined_members().keys()) except Exception as e: return False return False
def set_room(self, room_id): try: from matrix_client.room import Room except ModuleNotFoundError: raise ModuleNotFoundError("No matrix client module found, please run j.clients.matrix.install() first") self.room = Room(client=self.client, room_id=room_id)
def bot_cmd_fug(room: Room, event: dict): """ Send ``fug -_-``, even more useful than ``meh~``. :param Room room: Matrix room :param dict event: event content """ room.send_notice("fug -_-")
def bot_cmd_meh(room: Room, event: dict): """ Send ``meh~``, very useful. :param Room room: Matrix room :param dict event: event content """ room.send_notice("meh~")
def is_room_member(self, room_id, user_id): try: r = Room(self.client, room_id) room_members = [m.user_id for m in r.get_joined_members()] return user_id in room_members except Exception as e: self.logger.error("Error when fetching room members: %s" % e) return False return False
def safe_send_message(self, room: Room, body: str, html: str): logger.debug(f"safe_send_message({room}, {body}, {html})") members = room.get_joined_members() logger.debug(f"room joined members: {members}") for u in members: if u.user_id not in self.allowed_users: body = "I'm sorry, but not everyone in this room has clearance, so I'm not going to respond." html = None break try: room.send_html(html, body) except MatrixError as me: logger.error(me)
def bot_cmd_bashorg(room: Room, event: dict): """ Send ``смешнявка с башорга``! **BEWARE: dirty hacks!** :param Room room: Matrix room :param dict event: event content """ log.debug("started dl") raw_text = requests.get("http://bash.im/forweb/?u").text without_spaces_text = raw_text.replace("\' + \'", "") log.debug(f"pre-parse ${without_spaces_text}") quote = REGEX_QUOTE_EXTRACT.search(without_spaces_text).group(1) log.debug(f"finally, quote \'${quote}\'") room.send_html(quote, quote)
def make_message(convert_to_hex: bool = False, overwrite_data=None): from matrix_client.room import Room room = Room(None, '!roomID:server') if not overwrite_data: message = SecretRequest( message_identifier=random.randint(0, UINT64_MAX), payment_identifier=1, secrethash=UNIT_SECRETHASH, amount=1, expiration=10, ) message.sign(HOP1_KEY) data = message.encode() if convert_to_hex: data = '0x' + data.hex() else: data = json.dumps(message.to_dict()) else: data = overwrite_data event = dict( type='m.room.message', sender=USERID1, content={ 'msgtype': 'm.text', 'body': data, }, ) return room, event
def matrix_all_chat_message(self, room: MatrixRoom, event: Dict): """ Handle a message sent to Matrix all-chat room. Currently just sends a warning that nobody will hear your message. :param room: Matrix room object representing the all-chat room :param event: The Matrix event that was received. Assumed to be an m.room.message . """ # Always ignore our own messages if event['sender'] == self.bot_id: return logging.debug('matrix_all_chat_message: {} {}'.format( room.room_id, str(event))) room.send_notice('Don\'t talk in here! Nobody gets your messages.')
def send_message(self, room_id: str, text: str, name: str = None, avatar_url: str = None, file_url: str = None, file_name: str = None, file_mimetype: str = None, file_authorization: str = None): room = Room(self._client, room_id) current_avatar_url = None current_name = None avatar_uri = None if room_id in self._cache['rooms']: current_name = self._cache['rooms'][room_id]['name'] current_avatar_url = self._cache['rooms'][room_id]['avatar_url'] else: self._cache['rooms'][room_id] = {} if avatar_url is not None and avatar_url != current_avatar_url: if avatar_url in self._cache['uploaded_avatars']: avatar_uri = self._cache['uploaded_avatars'][avatar_url] print("Use cache avatar for an user " + avatar_uri + " (" + avatar_url + ")") else: avatar_content = request.urlopen(avatar_url).read() avatar_uri = self._client.upload(avatar_content, 'image/png') self._cache['uploaded_avatars'][avatar_url] = avatar_uri print("Uploaded a new avatar for an user " + avatar_uri + " (" + avatar_url + ")") if (name is not None and name is not current_name) or avatar_uri is not None: room.set_user_profile(displayname=name, avatar_url=avatar_uri) self._cache['rooms'][room_id]['name'] = name self._cache['rooms'][room_id]['avatar_url'] = avatar_url self.__save_cache() if file_url is not None and file_mimetype is not None and file_name is not None: rq = Request(file_url) rq.add_header('Authorization', file_authorization) file_content = urlopen(rq).read() file_uri = self._client.upload(file_content, file_mimetype) if file_mimetype in ['image/png', 'image/jpeg']: room.send_image(file_uri, file_name) else: room.send_file(file_uri, file_name) if text is not None: room.send_text(text)
def write_media(self, media_type: str, room: Room, url: str) -> None: """Get media, upload it and post to room """ # image is the only media type supported right now if media_type != 'image': logger.error('%s as media type is not supported', media_type) return None # getting image and analyze it logger.info('download %s', url) image_data = get_image(url) logger.debug('got image_data: %s', image_data) if not image_data: logger.error('got no image_data') return # analyze image file and create image info dict media_info = {} # type: Dict[str, Union[str, int, BytesIO, None]] # getting mimetype media_info['mimetype'] = image_data.get('content-type') if not media_info['mimetype']: media_info['mimetype'] = mimetypes.guess_type(url)[0] # getting name name = urlsplit(url).path.split('/')[-1] # image size media_info['h'] = image_data.get('height') media_info['w'] = image_data.get('width') logger.debug('media_info content: %s', media_info) # upload it to homeserver logger.info('upload file') uploaded = self.client.upload(image_data['image'], media_info['mimetype']) logger.debug('upload: %s', uploaded) # send image to room logger.info('send media: %s', name) room.send_image(uploaded, name, **media_info)
def eventCallback(self, event): if 'type' in event and 'room_id' in event and 'content' in event and event[ 'type'] == 'm.room.message': room = Room(self.client, event['room_id']) self.messageReceived.emit(room, event['sender'], event['content'], time.time() - event['unsigned']['age']) if 'type' in event and 'room_id' in event and 'content' in event and event[ 'type'] == 'm.room.canonical_alias': self.roomUpdated.emit(event['room_id'], 'canonical_alias', event['content']['alias'])
def __init__(self): self.BOTUSERNAME = "******" self.BOTPASSWORD = "******" self.BOTSERVO = "matrix.org" self.RID = "!RnpiUpFIsfzZfHdHQf:matrix.org" self.realRID = "!obQcCWaLRAUgiGBvMg:postmarketos.org" self.MainClient = MatrixClient("https://" + self.BOTSERVO) self.token = self.MainClient.login_with_password( username=self.BOTUSERNAME, password=self.BOTPASSWORD) self.APIWrapper = MatrixHttpApi("https://" + self.BOTSERVO, token=self.token) self.target_room = Room(self.MainClient, self.RID) print("ready")
def test_megolm_outbound_persistence(self, device): session = MegolmOutboundSession(max_messages=2, max_age=100000) session.message_count = 1 session.add_device(self.device_id) sessions = {} self.store.load_outbound_sessions(sessions) assert not sessions assert not self.store.get_outbound_session(self.room_id) self.store.save_outbound_session(self.room_id, session) self.store.save_megolm_outbound_devices(self.room_id, {self.device_id}) self.store.load_outbound_sessions(sessions) assert sessions[self.room_id].id == session.id assert sessions[self.room_id].devices == session.devices assert sessions[self.room_id].creation_time == session.creation_time assert sessions[self.room_id].max_messages == session.max_messages assert sessions[self.room_id].message_count == session.message_count assert sessions[self.room_id].max_age == session.max_age saved_session = self.store.get_outbound_session(self.room_id) assert saved_session.id == session.id assert saved_session.devices == session.devices assert saved_session.creation_time == session.creation_time assert saved_session.max_messages == session.max_messages assert saved_session.message_count == session.message_count assert saved_session.max_age == session.max_age sessions.clear() saved_session = self.store.get_outbound_session(self.room_id, sessions) assert sessions[self.room_id].id == session.id self.store.remove_outbound_session(self.room_id) assert not self.store.get_outbound_session(self.room_id) self.store.save_outbound_session(self.room_id, session) saved_session = self.store.get_outbound_session(self.room_id) # Verify the saved devices have been erased with the session assert not saved_session.devices room = Room(None, self.room_id) with pytest.raises(AttributeError): device.megolm_build_encrypted_event(room, {}) assert device.megolm_outbound_sessions[self.room_id].id == session.id self.store.remove_olm_account() assert not self.store.get_outbound_session(self.room_id)
def on_message(self, room: Room, event: Dict) -> None: """Callback for recieved messages Gets events and checks if something can be triggered. """ logger.debug(event) logger.info('stores msg in db') self.store_msg(event) if event['content'].get('msgtype') == 'm.text' and event['sender'] != \ self.uid: # add config to event event['config'] = self.config # gives event to mossbot and watching out for a return message msg = MOSS.serve(event) if msg and msg.data: if msg.type == 'text': logger.info('sending text msg...') room.send_text(msg.data) elif msg.type == 'notice': logger.info('sending notice msg...') room.send_notice(msg.data) elif msg.type == 'html': logger.info('sending html msg...') room.send_html(msg.data) elif msg.type == 'image': logger.info('sending image msg...') self.write_media('image', room, msg.data) else: logger.error('could not recognize msg type "%s"', msg[0]) elif msg and msg.type == 'skip': logger.info('skipping msg...') else: logger.debug('no matching in event')
class MatrixClient(JSBaseConfigClient): def __init__(self, instance, data=None, parent=None, interactive=False): if not data: data = {} JSBaseConfigClient.__init__(self, instance=instance, data=data, parent=parent, template=TEMPLATE, interactive=interactive) self._client = None self.room = None @property def client(self): if self._client is None: try: from matrix_client.client import MatrixClient except ModuleNotFoundError: raise ModuleNotFoundError( "No matrix client module found, please run j.clients.matrix.install() first" ) self._client = MatrixClient(base_url=self.config.data['base_url']) if self.config.data['user'] and self.config.data['password_']: self._client.login_with_password( username=self.config.data['user'], password=self.config.data['password_']) else: jwt = j.clients.itsyouonline.get().jwt response = self._client.api.login(login_type="m.login.jwt", token=jwt) self._client.user_id = response["user_id"] self._client.token = response["access_token"] self._client.hs = response["home_server"] self._client.api.token = self._client.token return self._client def create_user(self, username, password): return self.client.register_with_password(username, password) def create_room(self, alias=None, is_public=False, invitees=()): """ Create a new room on the homeserver. Args: alias (str): The canonical_alias of the room. is_public (bool): The public/private visibility of the room. invitees (str[]): A set of user ids to invite into the room. Returns: Room Raises: MatrixRequestError """ return self.client.create_room(alias=alias, is_public=is_public, invitees=invitees) def join_room(self, room_id_or_alias): """ Join a room. Args: room_id_or_alias (str): Room ID or an alias. Returns: Room Raises: MatrixRequestError """ return self.client.join_room(room_id_or_alias) def get_rooms(self): """ Return a dict of {room_id: Room objects} that the user has joined. Returns: Room{}: Rooms the user has joined. """ return self.client.get_rooms() def get_user(self, user_id): """ Return a User by their id. NOTE: This function only returns a user object, it does not verify the user with the Home Server. Args: user_id (str): The matrix user id of a user. """ return self.client.get_user(user_id=user_id) def upload(self, content, content_type): """ Upload content to the home server and receive a MXC url. Args: content (bytes): The data of the content. content_type (str): The mime-type of the content. """ return self.client.upload(content, content_type) def set_room(self, room_id): try: from matrix_client.room import Room except ModuleNotFoundError: raise ModuleNotFoundError( "No matrix client module found, please run j.clients.matrix.install() first" ) self.room = Room(client=self.client, room_id=room_id) @room_check def send_text(self, text): """ Send a plain text message to the room. Args: text (str): The message to send """ return self.room.send_text(text) @room_check def send_html(self, html, body=None, msg_type="m.text"): """Send an html formatted message.""" return self.room.send_html(html, body, msg_type) @room_check def set_account_data(self, account_type, account_data): return self.room.set_account_data(account_type, account_data) @room_check def get_tags(self): return self.room.get_tags() @room_check def remove_tag(self, tag): return self.room.remove_tag(tag) @room_check def add_tag(self, tag, order=None, content=None): return self.room.add_tag(tag, order, content) @room_check def send_emote(self, text): """ Send a emote (/me style) message to the room. Args: text (str): The message to send """ return self.room.send_emote(text) @room_check def send_notice(self, text): return self.client.api.send_notice(self.room.room_id, text) @room_check def send_image(self, url, name, **imageinfo): """ Send a pre-uploaded image to the room. See http://matrix.org/docs/spec/r0.0.1/client_server.html#m-image for imageinfo Args: url (str): The mxc url of the image. name (str): The filename of the image. imageinfo (): Extra information about the image. """ return self.room.send_image(url, name, **imageinfo) @room_check def send_location(self, geo_uri, name, thumb_url=None, **thumb_info): """ Send a location to the room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#m-location for thumb_info Args: geo_uri (str): The geo uri representing the location. name (str): Description for the location. thumb_url (str): URL to the thumbnail of the location. thumb_info (): Metadata about the thumbnail, type ImageInfo. """ return self.room.send_location(geo_uri, name, thumb_url, **thumb_info) @room_check def send_video(self, url, name, **videoinfo): """ Send a pre-uploaded video to the room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#m-video for videoinfo Args: url (str): The mxc url of the video. name (str): The filename of the video. videoinfo (): Extra information about the video. """ return self.room.send_video(url, name, **videoinfo) @room_check def send_audio(self, url, name, **audioinfo): """ Send a pre-uploaded audio to the room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#m-audio for audioinfo Args: url (str): The mxc url of the audio. name (str): The filename of the audio. audioinfo (): Extra information about the audio. """ return self.room.send_audio(url, name, **audioinfo)
def matrix_control_message(self, room: MatrixRoom, event: Dict): """ Handle a message sent to the control room. Does nothing unless a valid command is received: refresh Probes the presence of all XMPP contacts, and updates the roster. purge Leaves any ((un-mapped and non-special) or empty) Matrix rooms. joinmuc [email protected] Joins a muc leavemuc [email protected] Leaves a muc :param room: Matrix room object representing the control room :param event: The Matrix event that was received. Assumed to be an m.room.message . """ # Always ignore our own messages if event['sender'] == self.bot_id: return logging.debug('matrix_control_message: {} {}'.format( room.room_id, str(event))) if event['content']['msgtype'] == 'm.text': message_body = event['content']['body'] logging.info('Matrix received control message: ' + message_body) message_parts = message_body.split() if len(message_parts) > 0: message_parts[0] = message_parts[0].lower() # what about a empty body? if message_parts[0] == 'refresh': for jid in self.topic_room_id_map.keys(): self.xmpp.send_presence(pto=jid, ptype='probe') self.xmpp.send_presence() self.xmpp.get_roster() elif message_parts[0] == 'purge': self.special_rooms['control'].send_text( 'Purging unused rooms') # Leave from unwanted rooms for room in self.get_unmapped_rooms( ) + self.get_empty_rooms(): logging.info( 'Leaving room {r.room_id} ({r.name}) [{r.topic}]'. format(r=room)) if room.topic.startswith(self.groupchat_flag): room_jid = room.topic[len(self.groupchat_flag):] self.xmpp.plugin['xep_0045'].leaveMUC(room_jid) room.leave() elif len(message_parts) > 1: if message_parts[0] == 'joinmuc': room_jid = message_parts[1] logging.info('XMPP MUC join: {}'.format(room_jid)) self.create_groupchat_room(room_jid) self.xmpp.plugin['xep_0045'].joinMUC( room_jid, self.xmpp_groupchat_nick) elif message_parts[0] == 'leavemuc': room_jid = message_parts[1] logging.info('XMPP MUC leave: {}'.format(room_jid)) self.xmpp.plugin['xep_0045'].leaveMUC( room_jid, self.xmpp_groupchat_nick) room = self.get_room_for_jid(self.groupchat_flag + room_jid) room.leave()
def create_room(alias, is_public=False, invitees=None): # pylint: disable=unused-argument room = Room(client, f"!room_id:ownserver.com") room.canonical_alias = alias return room
def main(): module = AnsibleModule( argument_spec={ "username": { "required": True, "type": "str" }, "password": { "required": True, "type": "str", "no_log": True }, "domain": { "required": False, "type": "str", "default": "http://localhost:8008" }, "join_rule": { "required": False, "type": "str", "choices": ["public", "invite"] }, "history_visibility": { "required": False, "type": "str", "choices": ["invited", "joined", "shared", "world_readable"] }, "invites": { "required": False, "type": "list", "default": [] }, "power_levels": { "required": False, "type": "list", "default": [] }, "room_alias": { "required": False, "type": "str" }, }) client = MatrixClient(module.params['domain']) client.login(module.params["username"], module.params["password"]) room = None changed = False try: room = client.create_room(module.params["room_alias"], invitees=module.params["invites"]) changed = True except MatrixRequestError as e: if json.loads(e.content)["errcode"] == "M_ROOM_IN_USE": room_id = client.api.get_room_id("#" + module.params["room_alias"] + ":" + client.hs) room = Room(client, room_id) for user_id in module.params["invites"]: # Failure can be due to many reasons, one of which is that the user is # already in the room. We interpret failure as lack of changing. changed |= room.invite_user(user_id) else: raise if module.params["power_levels"] != []: content = client.api.get_power_levels(room.room_id) content_changed = False for [user, level] in module.params["power_levels"]: if content["users"].get(user, -1) != level: content["users"][user] = level content_changed = True if content_changed: changed = True client.api.set_power_levels(room.room_id, content) if module.params["join_rule"] is not None: current = get_state(room, "m.room.join_rules") if current is None or current["join_rule"] != module.params[ "join_rule"]: client.api.set_join_rule(room.room_id, module.params["join_rule"]) if module.params["history_visibility"] is not None: current = get_state(room, "m.room.history_visibility") if current is None or current["history_visibility"] != module.params[ "history_visibility"]: changed = True room.send_state_event( "m.room.history_visibility", { "history_visibility": module.params["history_visibility"], }) module.exit_json(changed=changed, meta={"room_id": room.room_id})
def create_room(alias, is_public=False, invitees=None): room = Room(client, f'!room_id:ownserver.com') room.canonical_alias = alias return room
service=args.e, host=args.l, host_display=args.n, host_output=args.o, ) if args.ip4: message += 'IPv4: {ip4}; '.format(ip4=args.ip4) if args.ip6: message += 'IPv6: {ip6}; '.format(ip6=args.ip6) if args.c: message += 'Comment: {c}; '.format(c=args.c) if args.b: message += 'Comment by: {b}; '.format(b=args.b) if args.so: message += 'Service Output: {so}; '.format(so=args.so) print(message) # MESSAGE SENDING if PASSWORD: client = MatrixClient(SERVER) token = client.login_with_password(USERNAME, PASSWORD) elif TOKEN: client = MatrixClient(SERVER, token=TOKEN, user_id=USERNAME) else: sys.exit(0) if ROOMID: room = Room(client, ROOMID) room.send_text(message)
def matrix_control_message(self, room: MatrixRoom, event: Dict): """ Handle a message sent to the control room. Does nothing unless a valid command is received: refresh Probes the presence of all XMPP contacts, and updates the roster. purge Leaves any ((un-mapped and non-special) or empty) Matrix rooms. joinmuc [email protected] Joins a muc leavemuc [email protected] Leaves a muc :param room: Matrix room object representing the control room :param event: The Matrix event that was received. Assumed to be an m.room.message . """ # Always ignore our own messages if event['sender'] == self.bot_id: return logger.debug('matrix_control_message: {} {}'.format(room.room_id, str(event))) if event['content']['msgtype'] == 'm.text': message_body = event['content']['body'] logger.info('Matrix received control message: ' + message_body) message_parts = message_body.split() if len(message_parts) < 1: logger.warning('Received empty control message, ignoring') return if message_parts[0] == 'refresh': for jid in self.topic_room_id_map.keys(): self.xmpp.send_presence(pto=jid, ptype='probe') self.xmpp.send_presence() self.xmpp.get_roster() elif message_parts[0] == 'purge': self.special_rooms['control'].send_text('Purging unused rooms') # Leave from unwanted rooms for room in self.get_unmapped_rooms() + self.get_empty_rooms(): logger.info('Leaving room {r.room_id} ({r.name}) [{r.topic}]'.format(r=room)) if room.topic in self.topic_room_id_map.keys(): self.leave_mapped_room(room.topic) else: room.leave() elif message_parts[0] == 'joinmuc': if len(message_parts) < 2: logger.warning('joinmuc command didn\'t specify a room, ignoring') return room_jid = message_parts[1] logger.info('XMPP MUC join: {}'.format(room_jid)) self.create_groupchat_room(room_jid) self.xmpp.plugin['xep_0045'].joinMUC(room_jid, self.xmpp_groupchat_nick) elif message_parts[0] == 'leavemuc': if len(message_parts) < 2: logger.warning('leavemuc command didn\'t specify a room, ignoring') return room_jid = message_parts[1] room_topic = self.groupchat_flag + room_jid success = self.leave_mapped_room(room_topic) if not success: msg = 'Groupchat {} isn\'t mapped or doesn\'t exist'.format(room_jid) else: msg = 'Left groupchat {}'.format(room_jid) self.special_rooms['control'].send_notice(msg)
class TestCryptoStore(object): # Initialise a store and test some init code device_id = 'AUIETSRN' user_id = '@user:matrix.org' room_id = '!test:example.com' room = Room(None, room_id) user = User(None, user_id, '') room._members[user_id] = user db_name = 'test.db' db_path = mkdtemp() store_conf = { 'db_name': db_name, 'db_path': db_path } store = CryptoStore( user_id, device_id=device_id, db_path=db_path, db_name=db_name) db_filepath = os.path.join(db_path, db_name) assert os.path.exists(db_filepath) store.close() store = CryptoStore( user_id, device_id=device_id, db_path=db_path, db_name=db_name) @pytest.fixture(autouse=True, scope='class') def cleanup(self): yield os.remove(self.db_filepath) @pytest.fixture() def account(self): account = self.store.get_olm_account() if account is None: account = olm.Account() self.store.save_olm_account(account) return account @pytest.fixture() def curve_key(self, account): return account.identity_keys['curve25519'] @pytest.fixture() def ed_key(self, account): return account.identity_keys['ed25519'] @pytest.fixture() def device(self): return OlmDevice(None, self.user_id, self.device_id, store_conf=self.store_conf) def test_olm_account_persistence(self): account = olm.Account() identity_keys = account.identity_keys self.store.remove_olm_account() # Try to load inexisting account saved_account = self.store.get_olm_account() assert saved_account is None # Try to load inexisting account without device_id self.store.device_id = None with pytest.raises(ValueError): self.store.get_olm_account() self.store.device_id = self.device_id # Save and load self.store.save_olm_account(account) saved_account = self.store.get_olm_account() assert saved_account.identity_keys == identity_keys # Save and load without device_id self.store.save_olm_account(account) self.store.device_id = None saved_account = self.store.get_olm_account() assert saved_account.identity_keys == identity_keys assert self.store.device_id == self.device_id # Replace the account, causing foreign keys to be deleted self.store.save_sync_token('test') self.store.replace_olm_account(account) assert self.store.get_sync_token() is None # Load the account from an OlmDevice device = OlmDevice(None, self.user_id, self.device_id, store_conf=self.store_conf) assert device.olm_account.identity_keys == account.identity_keys # Load the account from an OlmDevice, without device_id device = OlmDevice(None, self.user_id, store_conf=self.store_conf) assert device.device_id == self.device_id def test_olm_sessions_persistence(self, account, curve_key, device): session = olm.OutboundSession(account, curve_key, curve_key) sessions = defaultdict(list) self.store.load_olm_sessions(sessions) assert not sessions assert not self.store.get_olm_sessions(curve_key) self.store.save_olm_session(curve_key, session) self.store.load_olm_sessions(sessions) assert sessions[curve_key][0].id == session.id saved_sessions = self.store.get_olm_sessions(curve_key) assert saved_sessions[0].id == session.id sessions.clear() saved_sessions = self.store.get_olm_sessions(curve_key, sessions) assert sessions[curve_key][0].id == session.id # Replace the session when its internal state has changed pickle = session.pickle() session.encrypt('test') self.store.save_olm_session(curve_key, session) saved_sessions = self.store.get_olm_sessions(curve_key) assert saved_sessions[0].pickle != pickle # Load sessions dynamically assert not device.olm_sessions with pytest.raises(AttributeError): device._olm_decrypt(None, curve_key) assert device.olm_sessions[curve_key][0].id == session.id device.olm_sessions.clear() device.device_keys[self.user_id][self.device_id] = device device.olm_ensure_sessions({self.user_id: [self.device_id]}) assert device.olm_sessions[curve_key][0].id == session.id # Test cascade deletion self.store.remove_olm_account() assert not self.store.get_olm_sessions(curve_key) def test_megolm_inbound_persistence(self, curve_key, ed_key, device): out_session = olm.OutboundGroupSession() session = MegolmInboundSession(out_session.session_key, ed_key) session.forwarding_chain.append(curve_key) sessions = defaultdict(lambda: defaultdict(dict)) self.store.load_inbound_sessions(sessions) assert not sessions assert not self.store.get_inbound_session(self.room_id, curve_key, session.id) self.store.save_inbound_session(self.room_id, curve_key, session) self.store.load_inbound_sessions(sessions) assert sessions[self.room_id][curve_key][session.id].id == session.id saved_session = self.store.get_inbound_session(self.room_id, curve_key, session.id) assert saved_session.id == session.id assert saved_session.forwarding_chain == [curve_key] sessions = {} saved_session = self.store.get_inbound_session(self.room_id, curve_key, session.id, sessions) assert sessions[session.id].id == session.id assert not device.megolm_inbound_sessions created = device.megolm_add_inbound_session( self.room_id, curve_key, ed_key, session.id, out_session.session_key) assert not created assert device.megolm_inbound_sessions[self.room_id][curve_key][session.id].id == \ session.id device.megolm_inbound_sessions.clear() content = { 'sender_key': curve_key, 'session_id': session.id, 'algorithm': device._megolm_algorithm, 'device_id': '' } event = { 'sender': '', 'room_id': self.room_id, 'content': content } with pytest.raises(KeyError): device.megolm_decrypt_event(event) assert device.megolm_inbound_sessions[self.room_id][curve_key][session.id].id == \ session.id self.store.remove_olm_account() assert not self.store.get_inbound_session(self.room_id, curve_key, session.id) @pytest.mark.usefixtures('account') def test_megolm_outbound_persistence(self, device): session = MegolmOutboundSession(max_messages=2, max_age=100000) session.message_count = 1 session.add_device(self.device_id) sessions = {} self.store.load_outbound_sessions(sessions) assert not sessions assert not self.store.get_outbound_session(self.room_id) self.store.save_outbound_session(self.room_id, session) self.store.save_megolm_outbound_devices(self.room_id, {self.device_id}) self.store.load_outbound_sessions(sessions) assert sessions[self.room_id].id == session.id assert sessions[self.room_id].devices == session.devices assert sessions[self.room_id].creation_time == session.creation_time assert sessions[self.room_id].max_messages == session.max_messages assert sessions[self.room_id].message_count == session.message_count assert sessions[self.room_id].max_age == session.max_age saved_session = self.store.get_outbound_session(self.room_id) assert saved_session.id == session.id assert saved_session.devices == session.devices assert saved_session.creation_time == session.creation_time assert saved_session.max_messages == session.max_messages assert saved_session.message_count == session.message_count assert saved_session.max_age == session.max_age sessions.clear() saved_session = self.store.get_outbound_session(self.room_id, sessions) assert sessions[self.room_id].id == session.id self.store.remove_outbound_session(self.room_id) assert not self.store.get_outbound_session(self.room_id) self.store.save_outbound_session(self.room_id, session) saved_session = self.store.get_outbound_session(self.room_id) # Verify the saved devices have been erased with the session assert not saved_session.devices room = Room(None, self.room_id) with pytest.raises(AttributeError): device.megolm_build_encrypted_event(room, {}) assert device.megolm_outbound_sessions[self.room_id].id == session.id self.store.remove_olm_account() assert not self.store.get_outbound_session(self.room_id) @pytest.mark.usefixtures('account') def test_device_keys_persistence(self, device): user_devices = {self.user_id: [self.device_id]} device_keys = defaultdict(dict) device._verified = True self.store.load_device_keys(None, device_keys) assert not device_keys assert not self.store.get_device_keys(None, user_devices, device_keys) assert not device_keys device_keys_to_save = {self.user_id: {self.device_id: device}} self.store.save_device_keys(device_keys_to_save) self.store.load_device_keys(None, device_keys) assert device_keys[self.user_id][self.device_id].curve25519 == \ device.curve25519 assert device_keys[self.user_id][self.device_id].verified device_keys.clear() devices = self.store.get_device_keys(None, user_devices)[self.user_id] assert devices[self.device_id].curve25519 == device.curve25519 assert self.store.get_device_keys(None, user_devices, device_keys) assert device_keys[self.user_id][self.device_id].curve25519 == \ device.curve25519 assert device_keys[self.user_id][self.device_id].verified # Test device verification persistence device.verified = False device.ignored = True devices = self.store.get_device_keys(None, user_devices)[self.user_id] assert not devices[self.device_id].verified assert devices[self.device_id].ignored # Test [] wildcard devices = self.store.get_device_keys(None, {self.user_id: []})[self.user_id] assert devices[self.device_id].curve25519 == device.curve25519 device.device_list.tracked_user_ids = {self.user_id} device.device_list.get_room_device_keys(self.room) assert device_keys[self.user_id][self.device_id].curve25519 == \ device.curve25519 # Test multiples [] device_keys.clear() user_id = 'test' device_id = 'test' device_keys_to_save[user_id] = {device_id: device} self.store.save_device_keys(device_keys_to_save) user_devices[user_id] = [] user_devices[self.user_id] = [] device_keys = self.store.get_device_keys(None, user_devices) assert device_keys[self.user_id][self.device_id].curve25519 == device.curve25519 assert device_keys[user_id][device_id].curve25519 == device.curve25519 # Try to verify a device that has no keys device._ed25519 = None with pytest.raises(ValueError): device.verified = False self.store.remove_olm_account() assert not self.store.get_device_keys(None, user_devices) @pytest.mark.usefixtures('account') def test_tracked_users_persistence(self): tracked_user_ids = set() tracked_user_ids_to_save = {self.user_id} self.store.load_tracked_users(tracked_user_ids) assert not tracked_user_ids self.store.save_tracked_users(tracked_user_ids_to_save) self.store.load_tracked_users(tracked_user_ids) assert tracked_user_ids == tracked_user_ids_to_save self.store.remove_tracked_users({self.user_id}) tracked_user_ids.clear() self.store.load_tracked_users(tracked_user_ids) assert not tracked_user_ids @pytest.mark.usefixtures('account') def test_sync_token_persistence(self): sync_token = 'test' assert not self.store.get_sync_token() self.store.save_sync_token(sync_token) assert self.store.get_sync_token() == sync_token sync_token = 'new' self.store.save_sync_token(sync_token) assert self.store.get_sync_token() == sync_token @pytest.mark.usefixtures('account') def test_key_requests(self): session_id = 'test' session_ids = set() self.store.load_outgoing_key_requests(session_ids) assert not session_ids self.store.add_outgoing_key_request(session_id) self.store.load_outgoing_key_requests(session_ids) assert session_id in session_ids session_ids.clear() self.store.remove_outgoing_key_request(session_id) self.store.load_outgoing_key_requests(session_ids) assert not session_ids def test_load_all(self, account, curve_key, ed_key, device): curve_key = account.identity_keys['curve25519'] session = olm.OutboundSession(account, curve_key, curve_key) out_session = MegolmOutboundSession() out_session.add_device(self.device_id) in_session = MegolmInboundSession(out_session.session_key, ed_key) device_keys_to_save = {self.user_id: {self.device_id: device}} self.store.save_inbound_session(self.room_id, curve_key, in_session) self.store.save_olm_session(curve_key, session) self.store.save_outbound_session(self.room_id, out_session) self.store.save_megolm_outbound_devices(self.room_id, {self.device_id}) self.store.save_device_keys(device_keys_to_save) device = OlmDevice( None, self.user_id, self.device_id, store_conf=self.store_conf, load_all=True) assert session.id in {s.id for s in device.olm_sessions[curve_key]} saved_in_session = \ device.megolm_inbound_sessions[self.room_id][curve_key][in_session.id] assert saved_in_session.id == in_session.id saved_out_session = device.megolm_outbound_sessions[self.room_id] assert saved_out_session.id == out_session.id assert saved_out_session.devices == out_session.devices assert device.device_keys[self.user_id][self.device_id].curve25519 == \ device.curve25519