def test_validate_userid_signature(): ownserver = "https://ownserver.com" api = Mock() api.base_url = ownserver server_name = urlparse(ownserver).netloc signer = make_signer() user = Mock(spec=User) user.api = api user.user_id = f"@{to_normalized_address(signer.address)}:{server_name}" user.displayname = None user.get_display_name = Mock(side_effect=lambda: user.displayname) # displayname is None, get_display_name will be called but continue to give None with pytest.raises(AssertionError): assert validate_userid_signature(user) assert user.get_display_name.call_count == 0 # successfuly recover valid displayname user.displayname = encode_hex(signer.sign(user.user_id.encode())) assert validate_userid_signature(user) == signer.address assert user.get_display_name.call_count == 0 # assert another call will cache the result and avoid wasteful get_display_name call assert validate_userid_signature(user) == signer.address assert user.get_display_name.call_count == 0 # non-hex displayname should be gracefully handled user.displayname = "random gibberish" assert validate_userid_signature(user) is None assert user.get_display_name.call_count == 0 # valid signature but from another user should also return None user.displayname = encode_hex(make_signer().sign(user.user_id.encode())) assert validate_userid_signature(user) is None assert user.get_display_name.call_count == 0 # same address, but different user_id, even if valid, should be rejected # (prevent personification) user.displayname = encode_hex(signer.sign(user.user_id.encode())) user.user_id = f"@{to_normalized_address(signer.address)}.deadbeef:{server_name}" assert validate_userid_signature(user) is None assert user.get_display_name.call_count == 0 # but non-default but valid user_id should be accepted user.displayname = encode_hex(signer.sign(user.user_id.encode())) assert validate_userid_signature(user) == signer.address assert user.get_display_name.call_count == 0 # non-compliant user_id shouldn't even call get_display_name user.user_id = f"@my_user:{server_name}" assert validate_userid_signature(user) is None assert user.get_display_name.call_count == 0
def _handle_message(self, room: Optional[Room], message: MatrixMessage) -> List[SignedMessage]: """Handle a single Matrix message. The matrix message is expected to be a NDJSON, and each entry should be a valid JSON encoded Raiden message. If `room` is None this means we are processing a `to_device` message """ is_valid_type = (message["type"] == "m.room.message" and message["content"]["msgtype"] == MatrixMessageType.TEXT.value) if not is_valid_type: return [] sender_id = message["sender"] user = self._get_user_from_user_id(sender_id) try: self._displayname_cache.warm_users([user]) # handles the "Could not get 'display_name' for user" case except TransportError as ex: log.error("Could not warm display cache", peer_user=user.user_id, error=str(ex)) return [] peer_address = validate_userid_signature(user) if not peer_address: log.debug( "Message from invalid user displayName signature", peer_user=user.user_id, room=room, ) return [] data = message["content"]["body"] if not isinstance(data, str): log.warning( "Received message body not a string", peer_user=user.user_id, peer_address=to_checksum_address(peer_address), room=room, ) return [] messages = deserialize_messages(data=data, peer_address=peer_address, rate_limiter=self._rate_limiter) if not messages: return [] return messages
def _handle_message(self, room: Any, event: dict) -> bool: """ Handle text messages sent to listening rooms """ if event["type"] != "m.room.message" or event["content"][ "msgtype"] != "m.text": # Ignore non-messages and non-text messages return False sender_id = event["sender"] user = self._get_user(sender_id) peer_address = validate_userid_signature(user) if not peer_address: log.debug( "Message from invalid user displayName signature", peer_user=user.user_id, room=room, ) return False data = event["content"]["body"] if not isinstance(data, str): log.warning( "Received message body not a string", peer_user=user.user_id, peer_address=to_checksum_address(peer_address), room=room, ) return False messages = deserialize_messages(data, peer_address) if not messages: return False for message in messages: log.debug("Message received", message=message, sender=to_checksum_address(message.sender)) self.message_received_callback(message) return True
def _handle_message(self, room: Any, event: dict) -> bool: """ Handle text messages sent to listening rooms """ if event['type'] != 'm.room.message' or event['content'][ 'msgtype'] != 'm.text': # Ignore non-messages and non-text messages return False sender_id = event['sender'] user = self._get_user(sender_id) peer_address = validate_userid_signature(user) if not peer_address: log.debug( 'Message from invalid user displayName signature', peer_user=user.user_id, room=room, ) return False data = event['content']['body'] if not isinstance(data, str): log.warning( 'Received message body not a string', peer_user=user.user_id, peer_address=to_checksum_address(peer_address), room=room, ) return False messages: List[SignedMessage] = list() for line in data.splitlines(): line = line.strip() if not line: continue logger = log.bind(peer_address=to_checksum_address(peer_address)) try: message_dict = json.loads(line) message = message_from_dict(message_dict) except (UnicodeDecodeError, json.JSONDecodeError) as ex: logger.warning("Can't parse message data JSON", message_data=line, _exc=ex) continue except (InvalidProtocolMessage, KeyError) as ex: logger.warning("Message data JSON is not a valid message", message_data=line, _exc=ex) continue if not isinstance(message, SignedMessage): logger.warning('Received invalid message', message=message) continue elif message.sender != peer_address: logger.warning('Message not signed by sender!', message=message, signer=message.sender) continue messages.append(message) if not messages: return False for message in messages: log.debug('Message received', message=message, sender=to_checksum_address(message.sender)) self.callback(message) return True
def _validate_userid_signature(user: User) -> Optional[Address]: return validate_userid_signature(user)