Beispiel #1
0
def login_with_token(client: GMatrixClient, user_id: str,
                     access_token: str) -> Optional[User]:
    """Reuse an existing authentication code.

    If this succeeds it means the user has logged in the past, so we assume the
    display name is properly set and that there may be rooms open from past
    executions.
    """
    client.set_access_token(user_id=user_id, token=access_token)

    try:
        # Test the credentials. Any API that requries authentication
        # would be enough.
        client.api.get_devices()
    except MatrixRequestError as ex:
        log.debug(
            "Couldn't use previous login credentials",
            node=node_address_from_userid(client.user_id),
            prev_user_id=user_id,
            _exception=ex,
        )
        return None

    log.debug(
        "Success. Valid previous credentials",
        node=node_address_from_userid(client.user_id),
        user_id=user_id,
    )
    return client.get_user(client.user_id)
Beispiel #2
0
    def _setup_client(self, matrix_client: GMatrixClient) -> None:
        exception_str = "Could not login/register to matrix."

        try:
            login(matrix_client,
                  signer=self.local_signer,
                  device_id=self.device_id)
            exception_str = "Could not join broadcasting room."
            server = urlparse(matrix_client.api.base_url).netloc
            room_alias = f"#{self.broadcast_room_alias_prefix}:{server}"

            broadcast_room = join_broadcast_room(
                client=matrix_client, broadcast_room_alias=room_alias)
            broadcast_room_id = broadcast_room.room_id

            if matrix_client == self.main_client:
                self.broadcast_room = broadcast_room
                self.broadcast_room_id = broadcast_room_id

            # Don't listen for messages on the discovery room on all clients
            sync_filter_id = matrix_client.create_sync_filter(
                not_rooms=[broadcast_room])
            matrix_client.set_sync_filter_id(sync_filter_id)
        except (MatrixRequestError, ValueError):
            raise ConnectionError(exception_str)
Beispiel #3
0
def first_login(client: GMatrixClient, signer: Signer, username: str) -> User:
    """Login for the first time.

    This relies on the Matrix server having the `eth_auth_provider` plugin
    installed, the plugin will automatically create the user on the first
    login. The plugin requires the password to be the signature of the server
    hostname, verified by the server to prevent account creation spam.

    Displayname is the signature of the whole user_id (including homeserver),
    to be verified by other peers and prevent impersonation attacks.
    """
    server_url = client.api.base_url
    server_name = urlparse(server_url).netloc

    # The plugin `eth_auth_provider` expects a signature of the server_name as
    # the user's password.
    #
    # For a honest matrix server:
    #
    # - This prevents impersonation attacks / name squatting, since the plugin
    # will validate the username by recovering the adddress from the signature
    # and check the recovered address and the username matches.
    #
    # For a badly configured server (one without the plugin):
    #
    # - An attacker can front run and register the username before the honest
    # user:
    #    - Because the attacker cannot guess the correct password, when the
    #    honest node tries to login it will fail, which tells us the server is
    #    improperly configured and should be blacklisted.
    #    - The attacker cannot forge a signature to use as a display name, so
    #    the partner node can tell there is a malicious node trying to
    #    eavesdrop the conversation and that matrix server should be
    #    blacklisted.
    # - The username is available, but because the plugin is not installed the
    # login will fail since the user is not registered. Here too one can infer
    # the server is improperly configured and blacklist the server.
    password = encode_hex(signer.sign(server_name.encode()))

    # Disabling sync because login is done before the transport is fully
    # initialized, i.e. the inventory rooms don't have the callbacks installed.
    client.login(username, password, sync=False)

    # Because this is the first login, the display name has to be set, this
    # prevents the impersonation metioned above. subsequent calls will reuse
    # the authentication token and the display name will be properly set.
    signature_bytes = signer.sign(client.user_id.encode())
    signature_hex = encode_hex(signature_bytes)

    user = client.get_user(client.user_id)
    user.set_display_name(signature_hex)

    log.debug(
        "Logged to a new server",
        node=to_checksum_address(username),
        homeserver=server_name,
        server_url=server_url,
    )
    return user
Beispiel #4
0
def new_client(server: "ParsedURL") -> GMatrixClient:
    server_name = server.netloc

    signer = make_signer()
    username = str(to_normalized_address(signer.address))
    password = encode_hex(signer.sign(server_name.encode()))

    client = GMatrixClient(server)
    client.login(username, password, sync=False)

    return client
Beispiel #5
0
def handle_and_time_invite(
        invite_start: float,
        client: GMatrixClient,
        room_id: RoomID,
        state: Dict,  # pylint: disable=unused-argument
) -> None:
    invite_elapsed = time.monotonic() - invite_start
    log.debug(RECEIVED + INVITE, invite_elapsed=invite_elapsed)

    with logtime(ROOM, room_id=room_id):
        client.join_room(room_id_or_alias=room_id)
Beispiel #6
0
    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
Beispiel #7
0
def new_client(handle_messages_callback: Callable[[MatrixSyncMessages], bool],
               server: "ParsedURL") -> GMatrixClient:
    server_name = server.netloc

    signer = make_signer()
    username = str(to_normalized_address(signer.address))
    password = encode_hex(signer.sign(server_name.encode()))

    client = GMatrixClient(handle_messages_callback, server)
    client.login(username, password, sync=False)

    return client
Beispiel #8
0
def make_client(
    handle_messages_callback: Callable[[MatrixSyncMessages], bool],
    servers: List[str],
    *args: Any,
    **kwargs: Any,
) -> GMatrixClient:
    """Given a list of possible servers, chooses the closest available and create a GMatrixClient

    Params:
        servers: list of servers urls, with scheme (http or https)
        Rest of args and kwargs are forwarded to GMatrixClient constructor
    Returns:
        GMatrixClient instance for one of the available servers
    """
    if len(servers) > 1:
        sorted_servers = sort_servers_closest(servers)
        log.debug("Selecting best matrix server",
                  sorted_servers=sorted_servers)
    elif len(servers) == 1:
        sorted_servers = {servers[0]: 0}
    else:
        raise TransportError("No valid servers list given")

    last_ex = None
    for server_url, rtt in sorted_servers.items():
        client = GMatrixClient(handle_messages_callback, server_url, *args,
                               **kwargs)

        retries = 3
        while retries:
            retries -= 1
            try:
                client.api._send("GET",
                                 "/versions",
                                 api_path="/_matrix/client")
            except MatrixRequestError as ex:
                log.warning(
                    "Matrix server returned an error, retrying",
                    server_url=server_url,
                    _exception=ex,
                )
                last_ex = ex
            except MatrixError as ex:
                log.warning("Selected server not usable",
                            server_url=server_url,
                            _exception=ex)
                last_ex = ex
                retries = 0
            else:
                log.info(
                    "Using Matrix server",
                    server_url=server_url,
                    server_ident=client.api.server_ident,
                    average_rtt=rtt,
                )
                return client

    raise TransportError(
        "Unable to find a reachable Matrix server. Please check your network connectivity."
    ) from last_ex
Beispiel #9
0
 def make_client_monkey(
     handle_messages_callback, handle_member_join_callback, servers, *args, **kwargs
 ):  # pylint: disable=unused-argument
     return GMatrixClient(
         handle_messages_callback=handle_messages_callback,
         handle_member_join_callback=handle_member_join_callback,
         base_url=servers[0],
     )
def new_user(matrix_server_url: str) -> LoggedUser:
    client = GMatrixClient(time_messages, ignore_member_join, matrix_server_url)
    signer = factories.make_signer()

    with logtime(USER) as details:
        user = login(client, signer)
        details["user_id"] = user.user_id

    return LoggedUser(client, signer, user)
Beispiel #11
0
def _try_login_or_register(client: GMatrixClient, base_username: str,
                           password: str, server_name: str, server_url, seed):

    rand = None
    # try login and register on first 5 possible accounts
    for i in range(JOIN_RETRIES):
        username = base_username
        if i:
            if not rand:
                rand = Random(
                )  # deterministic, random secret for username suffixes
                # initialize rand for seed (which requires a signature) only if/when needed
                rand.seed(int.from_bytes(seed, "big"))
            username = f"{username}.{rand.randint(0, 0xffffffff):08x}"

        try:
            client.login(username, password, sync=False)
            prev_sync_limit = client.set_sync_limit(0)
            client._sync()  # when logging, do initial_sync with limit=0
            client.set_sync_limit(prev_sync_limit)
            log.info("Login",
                     homeserver=server_name,
                     server_url=server_url,
                     username=username)
            break
        except MatrixRequestError as ex:
            if ex.code != 403:
                raise
            log.info(
                "Could not login. Trying register",
                homeserver=server_name,
                server_url=server_url,
                username=username,
            )
            try:
                client.register_with_password(username, password)
                log.debug("Register",
                          homeserver=server_name,
                          server_url=server_url,
                          username=username)
                break
            except MatrixRequestError as ex:
                if ex.code != 400:
                    raise
                log.debug("Username taken. Continuing")
                continue
    else:
        raise ValueError("Could not register or login!")
Beispiel #12
0
def run_sync_loop(client: GMatrixClient):

    sync_token = None

    while True:
        response = client.api.sync(since=sync_token)
        sync_token = response["next_batch"]
        if response:

            for room_id, invite_room in response["rooms"]["invite"].items():

                try:
                    print(f"joining room: {room_id}")
                    client.join_room(room_id)
                except MatrixRequestError:
                    room_to_leave = client._mkroom(room_id)
                    room_to_leave.leave()

            for room_id, sync_room in response["rooms"]["join"].items():
                if room_id not in client.rooms:
                    client._mkroom(room_id)

                room = client.rooms[room_id]

                for event in sync_room["state"]["events"]:
                    event["room_id"] = room_id
                    room._process_state_event(event)
                for event in sync_room["timeline"]["events"]:
                    event["room_id"] = room_id
                    room._put_event(event)

                call_events = list()
                call_events.append(
                    (
                        room,
                        [
                            message
                            for message in sync_room["timeline"]["events"]
                            if message["type"] in ["m.call.invite", "m.call.answer", "m.call.candidates"]
                        ],
                    )
                )
                handle_call_events(client, call_events)
def mock_matrix(monkeypatch, retry_interval, retries_before_backoff):

    from raiden.network.transport.matrix.client import User
    from raiden.network.transport.matrix import transport as transport_module

    monkeypatch.setattr(User, "get_display_name",
                        lambda _: "random_display_name")
    monkeypatch.setattr(transport_module, "make_client",
                        lambda url, *a, **kw: GMatrixClient(url[0]))

    def mock_get_user(klass, user: Union[User, str]) -> User:  # pylint: disable=unused-argument
        return User(None, USERID1)

    def mock_get_room_ids_for_address(  # pylint: disable=unused-argument
            klass,
            address: Address,
            filter_private: bool = None) -> List[str]:
        return ["!roomID:server"]

    def mock_set_room_id_for_address(  # pylint: disable=unused-argument
            self, address: Address, room_id: Optional[str]):
        pass

    def mock_receive_message(klass, message):  # pylint: disable=unused-argument
        # We are just unit testing the matrix transport receive so do nothing
        assert message
        assert message.sender

    config = dict(
        retry_interval=retry_interval,
        retries_before_backoff=retries_before_backoff,
        server="http://none",
        server_name="none",
        available_servers=[],
        global_rooms=[],
        private_rooms=False,
    )

    transport = MatrixTransport(config)
    transport._raiden_service = MockRaidenService()
    transport._stop_event.clear()
    transport._address_mgr.add_userid_for_address(factories.HOP1, USERID1)
    transport._client.user_id = USERID0

    monkeypatch.setattr(MatrixTransport, "_get_user", mock_get_user)
    monkeypatch.setattr(MatrixTransport, "_get_room_ids_for_address",
                        mock_get_room_ids_for_address)
    monkeypatch.setattr(MatrixTransport, "_set_room_id_for_address",
                        mock_set_room_id_for_address)
    monkeypatch.setattr(MatrixTransport, "_receive_message",
                        mock_receive_message)

    return transport
def main():
    host = sys.argv[1]

    client = GMatrixClient(host, user_id=USER_ID, token=ACCESS_TOKEN)
    client.join_room(ROOM_ALIAS)

    current_presence = "offline"
    while True:
        if current_presence == "offline":
            client.set_presence_state(UserPresence.ONLINE.value)
        else:
            client.set_presence_state(UserPresence.OFFLINE.value)

        # Confirm user presence
        current_presence = client.get_user_presence(USER_ID)

        print("Change status to: ", current_presence)

        gevent.sleep(5)
def main(keystore_file: str, password: str, host: str, room_id: str, other_user_id: str):
    private_key = get_private_key(keystore_file, password)
    client = GMatrixClient(host)

    user = login(client=client, signer=LocalSigner(private_key=decode_hex(private_key)))

    log.info("Logged in", user=user, server=host, room_id=room_id)
    # print("TKN: \n" + client.token)

    client.add_presence_listener(callback)
    client.start_listener_thread()

    # try:
    client.join_room(room_id)
    # except MatrixRequestError:
    #     client.create_room(alias="raiden_goerli_discovery", is_public=True)

    while True:
        current_presence = client.get_user_presence(other_user_id)
        log.warning("User presence", other_user=other_user_id, presence=current_presence)

        gevent.sleep(1)
Beispiel #16
0
def login_or_register(client: GMatrixClient,
                      signer: Signer,
                      prev_user_id: str = None,
                      prev_access_token: str = None) -> User:
    """Login to a Raiden matrix server with password and displayname proof-of-keys

    - Username is in the format: 0x<eth_address>(.<suffix>)?, where the suffix is not required,
    but a deterministic (per-account) random 8-hex string to prevent DoS by other users registering
    our address
    - Password is the signature of the server hostname, verified by the server to prevent account
    creation spam
    - Displayname currently is the signature of the whole user_id (including homeserver), to be
    verified by other peers. May include in the future other metadata such as protocol version

    Params:
        client: GMatrixClient instance configured with desired homeserver
        signer: raiden.utils.signer.Signer instance for signing password and displayname
        prev_user_id: (optional) previously persisted client.user_id. Must match signer's account
        prev_access_token: (optional) previously persisted client.access_token for prev_user_id
    Returns:
        Own matrix_client.User
    """

    server_url = client.api.base_url
    server_name = urlparse(server_url).netloc

    base_username = to_normalized_address(signer.address)

    user = _check_previous_login(client, prev_user_id, prev_access_token,
                                 base_username, server_name)

    if user is None:
        # password is signed server address
        password = encode_hex(signer.sign(server_name.encode()))
        seed = signer.sign(b"seed")[-32:]

        _try_login_or_register(client, base_username, password, server_name,
                               server_url, seed)

        name = encode_hex(signer.sign(client.user_id.encode()))
        user = client.get_user(client.user_id)
        user.set_display_name(name)

    log.info("Login or register for Hub Node is successfully run")
    return user
Beispiel #17
0
def join_broadcast_room(client: GMatrixClient,
                        broadcast_room_alias: str) -> Room:
    """ Join the public broadcast through the alias `broadcast_room_alias`.

    When a new Matrix instance is deployed the broadcast room _must_ be created
    and aliased, Raiden will not use a server that does not have the discovery
    room properly set. Requiring the setup of the broadcast alias as part of
    the server setup fixes a serious race condition where multiple discovery
    rooms are created, which would break the presence checking.
    See: https://github.com/raiden-network/raiden-transport/issues/46
    """
    try:
        return client.join_room(broadcast_room_alias)
    except MatrixRequestError:
        raise RaidenUnrecoverableError(
            f"Could not join broadcast room {broadcast_room_alias}. "
            f"Make sure the Matrix server you're trying to connect to uses the recommended server "
            f"setup, esp. the server-side broadcast room creation. "
            f"See https://github.com/raiden-network/raiden-transport.")
Beispiel #18
0
def login_or_register_light_client(client: GMatrixClient, **kwargs):

    if kwargs['encrypted_light_client_password_signature'] is not None:
        descrypt_light_client_password_signature = \
            decrypt(kwargs['private_key_hub'],
                    bytes.fromhex(kwargs['encrypted_light_client_password_signature']))

    if kwargs['encrypted_light_client_display_name_signature'] is not None:
        descrypt_light_client_display_name_signature = \
            decrypt(kwargs['private_key_hub'],
                    bytes.fromhex(kwargs['encrypted_light_client_display_name_signature']))

    if kwargs['encrypted_light_client_seed_for_retry_signature'] is not None:
        desctypt_seed_retry_signature = \
            decrypt(kwargs['private_key_hub'],
                    bytes.fromhex(kwargs['encrypted_light_client_seed_for_retry_signature']))

    server_url = client.api.base_url
    server_name = urlparse(server_url).netloc

    base_username = to_normalized_address(kwargs['light_client_address'])

    user = _check_previous_login(client, kwargs['prev_user_id'],
                                 kwargs['prev_access_token'], base_username,
                                 server_name)

    if user is None:
        # password is signed server address
        password = descrypt_light_client_password_signature.decode("utf-8")
        seed = decode_hex(desctypt_seed_retry_signature.decode("utf-8"))[-32:]

        _try_login_or_register(client, base_username, password, server_name,
                               server_url, seed)

        name = descrypt_light_client_display_name_signature.decode("utf-8")
        user = client.get_user(client.user_id)
        user.set_display_name(name)

    log.info("Login or register for LightCLient with address " +
             base_username + " is successfully run")

    return user
Beispiel #19
0
def _check_previous_login(client: GMatrixClient,
                          prev_user_id: str = None,
                          prev_access_token: str = None,
                          base_username: str = None,
                          server_name: str = None):

    # log.info("User: "******"Access Token: " + prev_access_token)

    _match_user = re.match(
        f"^@{re.escape(base_username)}.*:{re.escape(server_name)}$",
        prev_user_id or "")
    if _match_user:  # same user as before
        assert prev_user_id is not None
        log.info("Trying previous user login", user_id=prev_user_id)
        client.set_access_token(user_id=prev_user_id, token=prev_access_token)

        try:
            client.api.get_devices()
        except MatrixRequestError as ex:
            log.debug(
                "Couldn't use previous login credentials, discarding",
                prev_user_id=prev_user_id,
                _exception=ex,
            )
        else:
            prev_sync_limit = client.set_sync_limit(0)
            client._sync()  # initial_sync
            client.set_sync_limit(prev_sync_limit)
            log.info("Success. Valid previous credentials",
                     user_id=prev_user_id)
            return client.get_user(client.user_id)
    elif prev_user_id:
        log.debug(
            "Different server or account, discarding",
            prev_user_id=prev_user_id,
            current_address=base_username,
            current_server=server_name,
        )
Beispiel #20
0
def make_client(servers: Sequence[str], *args, **kwargs) -> GMatrixClient:
    """Given a list of possible servers, chooses the closest available and create a GMatrixClient

    Params:
        servers: list of servers urls, with scheme (http or https)
        Rest of args and kwargs are forwarded to GMatrixClient constructor
    Returns:
        GMatrixClient instance for one of the available servers
    """
    if len(servers) > 1:
        sorted_servers = [
            server_url for (server_url, _) in sort_servers_closest(servers)
        ]
        log.info(
            'Automatically selecting matrix homeserver based on RTT',
            sorted_servers=sorted_servers,
        )
    elif len(servers) == 1:
        sorted_servers = servers
    else:
        raise TransportError('No valid servers list given')

    last_ex = None
    for server_url in sorted_servers:
        server_url: str = server_url
        client = GMatrixClient(server_url, *args, **kwargs)
        try:
            client.api._send('GET', '/versions', api_path='/_matrix/client')
        except MatrixError as ex:
            log.warning('Selected server not usable',
                        server_url=server_url,
                        _exception=ex)
            last_ex = ex
        else:
            break
    else:
        raise TransportError(
            'Unable to find a reachable Matrix server. Please check your network connectivity.',
        ) from last_ex
    return client
Beispiel #21
0
def login_or_register(
    client: GMatrixClient,
    signer: Signer,
    prev_user_id: str = None,
    prev_access_token: str = None,
) -> User:
    """Login to a Raiden matrix server with password and displayname proof-of-keys

    - Username is in the format: 0x<eth_address>(.<suffix>)?, where the suffix is not required,
    but a deterministic (per-account) random 8-hex string to prevent DoS by other users registering
    our address
    - Password is the signature of the server hostname, verified by the server to prevent account
    creation spam
    - Displayname currently is the signature of the whole user_id (including homeserver), to be
    verified by other peers. May include in the future other metadata such as protocol version

    Params:
        client: GMatrixClient instance configured with desired homeserver
        signer: raiden.utils.signer.Signer instance for signing password and displayname
        prev_user_id: (optional) previous persisted client.user_id. Must match signer's account
        prev_access_token: (optional) previous persistend client.access_token for prev_user_id
    Returns:
        Own matrix_client.User
    """
    server_url = client.api.base_url
    server_name = urlparse(server_url).netloc

    base_username = to_normalized_address(signer.address)
    _match_user = re.match(
        f'^@{re.escape(base_username)}.*:{re.escape(server_name)}$',
        prev_user_id or '',
    )
    if _match_user:  # same user as before
        log.debug('Trying previous user login', user_id=prev_user_id)
        client.set_access_token(user_id=prev_user_id, token=prev_access_token)

        try:
            client.api.get_devices()
        except MatrixRequestError as ex:
            log.debug(
                'Couldn\'t use previous login credentials, discarding',
                prev_user_id=prev_user_id,
                _exception=ex,
            )
        else:
            prev_sync_limit = client.set_sync_limit(0)
            client._sync()  # initial_sync
            client.set_sync_limit(prev_sync_limit)
            log.debug('Success. Valid previous credentials',
                      user_id=prev_user_id)
            return client.get_user(client.user_id)
    elif prev_user_id:
        log.debug(
            'Different server or account, discarding',
            prev_user_id=prev_user_id,
            current_address=base_username,
            current_server=server_name,
        )

    # password is signed server address
    password = encode_hex(signer.sign(server_name.encode()))
    rand = None
    # try login and register on first 5 possible accounts
    for i in range(JOIN_RETRIES):
        username = base_username
        if i:
            if not rand:
                rand = Random(
                )  # deterministic, random secret for username suffixes
                # initialize rand for seed (which requires a signature) only if/when needed
                rand.seed(int.from_bytes(signer.sign(b'seed')[-32:], 'big'))
            username = f'{username}.{rand.randint(0, 0xffffffff):08x}'

        try:
            client.login(username, password, sync=False)
            prev_sync_limit = client.set_sync_limit(0)
            client._sync()  # when logging, do initial_sync with limit=0
            client.set_sync_limit(prev_sync_limit)
            log.debug(
                'Login',
                homeserver=server_name,
                server_url=server_url,
                username=username,
            )
            break
        except MatrixRequestError as ex:
            if ex.code != 403:
                raise
            log.debug(
                'Could not login. Trying register',
                homeserver=server_name,
                server_url=server_url,
                username=username,
            )
            try:
                client.register_with_password(username, password)
                log.debug(
                    'Register',
                    homeserver=server_name,
                    server_url=server_url,
                    username=username,
                )
                break
            except MatrixRequestError as ex:
                if ex.code != 400:
                    raise
                log.debug('Username taken. Continuing')
                continue
    else:
        raise ValueError('Could not register or login!')

    name = encode_hex(signer.sign(client.user_id.encode()))
    user = client.get_user(client.user_id)
    user.set_display_name(name)
    return user
Beispiel #22
0
def join_global_room(client: GMatrixClient,
                     name: str,
                     servers: Sequence[str] = ()) -> Room:
    """Join or create a global public room with given name

    First, try to join room on own server (client-configured one)
    If can't, try to join on each one of servers, and if able, alias it in our server
    If still can't, create a public room with name in our server

    Params:
        client: matrix-python-sdk client instance
        name: name or alias of the room (without #-prefix or server name suffix)
        servers: optional: sequence of known/available servers to try to find the room in
    Returns:
        matrix's Room instance linked to client
    """
    our_server_name = urlparse(client.api.base_url).netloc
    assert our_server_name, 'Invalid client\'s homeserver url'
    servers = [our_server_name] + [  # client's own server first
        urlparse(s).netloc for s in servers
        if urlparse(s).netloc not in {None, '', our_server_name}
    ]

    our_server_global_room_alias_full = f'#{name}:{servers[0]}'

    # try joining a global room on any of the available servers, starting with ours
    for server in servers:
        global_room_alias_full = f'#{name}:{server}'
        try:
            global_room = client.join_room(global_room_alias_full)
        except MatrixRequestError as ex:
            if ex.code not in (403, 404, 500):
                raise
            log.debug(
                'Could not join global room',
                room_alias_full=global_room_alias_full,
                _exception=ex,
            )
        else:
            if our_server_global_room_alias_full not in global_room.aliases:
                # we managed to join a global room, but it's not aliased in our server
                global_room.add_room_alias(our_server_global_room_alias_full)
                global_room.aliases.append(our_server_global_room_alias_full)
            break
    else:
        log.debug('Could not join any global room, trying to create one')
        for _ in range(JOIN_RETRIES):
            try:
                global_room = client.create_room(name, is_public=True)
            except MatrixRequestError as ex:
                if ex.code not in (400, 409):
                    raise
                try:
                    global_room = client.join_room(
                        our_server_global_room_alias_full, )
                except MatrixRequestError as ex:
                    if ex.code not in (404, 403):
                        raise
                else:
                    break
            else:
                break
        else:
            raise TransportError('Could neither join nor create a global room')

    return global_room
Beispiel #23
0
def test_admin_is_allowed_to_kick(matrix_transports, local_matrix_servers):
    server_name = local_matrix_servers[0].netloc
    admin_credentials = get_admin_credentials(server_name)
    broadcast_room_name = make_room_alias(UNIT_CHAIN_ID, "discovery")
    broadcast_room_alias = f"#{broadcast_room_name}:{server_name}"

    transport0, transport1, transport2 = matrix_transports

    raiden_service0 = MockRaidenService()
    raiden_service1 = MockRaidenService()
    # start transports to join broadcast rooms as normal users
    transport0.start(raiden_service0, [], None)
    transport1.start(raiden_service1, [], None)
    # admin login using raiden.tests.utils.transport.AdminAuthProvider
    admin_client = GMatrixClient(ignore_messages, ignore_member_join,
                                 local_matrix_servers[0])
    admin_client.login(admin_credentials["username"],
                       admin_credentials["password"],
                       sync=False)
    room_id = admin_client.join_room(broadcast_room_alias).room_id

    # get members of room and filter not kickable users (power level 100)
    def _get_joined_room_members():
        membership_events = admin_client.api.get_room_members(room_id)["chunk"]
        member_ids = [
            event["state_key"] for event in membership_events
            if event["content"]["membership"] == "join"
        ]
        return set(member_ids)

    members = _get_joined_room_members()
    power_levels_event = admin_client.api.get_power_levels(room_id)
    admin_user_ids = [
        key for key, value in power_levels_event["users"].items()
        if value >= 50
    ]
    non_admin_user_ids = [
        member for member in members if member not in admin_user_ids
    ]
    # transport0 and transport1 should still be in non_admin_user_ids
    assert len(non_admin_user_ids) > 1
    kick_user_id = non_admin_user_ids[0]

    # kick one user
    admin_client.api.kick_user(room_id, kick_user_id)

    # Assert missing member
    members_after_kick = _get_joined_room_members()
    assert len(members_after_kick) == len(members) - 1
    members_after_kick.add(kick_user_id)
    assert members_after_kick == members

    # check assumption that new user does not receive presence
    raiden_service2 = MockRaidenService()

    def local_presence_listener(event, event_id):  # pylint: disable=unused-argument
        assert event["sender"] != kick_user_id

    transport2._client.add_presence_listener(local_presence_listener)
    transport2.start(raiden_service2, [], None)

    transport2.stop()

    # rejoin and assert that normal user cannot kick
    kicked_transport = transport0 if transport0._user_id == kick_user_id else transport1
    kicked_transport._client.join_room(broadcast_room_alias)

    with pytest.raises(MatrixRequestError):
        kicked_transport._client.api.kick_user(room_id, non_admin_user_ids[1])
Beispiel #24
0
def first_login(client: GMatrixClient, signer: Signer, username: str,
                cap_str: str) -> User:
    """Login within a server.

    There are multiple cases where a previous auth token can become invalid and
    a new login is necessary:

    - The server is configured to automatically invalidate tokens after a while
      (not the default)
    - A server operator may manually wipe or invalidate existing access tokens
    - A node may have roamed to a different server (e.g. because the original
      server was temporarily unavailable) and is now 'returning' to the
      previously used server.

    This relies on the Matrix server having the `eth_auth_provider` plugin
    installed, the plugin will automatically create the user on the first
    login. The plugin requires the password to be the signature of the server
    hostname, verified by the server to prevent account creation spam.

    Displayname is the signature of the whole user_id (including homeserver),
    to be verified by other peers and prevent impersonation attacks.
    """
    server_url = client.api.base_url
    server_name = urlparse(server_url).netloc

    # The plugin `eth_auth_provider` expects a signature of the server_name as
    # the user's password.
    #
    # For a honest matrix server:
    #
    # - This prevents impersonation attacks / name squatting, since the plugin
    # will validate the username by recovering the address from the signature
    # and check the recovered address and the username matches.
    #
    # For a badly configured server (one without the plugin):
    #
    # - An attacker can front run and register the username before the honest
    # user:
    #    - Because the attacker cannot guess the correct password, when the
    #    honest node tries to login it will fail, which tells us the server is
    #    improperly configured and should be blacklisted.
    #    - The attacker cannot forge a signature to use as a display name, so
    #    the partner node can tell there is a malicious node trying to
    #    eavesdrop the conversation and that matrix server should be
    #    blacklisted.
    # - The username is available, but because the plugin is not installed the
    # login will fail since the user is not registered. Here too one can infer
    # the server is improperly configured and blacklist the server.
    password = encode_hex(signer.sign(server_name.encode()))

    # Disabling sync because login is done before the transport is fully
    # initialized, i.e. the inventory rooms don't have the callbacks installed.
    client.login(username, password, sync=False, device_id="raiden")

    # Because this is the first login, the display name has to be set, this
    # prevents the impersonation mentioned above. subsequent calls will reuse
    # the authentication token and the display name will be properly set.
    signature_bytes = signer.sign(client.user_id.encode())
    signature_hex = encode_hex(signature_bytes)

    user = client.get_user(client.user_id)

    try:
        current_display_name = user.get_display_name()
    except MatrixRequestError as ex:
        # calling site
        log.error(
            "Ignoring Matrix error in `get_display_name`",
            exc_info=ex,
            user_id=user.user_id,
        )
        current_display_name = ""

    # Only set the display name if necessary, since this is a slow operation.
    if current_display_name != signature_hex:
        user.set_display_name(signature_hex)

    try:
        current_capabilities = user.get_avatar_url() or ""
    except MatrixRequestError as ex:
        log.error(
            "Ignoring Matrix error in `get_avatar_url`",
            exc_info=ex,
            user_id=user.user_id,
        )
        current_capabilities = ""

    # Only set the capabilities if necessary.
    if current_capabilities != cap_str:
        user.set_avatar_url(cap_str)

    log.debug(
        "Logged in",
        node=to_checksum_address(username),
        homeserver=server_name,
        server_url=server_url,
    )
    return user
Beispiel #25
0
 def make_client_monkey(
     handle_messages_callback, servers, *args, **kwargs
 ):  # pylint: disable=unused-argument
     return GMatrixClient(handle_messages_callback, servers[0])
Beispiel #26
0
 def add_client(self, client: GMatrixClient) -> UUID:
     server_url = client.api.base_url
     self.server_url_to_listener_id[
         server_url] = client.add_presence_listener(
             self._create_presence_listener(server_url))
     return self.server_url_to_listener_id[server_url]
Beispiel #27
0
 def remove_client(self, client: GMatrixClient) -> None:
     listener_id = self.server_url_to_listener_id.pop(
         client.api.base_url, None)
     if listener_id is not None:
         client.remove_presence_listener(listener_id)