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
Esempio n. 2
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
Esempio n. 3
0
    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
Esempio n. 4
0
    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
Esempio n. 5
0
 def _validate_userid_signature(user: User) -> Optional[Address]:
     return validate_userid_signature(user)