Пример #1
0
    def __init__(
        self,
        private_key: PrivateKey,
        chain_id: ChainID,
        device_id: DeviceIDs,
        message_received_callback: Callable[[Message], None],
        servers: Optional[List[str]] = None,
    ) -> None:
        super().__init__()

        self.chain_id = chain_id
        self.device_id = device_id
        self.message_received_callback = message_received_callback
        self._displayname_cache = DisplayNameCache()
        self.startup_finished = AsyncResult()
        self._client_manager = ClientManager(
            available_servers=servers,
            device_id=self.device_id,
            chain_id=self.chain_id,
            private_key=private_key,
            handle_matrix_sync=self._handle_matrix_sync,
        )

        self.base_url = self._client.api.base_url
        self.user_manager = MultiClientUserAddressManager(
            client=self._client,
            displayname_cache=self._displayname_cache,
        )

        self._rate_limiter = RateLimiter(
            allowed_bytes=MATRIX_RATE_LIMIT_ALLOWED_BYTES,
            reset_interval=MATRIX_RATE_LIMIT_RESET_INTERVAL,
        )
Пример #2
0
    def __init__(
        self,
        private_key: str,
        chain_id: ChainID,
        service_room_suffix: str,
        message_received_callback: Callable[[Message], None],
        address_reachability_changed_callback: Callable[
            [Address, AddressReachability], None] = None,
        servers: List[str] = None,
    ) -> None:
        super().__init__()

        self.private_key = private_key
        self.chain_id = chain_id
        self.service_room_suffix = service_room_suffix
        self.message_received_callback = message_received_callback

        if servers:
            self.available_servers = servers
        else:
            self.available_servers = get_matrix_servers(
                DEFAULT_MATRIX_KNOWN_SERVERS[Environment.
                                             PRODUCTION] if chain_id ==
                1 else DEFAULT_MATRIX_KNOWN_SERVERS[Environment.DEVELOPMENT])

        self.client = make_client(
            servers=self.available_servers,
            http_pool_maxsize=4,
            http_retry_timeout=40,
            http_retry_delay=matrix_http_retry_delay,
        )
        self.broadcast_rooms: List[Room] = []
        self._displayname_cache = DisplayNameCache()
        self._user_manager: Optional[UserAddressManager] = None

        if address_reachability_changed_callback is not None:
            self._user_manager = UserAddressManager(
                client=self.client,
                displayname_cache=self._displayname_cache,
                address_reachability_changed_callback=
                address_reachability_changed_callback,
            )

        self.startup_finished = Event()
        self.rate_limiter = RateLimiter(
            allowed_bytes=MATRIX_RATE_LIMIT_ALLOWED_BYTES,
            reset_interval=MATRIX_RATE_LIMIT_RESET_INTERVAL,
        )
Пример #3
0
def user_addr_mgr(dummy_matrix_client, address_reachability_callback,
                  user_presence_callback):

    address_manager = NonValidatingUserAddressManager(
        client=dummy_matrix_client,
        displayname_cache=DisplayNameCache(),
        address_reachability_changed_callback=address_reachability_callback,
        user_presence_changed_callback=user_presence_callback,
    )

    def fetch_user_presence(user_id):
        if user_id in address_manager._userid_to_presence.keys():
            return address_manager.get_userid_presence(user_id)
        else:
            presence = UserPresence(
                dummy_matrix_client.get_user_presence(user_id))
            address_manager._userid_to_presence[user_id] = presence
            return address_manager._userid_to_presence[user_id]

    address_manager._fetch_user_presence = fetch_user_presence
    address_manager.start()

    yield address_manager

    address_manager.stop()
Пример #4
0
 def __init__(self, reachabilities: Dict[Address,
                                         AddressReachability]) -> None:
     self.reachabilities = reachabilities
     self.times = {address: datetime.utcnow() for address in reachabilities}
     self._userid_to_presence: dict = mock.MagicMock()
     self._address_to_userids: dict = mock.MagicMock()
     self._address_to_userids.__getitem__ = lambda self, key: {
         get_user_id_from_address(key)
     }
     self._displayname_cache = DisplayNameCache()
Пример #5
0
def test_client_manager_start(get_accounts, get_private_key):
    server_urls = [f"https://example0{i}.com" for i in range(5)]

    (c1, ) = get_accounts(1)
    private_key = get_private_key(c1)
    client_mock = Mock()
    client_mock.api.base_url = "https://example00.com"
    client_mock.user_id = "1"
    client_mock.sync_worker = AsyncResult()
    start_client_counter = 0

    def mock_start_client(server_url: str):  # pylint: disable=unused-argument
        nonlocal start_client_counter
        client_mock.sync_worker = AsyncResult()
        start_client_counter += 1
        return client_mock

    with patch.multiple(
            "raiden_libs.matrix",
            make_client=Mock(return_value=client_mock),
            get_matrix_servers=Mock(return_value=server_urls),
            login=Mock(),
            join_broadcast_room=Mock(),
    ):
        client_manager = ClientManager(
            available_servers=[f"https://example0{i}.com" for i in range(5)],
            broadcast_room_alias_prefix="_service",
            chain_id=ChainID(1),
            device_id=DeviceIDs.PFS,
            private_key=private_key,
            handle_matrix_sync=lambda s: True,
        )

        client_manager._start_client = mock_start_client  # pylint: disable=protected-access

        assert client_manager.known_servers == server_urls

        uam = MultiClientUserAddressManager(
            client=client_manager.main_client,
            displayname_cache=DisplayNameCache(),
        )
        uam.start()
        client_manager.user_manager = uam
        client_manager.stop_event.clear()

        gevent.spawn(client_manager.connect_client_forever,
                     client_mock.api.base_url)
        gevent.sleep(2)
        client_mock.sync_worker.set(True)
        gevent.sleep(2)
        client_manager.stop_event.set()
        client_mock.sync_worker.set(True)

        assert start_client_counter == 2
Пример #6
0
def test_filter_presences_by_client(presence_event, server_index):

    server_urls = [f"https://example0{i}.com" for i in range(5)]

    uuids_to_callbacks = {}
    server_url_to_clients = {}
    server_url_to_processed_presence = []

    def _mock_add_presence_listener(listener):
        uuid = uuid1()
        uuids_to_callbacks[uuid] = listener
        return uuid

    def _mock_remove_presence_listener(listener_id):
        uuids_to_callbacks.pop(listener_id)

    def _mock_presence_listener(
            self,
            event: Dict[str, Any],
            presence_update_id: int  # pylint: disable=unused-argument
    ) -> None:
        server_url_to_processed_presence.append(event)

    for server_url in server_urls:
        client = Mock()
        client.api.base_url = server_url
        client.add_presence_listener = _mock_add_presence_listener
        client.remove_presence_listener = _mock_remove_presence_listener
        server_url_to_clients[server_url] = client

    main_client = server_url_to_clients.pop(server_urls[0])

    with mock.patch.object(MultiClientUserAddressManager,
                           "_presence_listener",
                           new=_mock_presence_listener):
        uam = MultiClientUserAddressManager(
            client=main_client,
            displayname_cache=DisplayNameCache(),
        )
        uam.start()

        for client in server_url_to_clients.values():
            uam.add_client(client)

        # call presence listener on all clients
        for listener in uuids_to_callbacks.values():
            listener(presence_event, 0)

        # only the respective client should forward the presence to the uam
        expected_presences = 1

        assert len(server_url_to_processed_presence) == expected_presences

        # drop the client of the last homeserver in the list
        # and remove listener
        client = server_url_to_clients[server_urls[-1]]
        uam.remove_client(client)

        # only call the listener of the main client
        # if the presence event comes from the server with the dropped client
        # it should also consume the presence, otherwise not
        uuids_to_callbacks[uam._listener_id](presence_event, 0)  # pylint: disable=protected-access

        if server_index in [0, len(server_urls) - 1]:
            expected_presences += 1

        assert len(server_url_to_processed_presence) == expected_presences
Пример #7
0
class MatrixListener(gevent.Greenlet):
    # pylint: disable=too-many-instance-attributes
    def __init__(
        self,
        private_key: PrivateKey,
        chain_id: ChainID,
        device_id: DeviceIDs,
        message_received_callback: Callable[[Message], None],
        servers: Optional[List[str]] = None,
    ) -> None:
        super().__init__()

        self.chain_id = chain_id
        self.device_id = device_id
        self.message_received_callback = message_received_callback
        self._displayname_cache = DisplayNameCache()
        self.startup_finished = AsyncResult()
        self._client_manager = ClientManager(
            available_servers=servers,
            device_id=self.device_id,
            chain_id=self.chain_id,
            private_key=private_key,
            handle_matrix_sync=self._handle_matrix_sync,
        )

        self.base_url = self._client.api.base_url
        self.user_manager = MultiClientUserAddressManager(
            client=self._client,
            displayname_cache=self._displayname_cache,
        )

        self._rate_limiter = RateLimiter(
            allowed_bytes=MATRIX_RATE_LIMIT_ALLOWED_BYTES,
            reset_interval=MATRIX_RATE_LIMIT_RESET_INTERVAL,
        )

    @property
    def _client(self) -> GMatrixClient:
        return self._client_manager.main_client

    @property
    def server_url_to_other_clients(self) -> Dict[str, GMatrixClient]:
        return self._client_manager.server_url_to_other_clients

    def _run(self) -> None:  # pylint: disable=method-hidden

        self.user_manager.start()
        self._client_manager.start(self.user_manager)

        def set_startup_finished() -> None:
            self._client.processed.wait()
            self.startup_finished.set()

        startup_finished_greenlet = gevent.spawn(set_startup_finished)
        try:
            assert self._client.sync_worker
            self._client.sync_worker.get()
        finally:
            self._client_manager.stop()
            gevent.joinall({startup_finished_greenlet},
                           raise_error=True,
                           timeout=0)

    def _handle_matrix_sync(self, messages: MatrixSyncMessages) -> bool:
        all_messages: List[Message] = list()
        for room, room_messages in messages:
            if room is not None:
                # Ignore room messages
                # This will only handle to-device messages
                continue

            for text in room_messages:
                all_messages.extend(self._handle_message(room, text))

        log.debug("Incoming messages", messages=all_messages)

        for message in all_messages:
            self.message_received_callback(message)

        return True

    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"]
        self._displayname_cache.warm_users([User(self._client.api, sender_id)])
        # handles the "Could not get 'display_name' for user" case

        try:
            displayname = self._displayname_cache.userid_to_displayname[
                sender_id]
        except KeyError:
            log.exception("Could not warm display cache", peer_user=sender_id)
            return []

        peer_address = validate_user_id_signature(sender_id, displayname)

        if not peer_address:
            log.debug(
                "Message from invalid user displayName signature",
                peer_user=sender_id,
                room=room,
            )
            return []

        data = message["content"]["body"]
        if not isinstance(data, str):
            log.warning(
                "Received message body not a string",
                peer_user=sender_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
Пример #8
0
class MatrixListener(gevent.Greenlet):
    # pylint: disable=too-many-instance-attributes
    def __init__(
        self,
        private_key: str,
        chain_id: ChainID,
        service_room_suffix: str,
        message_received_callback: Callable[[Message], None],
        address_reachability_changed_callback: Callable[
            [Address, AddressReachability], None] = None,
        servers: List[str] = None,
    ) -> None:
        super().__init__()

        self.private_key = private_key
        self.chain_id = chain_id
        self.service_room_suffix = service_room_suffix
        self.message_received_callback = message_received_callback

        if servers:
            self.available_servers = servers
        else:
            self.available_servers = get_matrix_servers(
                DEFAULT_MATRIX_KNOWN_SERVERS[Environment.
                                             PRODUCTION] if chain_id ==
                1 else DEFAULT_MATRIX_KNOWN_SERVERS[Environment.DEVELOPMENT])

        self.client = make_client(
            servers=self.available_servers,
            http_pool_maxsize=4,
            http_retry_timeout=40,
            http_retry_delay=matrix_http_retry_delay,
        )
        self.broadcast_rooms: List[Room] = []
        self._displayname_cache = DisplayNameCache()
        self._user_manager: Optional[UserAddressManager] = None

        if address_reachability_changed_callback is not None:
            self._user_manager = UserAddressManager(
                client=self.client,
                displayname_cache=self._displayname_cache,
                address_reachability_changed_callback=
                address_reachability_changed_callback,
            )

        self.startup_finished = Event()
        self.rate_limiter = RateLimiter(
            allowed_bytes=MATRIX_RATE_LIMIT_ALLOWED_BYTES,
            reset_interval=MATRIX_RATE_LIMIT_RESET_INTERVAL,
        )

    def listen_forever(self) -> None:
        self.startup_finished.wait()
        self.client.listen_forever()

    def _run(self) -> None:  # pylint: disable=method-hidden
        self._start_client()

        self.client.start_listener_thread()
        assert self.client.sync_thread
        self.client.sync_thread.get()

    def stop(self) -> None:
        if self._user_manager:
            self._user_manager.stop()
        self.client.stop_listener_thread()

    def _start_client(self) -> None:
        try:
            if self._user_manager:
                self._user_manager.start()

            login(self.client,
                  signer=LocalSigner(private_key=decode_hex(self.private_key)))
        except (MatrixRequestError, ValueError):
            raise ConnectionError("Could not login/register to matrix.")

        try:
            self.join_global_rooms(client=self.client,
                                   available_servers=self.available_servers)
        except (MatrixRequestError, TransportError):
            raise ConnectionError(
                "Could not join monitoring broadcasting room.")

        # Add listener for global rooms
        for broadcast_room in self.broadcast_rooms:
            broadcast_room.add_listener(self._handle_message, "m.room.message")

        # Signal that startup is finished
        self.startup_finished.set()

    def follow_address_presence(self,
                                address: Address,
                                refresh: bool = False) -> None:
        if self._user_manager:
            self._user_manager.add_address(address)

            if refresh:
                self._user_manager.populate_userids_for_address(address)
                self._user_manager.track_address_presence(
                    address=address,
                    user_ids=self._user_manager.get_userids_for_address(
                        address))

            log.debug(
                "Tracking address",
                address=to_checksum_address(address),
                current_presence=self._user_manager.get_address_reachability(
                    address),
                refresh=refresh,
            )

    def _get_user_from_user_id(self, user_id: str) -> User:
        """Creates an User from an user_id, if none, or fetch a cached User """
        for broadcast_room in self.broadcast_rooms:
            if user_id in broadcast_room._members:  # pylint: disable=protected-access
                user: User = broadcast_room._members[user_id]  # pylint: disable=protected-access
                break
        else:
            user = self.client.get_user(user_id)

        return user

    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_from_user_id(sender_id)
        self._displayname_cache.warm_users([user])
        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, self.rate_limiter)
        if not messages:
            return False

        for message in messages:
            assert message.sender, "Message has no sender"
            self.message_received_callback(message)

        return True

    def join_global_rooms(
        self, client: GMatrixClient,
        available_servers: Sequence[str] = ()) -> None:
        """Join or create a global public room with given name on all available servers.
        If global rooms are not found, create a public room with the name on each server.

        Params:
            client: matrix-python-sdk client instance
            servers: optional: sequence of known/available servers to try to find the room in
        """
        suffix = self.service_room_suffix
        room_alias_prefix = make_room_alias(self.chain_id, suffix)

        parsed_servers = [
            urlparse(s).netloc for s in available_servers
            if urlparse(s).netloc not in {None, ""}
        ]

        for server in parsed_servers:
            room_alias_full = f"#{room_alias_prefix}:{server}"
            log.debug(f"Trying to join {suffix} room",
                      room_alias_full=room_alias_full)
            try:
                broadcast_room = client.join_room(room_alias_full)
                log.debug(f"Joined {suffix} room", room=broadcast_room)
                self.broadcast_rooms.append(broadcast_room)
            except MatrixRequestError as ex:
                if ex.code != 404:
                    log.debug(
                        f"Could not join {suffix} room, trying to create one",
                        room_alias_full=room_alias_full,
                    )
                    try:
                        broadcast_room = client.create_room(room_alias_full,
                                                            is_public=True)
                        log.debug(f"Created {suffix} room",
                                  room=broadcast_room)
                        self.broadcast_rooms.append(broadcast_room)
                    except MatrixRequestError:
                        log.debug(
                            f"Could neither join nor create a {suffix} room",
                            room_alias_full=room_alias_full,
                        )
                        raise TransportError(
                            f"Could neither join nor create a {suffix} room")

                else:
                    log.debug(
                        f"Could not join {suffix} room",
                        room_alias_full=room_alias_full,
                        _exception=ex,
                    )
                    raise