def _part_user(self, user_id):
        """Causes the given user_id to leave all the rooms they're joined to

        Returns:
            None
        """
        user = UserID.from_string(user_id)

        rooms_for_user = yield self.store.get_rooms_for_user(user_id)
        for room_id in rooms_for_user:
            logger.info("User parter parting %r from %r", user_id, room_id)
            try:
                yield self._room_member_handler.update_membership(
                    create_requester(user),
                    user,
                    room_id,
                    "leave",
                    ratelimit=False,
                    require_consent=False,
                )
            except Exception:
                logger.exception(
                    "Failed to part user %r from room %r: ignoring and continuing",
                    user_id, room_id,
                )
Beispiel #2
0
 def test_user_is_created_and_logged_in_if_doesnt_exist(self):
     local_part = "someone"
     display_name = "someone"
     user_id = "@someone:test"
     requester = create_requester("@as:test")
     result_user_id, result_token = yield self.handler.get_or_create_user(
         requester, local_part, display_name)
     self.assertEquals(result_user_id, user_id)
     self.assertEquals(result_token, 'secret')
 def test_user_is_created_and_logged_in_if_doesnt_exist(self):
     frank = UserID.from_string("@frank:test")
     user_id = frank.to_string()
     requester = create_requester(user_id)
     result_user_id, result_token = yield self.handler.get_or_create_user(
         requester, frank.localpart, "Frankie"
     )
     self.assertEquals(result_user_id, user_id)
     self.assertEquals(result_token, 'secret')
Beispiel #4
0
    def _auto_join_rooms(self, user_id):
        """Automatically joins users to auto join rooms - creating the room in the first place
        if the user is the first to be created.

        Args:
            user_id(str): The user to join
        """
        # auto-join the user to any rooms we're supposed to dump them into
        fake_requester = create_requester(user_id)

        # try to create the room if we're the first real user on the server. Note
        # that an auto-generated support user is not a real user and will never be
        # the user to create the room
        should_auto_create_rooms = False
        is_support = yield self.store.is_support_user(user_id)
        # There is an edge case where the first user is the support user, then
        # the room is never created, though this seems unlikely and
        # recoverable from given the support user being involved in the first
        # place.
        if self.hs.config.autocreate_auto_join_rooms and not is_support:
            count = yield self.store.count_all_users()
            should_auto_create_rooms = count == 1
        for r in self.hs.config.auto_join_rooms:
            try:
                if should_auto_create_rooms:
                    room_alias = RoomAlias.from_string(r)
                    if self.hs.hostname != room_alias.domain:
                        logger.warning(
                            'Cannot create room alias %s, '
                            'it does not match server domain',
                            r,
                        )
                    else:
                        # create room expects the localpart of the room alias
                        room_alias_localpart = room_alias.localpart

                        # getting the RoomCreationHandler during init gives a dependency
                        # loop
                        yield self.hs.get_room_creation_handler().create_room(
                            fake_requester,
                            config={
                                "preset": "public_chat",
                                "room_alias_name": room_alias_localpart
                            },
                            ratelimit=False,
                        )
                else:
                    yield self._join_user_to_room(fake_requester, r)
            except ConsentNotGivenError as e:
                # Technically not necessary to pull out this error though
                # moving away from bare excepts is a good thing to do.
                logger.error("Failed to join new user to %r: %r", r, e)
            except Exception as e:
                logger.error("Failed to join new user to %r: %r", r, e)
Beispiel #5
0
    def prepare(self, reactor, clock, hs):
        self.mock_distributor = Mock()
        self.mock_distributor.declare("registered_user")
        self.mock_captcha_client = Mock()
        self.macaroon_generator = Mock(
            generate_access_token=Mock(return_value='secret')
        )
        self.hs.get_macaroon_generator = Mock(return_value=self.macaroon_generator)
        self.handler = self.hs.get_registration_handler()
        self.store = self.hs.get_datastore()
        self.lots_of_users = 100
        self.small_number_of_users = 1

        self.requester = create_requester("@requester:test")
Beispiel #6
0
 def test_if_user_exists(self):
     store = self.hs.get_datastore()
     frank = UserID.from_string("@frank:test")
     yield store.register(
         user_id=frank.to_string(),
         token="jkv;g498752-43gj['eamb!-5",
         password_hash=None)
     local_part = "frank"
     display_name = "Frank"
     user_id = "@frank:test"
     requester = create_requester("@as:test")
     result_user_id, result_token = yield self.handler.get_or_create_user(
         requester, local_part, display_name)
     self.assertEquals(result_user_id, user_id)
     self.assertEquals(result_token, 'secret')
Beispiel #7
0
 def test_if_user_exists(self):
     store = self.hs.get_datastore()
     frank = UserID.from_string("@frank:test")
     yield store.register(
         user_id=frank.to_string(),
         token="jkv;g498752-43gj['eamb!-5",
         password_hash=None,
     )
     local_part = frank.localpart
     user_id = frank.to_string()
     requester = create_requester(user_id)
     result_user_id, result_token = yield self.handler.get_or_create_user(
         requester, local_part, None
     )
     self.assertEquals(result_user_id, user_id)
     self.assertTrue(result_token is not None)
Beispiel #8
0
    def on_POST(self, request):
        user_json = parse_json_object_from_request(request)

        access_token = get_access_token_from_request(request)
        app_service = self.store.get_app_service_by_token(
            access_token
        )
        if not app_service:
            raise SynapseError(403, "Invalid application service token.")

        requester = create_requester(app_service.sender)

        logger.debug("creating user: %s", user_json)
        response = yield self._do_create(requester, user_json)

        defer.returnValue((200, response))
    def setUp(self):
        self.mock_distributor = Mock()
        self.mock_distributor.declare("registered_user")
        self.mock_captcha_client = Mock()
        self.hs = yield setup_test_homeserver(
            self.addCleanup,
            expire_access_token=True,
        )
        self.macaroon_generator = Mock(
            generate_access_token=Mock(return_value='secret')
        )
        self.hs.get_macaroon_generator = Mock(return_value=self.macaroon_generator)
        self.handler = self.hs.get_handlers().registration_handler
        self.store = self.hs.get_datastore()
        self.hs.config.max_mau_value = 50
        self.lots_of_users = 100
        self.small_number_of_users = 1

        self.requester = create_requester("@requester:test")
    def send_notice(
        self, user_id, event_content,
        type=EventTypes.Message, state_key=None
    ):
        """Send a notice to the given user

        Creates the server notices room, if none exists.

        Args:
            user_id (str): mxid of user to send event to.
            event_content (dict): content of event to send
            type(EventTypes): type of event
            is_state_event(bool): Is the event a state event

        Returns:
            Deferred[FrozenEvent]
        """
        room_id = yield self.get_notice_room_for_user(user_id)

        system_mxid = self._config.server_notices_mxid
        requester = create_requester(system_mxid)

        logger.info("Sending server notice to %s", user_id)

        event_dict = {
            "type": type,
            "room_id": room_id,
            "sender": system_mxid,
            "content": event_content,
        }

        if state_key is not None:
            event_dict['state_key'] = state_key

        res = yield self._event_creation_handler.create_and_send_nonmember_event(
            requester, event_dict, ratelimit=False,
        )
        defer.returnValue(res)
Beispiel #11
0
    async def on_POST(self, request: SynapseRequest,
                      room_identifier: str) -> Tuple[int, JsonDict]:
        # This will always be set by the time Twisted calls us.
        assert request.args is not None

        requester = await self.auth.get_user_by_req(request)
        await assert_user_is_admin(self.auth, requester.user)

        content = parse_json_object_from_request(request)

        assert_params_in_dict(content, ["user_id"])
        target_user = UserID.from_string(content["user_id"])

        if not self.hs.is_mine(target_user):
            raise SynapseError(
                400, "This endpoint can only be used with local users")

        if not await self.admin_handler.get_user(target_user):
            raise NotFoundError("User not found")

        # Get the room ID from the identifier.
        try:
            remote_room_hosts = [
                x.decode("ascii") for x in request.args[b"server_name"]
            ]  # type: Optional[List[str]]
        except Exception:
            remote_room_hosts = None
        room_id, remote_room_hosts = await self.resolve_room_id(
            room_identifier, remote_room_hosts)

        fake_requester = create_requester(
            target_user, authenticated_entity=requester.authenticated_entity)

        # send invite if room has "JoinRules.INVITE"
        room_state = await self.state_handler.get_current_state(room_id)
        join_rules_event = room_state.get((EventTypes.JoinRules, ""))
        if join_rules_event:
            if not (join_rules_event.content.get("join_rule")
                    == JoinRules.PUBLIC):
                # update_membership with an action of "invite" can raise a
                # ShadowBanError. This is not handled since it is assumed that
                # an admin isn't going to call this API with a shadow-banned user.
                await self.room_member_handler.update_membership(
                    requester=requester,
                    target=fake_requester.user,
                    room_id=room_id,
                    action="invite",
                    remote_room_hosts=remote_room_hosts,
                    ratelimit=False,
                )

        await self.room_member_handler.update_membership(
            requester=fake_requester,
            target=fake_requester.user,
            room_id=room_id,
            action="join",
            remote_room_hosts=remote_room_hosts,
            ratelimit=False,
        )

        return 200, {"room_id": room_id}
Beispiel #12
0
    async def get_user_by_req(
        self,
        request: SynapseRequest,
        allow_guest: bool = False,
        rights: str = "access",
        allow_expired: bool = False,
    ) -> Requester:
        """Get a registered user's ID.

        Args:
            request: An HTTP request with an access_token query parameter.
            allow_guest: If False, will raise an AuthError if the user making the
                request is a guest.
            rights: The operation being performed; the access token must allow this
            allow_expired: If True, allow the request through even if the account
                is expired, or session token lifetime has ended. Note that
                /login will deliver access tokens regardless of expiration.

        Returns:
            Resolves to the requester
        Raises:
            InvalidClientCredentialsError if no user by that token exists or the token
                is invalid.
            AuthError if access is denied for the user in the access token
        """
        try:
            ip_addr = request.getClientIP()
            user_agent = get_request_user_agent(request)

            access_token = self.get_access_token_from_request(request)

            user_id, app_service = await self._get_appservice_user_id(request)
            if user_id and app_service:
                if ip_addr and self._track_appservice_user_ips:
                    await self.store.insert_client_ip(
                        user_id=user_id,
                        access_token=access_token,
                        ip=ip_addr,
                        user_agent=user_agent,
                        device_id="dummy-device",  # stubbed
                    )

                requester = create_requester(user_id, app_service=app_service)

                request.requester = user_id
                if user_id in self._force_tracing_for_users:
                    opentracing.force_tracing()
                opentracing.set_tag("authenticated_entity", user_id)
                opentracing.set_tag("user_id", user_id)
                opentracing.set_tag("appservice_id", app_service.id)

                return requester

            user_info = await self.get_user_by_access_token(
                access_token, rights, allow_expired=allow_expired
            )
            token_id = user_info.token_id
            is_guest = user_info.is_guest
            shadow_banned = user_info.shadow_banned

            # Deny the request if the user account has expired.
            if not allow_expired:
                if await self._account_validity_handler.is_user_expired(
                    user_info.user_id
                ):
                    # Raise the error if either an account validity module has determined
                    # the account has expired, or the legacy account validity
                    # implementation is enabled and determined the account has expired
                    raise AuthError(
                        403,
                        "User account has expired",
                        errcode=Codes.EXPIRED_ACCOUNT,
                    )

            device_id = user_info.device_id

            if access_token and ip_addr:
                await self.store.insert_client_ip(
                    user_id=user_info.token_owner,
                    access_token=access_token,
                    ip=ip_addr,
                    user_agent=user_agent,
                    device_id=device_id,
                )

            if is_guest and not allow_guest:
                raise AuthError(
                    403,
                    "Guest access not allowed",
                    errcode=Codes.GUEST_ACCESS_FORBIDDEN,
                )

            # Mark the token as used. This is used to invalidate old refresh
            # tokens after some time.
            if not user_info.token_used and token_id is not None:
                await self.store.mark_access_token_as_used(token_id)

            requester = create_requester(
                user_info.user_id,
                token_id,
                is_guest,
                shadow_banned,
                device_id,
                app_service=app_service,
                authenticated_entity=user_info.token_owner,
            )

            request.requester = requester
            if user_info.token_owner in self._force_tracing_for_users:
                opentracing.force_tracing()
            opentracing.set_tag("authenticated_entity", user_info.token_owner)
            opentracing.set_tag("user_id", user_info.user_id)
            if device_id:
                opentracing.set_tag("device_id", device_id)

            return requester
        except KeyError:
            raise MissingClientTokenError()
Beispiel #13
0
    def _send_dummy_events_to_fill_extremities(self):
        """Background task to send dummy events into rooms that have a large
        number of extremities
        """
        self._expire_rooms_to_exclude_from_dummy_event_insertion()
        room_ids = yield self.store.get_rooms_with_many_extremities(
            min_count=10,
            limit=5,
            room_id_filter=self._rooms_to_exclude_from_dummy_event_insertion.
            keys(),
        )

        for room_id in room_ids:
            # For each room we need to find a joined member we can use to send
            # the dummy event with.

            prev_events_and_hashes = yield self.store.get_prev_events_for_room(
                room_id)

            latest_event_ids = (event_id
                                for (event_id, _, _) in prev_events_and_hashes)

            members = yield self.state.get_current_users_in_room(
                room_id, latest_event_ids=latest_event_ids)
            dummy_event_sent = False
            for user_id in members:
                if not self.hs.is_mine_id(user_id):
                    continue
                requester = create_requester(user_id)
                try:
                    event, context = yield self.create_event(
                        requester,
                        {
                            "type": "org.matrix.dummy_event",
                            "content": {},
                            "room_id": room_id,
                            "sender": user_id,
                        },
                        prev_events_and_hashes=prev_events_and_hashes,
                    )

                    event.internal_metadata.proactively_send = False

                    yield self.send_nonmember_event(requester,
                                                    event,
                                                    context,
                                                    ratelimit=False)
                    dummy_event_sent = True
                    break
                except ConsentNotGivenError:
                    logger.info(
                        "Failed to send dummy event into room %s for user %s due to "
                        "lack of consent. Will try another user" %
                        (room_id, user_id))
                except AuthError:
                    logger.info(
                        "Failed to send dummy event into room %s for user %s due to "
                        "lack of power. Will try another user" %
                        (room_id, user_id))

            if not dummy_event_sent:
                # Did not find a valid user in the room, so remove from future attempts
                # Exclusion is time limited, so the room will be rechecked in the future
                # dependent on _DUMMY_EVENT_ROOM_EXCLUSION_EXPIRY
                logger.info(
                    "Failed to send dummy event into room %s. Will exclude it from "
                    "future attempts until cache expires" % (room_id, ))
                now = self.clock.time_msec()
                self._rooms_to_exclude_from_dummy_event_insertion[
                    room_id] = now
Beispiel #14
0
    def register(
        self,
        localpart=None,
        password=None,
        generate_token=True,
        guest_access_token=None,
        make_guest=False,
        admin=False,
        threepid=None,
    ):
        """Registers a new client on the server.

        Args:
            localpart : The local part of the user ID to register. If None,
              one will be generated.
            password (unicode) : The password to assign to this user so they can
              login again. This can be None which means they cannot login again
              via a password (e.g. the user is an application service user).
            generate_token (bool): Whether a new access token should be
              generated. Having this be True should be considered deprecated,
              since it offers no means of associating a device_id with the
              access_token. Instead you should call auth_handler.issue_access_token
              after registration.
        Returns:
            A tuple of (user_id, access_token).
        Raises:
            RegistrationError if there was a problem registering.
        """

        yield self.auth.check_auth_blocking(threepid=threepid)
        password_hash = None
        if password:
            password_hash = yield self.auth_handler().hash(password)

        if localpart:
            yield self.check_username(localpart,
                                      guest_access_token=guest_access_token)

            was_guest = guest_access_token is not None

            if not was_guest:
                try:
                    int(localpart)
                    raise RegistrationError(
                        400, "Numeric user IDs are reserved for guest users.")
                except ValueError:
                    pass

            user = UserID(localpart, self.hs.hostname)
            user_id = user.to_string()

            token = None
            if generate_token:
                token = self.macaroon_gen.generate_access_token(user_id)
            yield self.store.register(
                user_id=user_id,
                token=token,
                password_hash=password_hash,
                was_guest=was_guest,
                make_guest=make_guest,
                create_profile_with_localpart=(
                    # If the user was a guest then they already have a profile
                    None if was_guest else user.localpart),
                admin=admin,
            )

            if self.hs.config.user_directory_search_all_users:
                profile = yield self.store.get_profileinfo(localpart)
                yield self.user_directory_handler.handle_local_profile_change(
                    user_id, profile)

        else:
            # autogen a sequential user ID
            attempts = 0
            token = None
            user = None
            while not user:
                localpart = yield self._generate_user_id(attempts > 0)
                user = UserID(localpart, self.hs.hostname)
                user_id = user.to_string()
                yield self.check_user_id_not_appservice_exclusive(user_id)
                if generate_token:
                    token = self.macaroon_gen.generate_access_token(user_id)
                try:
                    yield self.store.register(
                        user_id=user_id,
                        token=token,
                        password_hash=password_hash,
                        make_guest=make_guest,
                        create_profile_with_localpart=user.localpart,
                    )
                except SynapseError:
                    # if user id is taken, just generate another
                    user = None
                    user_id = None
                    token = None
                    attempts += 1

        # auto-join the user to any rooms we're supposed to dump them into
        fake_requester = create_requester(user_id)

        # try to create the room if we're the first user on the server
        should_auto_create_rooms = False
        if self.hs.config.autocreate_auto_join_rooms:
            count = yield self.store.count_all_users()
            should_auto_create_rooms = count == 1

        for r in self.hs.config.auto_join_rooms:
            try:
                if should_auto_create_rooms:
                    room_alias = RoomAlias.from_string(r)
                    if self.hs.hostname != room_alias.domain:
                        logger.warning(
                            'Cannot create room alias %s, '
                            'it does not match server domain',
                            r,
                        )
                    else:
                        # create room expects the localpart of the room alias
                        room_alias_localpart = room_alias.localpart
                        yield self.room_creation_handler.create_room(
                            fake_requester,
                            config={
                                "preset": "public_chat",
                                "room_alias_name": room_alias_localpart
                            },
                            ratelimit=False,
                        )
                else:
                    yield self._join_user_to_room(fake_requester, r)
            except Exception as e:
                logger.error("Failed to join new user to %r: %r", r, e)

        # We used to generate default identicons here, but nowadays
        # we want clients to generate their own as part of their branding
        # rather than there being consistent matrix-wide ones, so we don't.
        defer.returnValue((user_id, token))
 def prepare(self, reactor, clock, hs):
     self.store = hs.get_datastore()
     self.user_id = self.register_user("foo", "pass")
     self.token = self.login("foo", "pass")
     self.requester = create_requester(self.user_id)
Beispiel #16
0
    def on_POST(self, request, room_id):
        requester = yield self.auth.get_user_by_req(request)
        is_admin = yield self.auth.is_server_admin(requester.user)
        if not is_admin:
            raise AuthError(403, "You are not a server admin")

        content = parse_json_object_from_request(request)
        assert_params_in_dict(content, ["new_room_user_id"])
        new_room_user_id = content["new_room_user_id"]

        room_creator_requester = create_requester(new_room_user_id)

        message = content.get("message", self.DEFAULT_MESSAGE)
        room_name = content.get("room_name", "Content Violation Notification")

        info = yield self._room_creation_handler.create_room(
            room_creator_requester,
            config={
                "preset": "public_chat",
                "name": room_name,
                "power_level_content_override": {
                    "users_default": -10,
                },
            },
            ratelimit=False,
        )
        new_room_id = info["room_id"]

        yield self.event_creation_handler.create_and_send_nonmember_event(
            room_creator_requester,
            {
                "type": "m.room.message",
                "content": {"body": message, "msgtype": "m.text"},
                "room_id": new_room_id,
                "sender": new_room_user_id,
            },
            ratelimit=False,
        )

        requester_user_id = requester.user.to_string()

        logger.info("Shutting down room %r", room_id)

        yield self.store.block_room(room_id, requester_user_id)

        users = yield self.state.get_current_user_in_room(room_id)
        kicked_users = []
        for user_id in users:
            if not self.hs.is_mine_id(user_id):
                continue

            logger.info("Kicking %r from %r...", user_id, room_id)

            target_requester = create_requester(user_id)
            yield self.room_member_handler.update_membership(
                requester=target_requester,
                target=target_requester.user,
                room_id=room_id,
                action=Membership.LEAVE,
                content={},
                ratelimit=False
            )

            yield self.room_member_handler.forget(target_requester.user, room_id)

            yield self.room_member_handler.update_membership(
                requester=target_requester,
                target=target_requester.user,
                room_id=new_room_id,
                action=Membership.JOIN,
                content={},
                ratelimit=False
            )

            kicked_users.append(user_id)

        aliases_for_room = yield self.store.get_aliases_for_room(room_id)

        yield self.store.update_aliases_for_room(
            room_id, new_room_id, requester_user_id
        )

        defer.returnValue((200, {
            "kicked_users": kicked_users,
            "local_aliases": aliases_for_room,
            "new_room_id": new_room_id,
        }))
Beispiel #17
0
    def test_send_typing_sharded(self):
        """Test that using two federation sender workers correctly sends
        new typing EDUs.
        """
        mock_client1 = Mock(spec=["put_json"])
        mock_client1.put_json.return_value = make_awaitable({})
        self.make_worker_hs(
            "synapse.app.federation_sender",
            {
                "send_federation": True,
                "worker_name": "sender1",
                "federation_sender_instances": ["sender1", "sender2"],
            },
            federation_http_client=mock_client1,
        )

        mock_client2 = Mock(spec=["put_json"])
        mock_client2.put_json.return_value = make_awaitable({})
        self.make_worker_hs(
            "synapse.app.federation_sender",
            {
                "send_federation": True,
                "worker_name": "sender2",
                "federation_sender_instances": ["sender1", "sender2"],
            },
            federation_http_client=mock_client2,
        )

        user = self.register_user("user3", "pass")
        token = self.login("user3", "pass")

        typing_handler = self.hs.get_typing_handler()

        sent_on_1 = False
        sent_on_2 = False
        for i in range(20):
            server_name = "other_server_%d" % (i, )
            room = self.create_room_with_remote_server(user, token,
                                                       server_name)
            mock_client1.reset_mock()  # type: ignore[attr-defined]
            mock_client2.reset_mock()  # type: ignore[attr-defined]

            self.get_success(
                typing_handler.started_typing(
                    target_user=UserID.from_string(user),
                    requester=create_requester(user),
                    room_id=room,
                    timeout=20000,
                ))

            self.replicate()

            if mock_client1.put_json.called:
                sent_on_1 = True
                mock_client2.put_json.assert_not_called()
                self.assertEqual(mock_client1.put_json.call_args[0][0],
                                 server_name)
                self.assertTrue(
                    mock_client1.put_json.call_args[1]["data"].get("edus"))
            elif mock_client2.put_json.called:
                sent_on_2 = True
                mock_client1.put_json.assert_not_called()
                self.assertEqual(mock_client2.put_json.call_args[0][0],
                                 server_name)
                self.assertTrue(
                    mock_client2.put_json.call_args[1]["data"].get("edus"))
            else:
                raise AssertionError(
                    "Expected send transaction from one or the other sender")

            if sent_on_1 and sent_on_2:
                break

        self.assertTrue(sent_on_1)
        self.assertTrue(sent_on_2)
Beispiel #18
0
    async def update_room_membership(
        self,
        sender: str,
        target: str,
        room_id: str,
        new_membership: str,
        content: Optional[JsonDict] = None,
    ) -> EventBase:
        """Updates the membership of a user to the given value.

        Added in Synapse v1.46.0.

        Args:
            sender: The user performing the membership change. Must be a user local to
                this homeserver.
            target: The user whose membership is changing. This is often the same value
                as `sender`, but it might differ in some cases (e.g. when kicking a user,
                the `sender` is the user performing the kick and the `target` is the user
                being kicked).
            room_id: The room in which to change the membership.
            new_membership: The new membership state of `target` after this operation. See
                https://spec.matrix.org/unstable/client-server-api/#mroommember for the
                list of allowed values.
            content: Additional values to include in the resulting event's content.

        Returns:
            The newly created membership event.

        Raises:
            RuntimeError if the `sender` isn't a local user.
            ShadowBanError if a shadow-banned requester attempts to send an invite.
            SynapseError if the module attempts to send a membership event that isn't
                allowed, either by the server's configuration (e.g. trying to set a
                per-room display name that's too long) or by the validation rules around
                membership updates (e.g. the `membership` value is invalid).
        """
        if not self.is_mine(sender):
            raise RuntimeError(
                "Tried to send an event as a user that isn't local to this homeserver",
            )

        requester = create_requester(sender)
        target_user_id = UserID.from_string(target)

        if content is None:
            content = {}

        # Set the profile if not already done by the module.
        if "avatar_url" not in content or "displayname" not in content:
            try:
                # Try to fetch the user's profile.
                profile = await self._hs.get_profile_handler().get_profile(
                    target_user_id.to_string(), )
            except SynapseError as e:
                # If the profile couldn't be found, use default values.
                profile = {
                    "displayname": target_user_id.localpart,
                    "avatar_url": None,
                }

                if e.code != 404:
                    # If the error isn't 404, it means we tried to fetch the profile over
                    # federation but the remote server responded with a non-standard
                    # status code.
                    logger.error(
                        "Got non-404 error status when fetching profile for %s",
                        target_user_id.to_string(),
                    )

            # Set the profile where it needs to be set.
            if "avatar_url" not in content:
                content["avatar_url"] = profile["avatar_url"]

            if "displayname" not in content:
                content["displayname"] = profile["displayname"]

        event_id, _ = await self._hs.get_room_member_handler(
        ).update_membership(
            requester=requester,
            target=target_user_id,
            room_id=room_id,
            action=new_membership,
            content=content,
        )

        # Try to retrieve the resulting event.
        event = await self._hs.get_datastore().get_event(event_id)

        # update_membership is supposed to always return after the event has been
        # successfully persisted.
        assert event is not None

        return event
Beispiel #19
0
    async def set_displayname(
        self,
        target_user: UserID,
        requester: Requester,
        new_displayname: str,
        by_admin: bool = False,
        deactivation: bool = False,
    ) -> None:
        """Set the displayname of a user

        Args:
            target_user: the user whose displayname is to be changed.
            requester: The user attempting to make this change.
            new_displayname: The displayname to give this user.
            by_admin: Whether this change was made by an administrator.
            deactivation: Whether this change was made while deactivating the user.
        """
        if not self.hs.is_mine(target_user):
            raise SynapseError(400, "User is not hosted on this homeserver")

        if not by_admin and target_user != requester.user:
            raise AuthError(400, "Cannot set another user's displayname")

        if not by_admin and not self.hs.config.registration.enable_set_displayname:
            profile = await self.store.get_profileinfo(target_user.localpart)
            if profile.display_name:
                raise SynapseError(
                    400,
                    "Changing display name is disabled on this server",
                    Codes.FORBIDDEN,
                )

        if not isinstance(new_displayname, str):
            raise SynapseError(400,
                               "'displayname' must be a string",
                               errcode=Codes.INVALID_PARAM)

        if len(new_displayname) > MAX_DISPLAYNAME_LEN:
            raise SynapseError(
                400,
                "Displayname is too long (max %i)" % (MAX_DISPLAYNAME_LEN, ))

        displayname_to_set: Optional[str] = new_displayname
        if new_displayname == "":
            displayname_to_set = None

        # If the admin changes the display name of a user, the requesting user cannot send
        # the join event to update the displayname in the rooms.
        # This must be done by the target user himself.
        if by_admin:
            requester = create_requester(
                target_user,
                authenticated_entity=requester.authenticated_entity,
            )

        await self.store.set_profile_displayname(target_user.localpart,
                                                 displayname_to_set)

        profile = await self.store.get_profileinfo(target_user.localpart)
        await self.user_directory_handler.handle_local_profile_change(
            target_user.to_string(), profile)

        await self._third_party_rules.on_profile_update(
            target_user.to_string(), profile, by_admin, deactivation)

        await self._update_join_states(requester, target_user)
Beispiel #20
0
    async def on_POST(self, request, room_id):
        requester = await self.auth.get_user_by_req(request)
        await assert_user_is_admin(self.auth, requester.user)

        content = parse_json_object_from_request(request)
        assert_params_in_dict(content, ["new_room_user_id"])
        new_room_user_id = content["new_room_user_id"]

        room_creator_requester = create_requester(new_room_user_id)

        message = content.get("message", self.DEFAULT_MESSAGE)
        room_name = content.get("room_name", "Content Violation Notification")

        info = await self._room_creation_handler.create_room(
            room_creator_requester,
            config={
                "preset": "public_chat",
                "name": room_name,
                "power_level_content_override": {
                    "users_default": -10
                },
            },
            ratelimit=False,
        )
        new_room_id = info["room_id"]

        requester_user_id = requester.user.to_string()

        logger.info("Shutting down room %r, joining to new room: %r", room_id,
                    new_room_id)

        # This will work even if the room is already blocked, but that is
        # desirable in case the first attempt at blocking the room failed below.
        await self.store.block_room(room_id, requester_user_id)

        users = await self.state.get_current_users_in_room(room_id)
        kicked_users = []
        failed_to_kick_users = []
        for user_id in users:
            if not self.hs.is_mine_id(user_id):
                continue

            logger.info("Kicking %r from %r...", user_id, room_id)

            try:
                target_requester = create_requester(user_id)
                await self.room_member_handler.update_membership(
                    requester=target_requester,
                    target=target_requester.user,
                    room_id=room_id,
                    action=Membership.LEAVE,
                    content={},
                    ratelimit=False,
                    require_consent=False,
                )

                await self.room_member_handler.forget(target_requester.user,
                                                      room_id)

                await self.room_member_handler.update_membership(
                    requester=target_requester,
                    target=target_requester.user,
                    room_id=new_room_id,
                    action=Membership.JOIN,
                    content={},
                    ratelimit=False,
                    require_consent=False,
                )

                kicked_users.append(user_id)
            except Exception:
                logger.exception(
                    "Failed to leave old room and join new room for %r",
                    user_id)
                failed_to_kick_users.append(user_id)

        await self.event_creation_handler.create_and_send_nonmember_event(
            room_creator_requester,
            {
                "type": "m.room.message",
                "content": {
                    "body": message,
                    "msgtype": "m.text"
                },
                "room_id": new_room_id,
                "sender": new_room_user_id,
            },
            ratelimit=False,
        )

        aliases_for_room = await maybe_awaitable(
            self.store.get_aliases_for_room(room_id))

        await self.store.update_aliases_for_room(room_id, new_room_id,
                                                 requester_user_id)

        return (
            200,
            {
                "kicked_users": kicked_users,
                "failed_to_kick_users": failed_to_kick_users,
                "local_aliases": aliases_for_room,
                "new_room_id": new_room_id,
            },
        )
Beispiel #21
0
    async def _remote_join(
        self,
        requester: Requester,
        remote_room_hosts: List[str],
        room_id: str,
        user: UserID,
        content: dict,
    ) -> Tuple[str, int]:
        """Implements RoomMemberHandler._remote_join
        """
        # filter ourselves out of remote_room_hosts: do_invite_join ignores it
        # and if it is the only entry we'd like to return a 404 rather than a
        # 500.
        remote_room_hosts = [
            host for host in remote_room_hosts if host != self.hs.hostname
        ]

        if len(remote_room_hosts) == 0:
            raise SynapseError(404, "No known servers")

        check_complexity = self.hs.config.limit_remote_rooms.enabled
        if check_complexity and self.hs.config.limit_remote_rooms.admins_can_join:
            check_complexity = not await self.auth.is_server_admin(user)

        if check_complexity:
            # Fetch the room complexity
            too_complex = await self._is_remote_room_too_complex(
                room_id, remote_room_hosts)
            if too_complex is True:
                raise SynapseError(
                    code=400,
                    msg=self.hs.config.limit_remote_rooms.complexity_error,
                    errcode=Codes.RESOURCE_LIMIT_EXCEEDED,
                )

        # We don't do an auth check if we are doing an invite
        # join dance for now, since we're kinda implicitly checking
        # that we are allowed to join when we decide whether or not we
        # need to do the invite/join dance.
        event_id, stream_id = await self.federation_handler.do_invite_join(
            remote_room_hosts, room_id, user.to_string(), content)
        await self._user_joined_room(user, room_id)

        # Check the room we just joined wasn't too large, if we didn't fetch the
        # complexity of it before.
        if check_complexity:
            if too_complex is False:
                # We checked, and we're under the limit.
                return event_id, stream_id

            # Check again, but with the local state events
            too_complex = await self._is_local_room_too_complex(room_id)

            if too_complex is False:
                # We're under the limit.
                return event_id, stream_id

            # The room is too large. Leave.
            requester = types.create_requester(user, None, False, None)
            await self.update_membership(requester=requester,
                                         target=user,
                                         room_id=room_id,
                                         action="leave")
            raise SynapseError(
                code=400,
                msg=self.hs.config.limit_remote_rooms.complexity_error,
                errcode=Codes.RESOURCE_LIMIT_EXCEEDED,
            )

        return event_id, stream_id
Beispiel #22
0
    async def _join_rooms(self, user_id: str):
        """
        Join or invite the user to the auto-join rooms.

        Args:
            user_id: The user to join
        """
        room_member_handler = self.hs.get_room_member_handler()

        for r in self.hs.config.registration.auto_join_rooms:
            logger.info("Auto-joining %s to %s", user_id, r)

            try:
                room_alias = RoomAlias.from_string(r)

                if RoomAlias.is_valid(r):
                    (
                        room_id,
                        remote_room_hosts,
                    ) = await room_member_handler.lookup_room_alias(room_alias)
                    room_id = room_id.to_string()
                else:
                    raise SynapseError(
                        400, "%s was not legal room ID or room alias" % (r, ))

                # Calculate whether the room requires an invite or can be
                # joined directly. Note that unless a join rule of public exists,
                # it is treated as requiring an invite.
                requires_invite = True

                state = await self.store.get_filtered_current_state_ids(
                    room_id,
                    StateFilter.from_types([(EventTypes.JoinRules, "")]))

                event_id = state.get((EventTypes.JoinRules, ""))
                if event_id:
                    join_rules_event = await self.store.get_event(
                        event_id, allow_none=True)
                    if join_rules_event:
                        join_rule = join_rules_event.content.get(
                            "join_rule", None)
                        requires_invite = join_rule and join_rule != JoinRules.PUBLIC

                # Send the invite, if necessary.
                if requires_invite:
                    await room_member_handler.update_membership(
                        requester=create_requester(
                            self.hs.config.registration.auto_join_user_id),
                        target=UserID.from_string(user_id),
                        room_id=room_id,
                        remote_room_hosts=remote_room_hosts,
                        action="invite",
                        ratelimit=False,
                    )

                # Send the join.
                await room_member_handler.update_membership(
                    requester=create_requester(user_id),
                    target=UserID.from_string(user_id),
                    room_id=room_id,
                    remote_room_hosts=remote_room_hosts,
                    action="join",
                    ratelimit=False,
                )

            except ConsentNotGivenError as e:
                # Technically not necessary to pull out this error though
                # moving away from bare excepts is a good thing to do.
                logger.error("Failed to join new user to %r: %r", r, e)
            except Exception as e:
                logger.error("Failed to join new user to %r: %r", r, e)
Beispiel #23
0
    async def _create_and_join_rooms(self, user_id: str):
        """
        Create the auto-join rooms and join or invite the user to them.

        This should only be called when the first "real" user registers.

        Args:
            user_id: The user to join
        """
        # Getting the handlers during init gives a dependency loop.
        room_creation_handler = self.hs.get_room_creation_handler()
        room_member_handler = self.hs.get_room_member_handler()

        # Generate a stub for how the rooms will be configured.
        stub_config = {
            "preset":
            self.hs.config.registration.autocreate_auto_join_room_preset,
        }

        # If the configuration providers a user ID to create rooms with, use
        # that instead of the first user registered.
        requires_join = False
        if self.hs.config.registration.auto_join_user_id:
            fake_requester = create_requester(
                self.hs.config.registration.auto_join_user_id)

            # If the room requires an invite, add the user to the list of invites.
            if self.hs.config.registration.auto_join_room_requires_invite:
                stub_config["invite"] = [user_id]

            # If the room is being created by a different user, the first user
            # registered needs to join it. Note that in the case of an invitation
            # being necessary this will occur after the invite was sent.
            requires_join = True
        else:
            fake_requester = create_requester(user_id)

        # Choose whether to federate the new room.
        if not self.hs.config.registration.autocreate_auto_join_rooms_federated:
            stub_config["creation_content"] = {"m.federate": False}

        for r in self.hs.config.registration.auto_join_rooms:
            logger.info("Auto-joining %s to %s", user_id, r)

            try:
                room_alias = RoomAlias.from_string(r)

                if self.hs.hostname != room_alias.domain:
                    logger.warning(
                        "Cannot create room alias %s, "
                        "it does not match server domain",
                        r,
                    )
                else:
                    # A shallow copy is OK here since the only key that is
                    # modified is room_alias_name.
                    config = stub_config.copy()
                    # create room expects the localpart of the room alias
                    config["room_alias_name"] = room_alias.localpart

                    info, _ = await room_creation_handler.create_room(
                        fake_requester,
                        config=config,
                        ratelimit=False,
                    )

                    # If the room does not require an invite, but another user
                    # created it, then ensure the first user joins it.
                    if requires_join:
                        await room_member_handler.update_membership(
                            requester=create_requester(user_id),
                            target=UserID.from_string(user_id),
                            room_id=info["room_id"],
                            # Since it was just created, there are no remote hosts.
                            remote_room_hosts=[],
                            action="join",
                            ratelimit=False,
                        )

            except ConsentNotGivenError as e:
                # Technically not necessary to pull out this error though
                # moving away from bare excepts is a good thing to do.
                logger.error("Failed to join new user to %r: %r", r, e)
            except Exception as e:
                logger.error("Failed to join new user to %r: %r", r, e)
Beispiel #24
0
    def register(
        self,
        localpart=None,
        password=None,
        generate_token=True,
        guest_access_token=None,
        make_guest=False,
        admin=False,
    ):
        """Registers a new client on the server.

        Args:
            localpart : The local part of the user ID to register. If None,
              one will be generated.
            password (str) : The password to assign to this user so they can
              login again. This can be None which means they cannot login again
              via a password (e.g. the user is an application service user).
            generate_token (bool): Whether a new access token should be
              generated. Having this be True should be considered deprecated,
              since it offers no means of associating a device_id with the
              access_token. Instead you should call auth_handler.issue_access_token
              after registration.
        Returns:
            A tuple of (user_id, access_token).
        Raises:
            RegistrationError if there was a problem registering.
        """
        yield run_on_reactor()
        password_hash = None
        if password:
            password_hash = yield self.auth_handler().hash(password)

        if localpart:
            yield self.check_username(localpart, guest_access_token=guest_access_token)

            was_guest = guest_access_token is not None

            if not was_guest:
                try:
                    int(localpart)
                    raise RegistrationError(
                        400,
                        "Numeric user IDs are reserved for guest users."
                    )
                except ValueError:
                    pass

            user = UserID(localpart, self.hs.hostname)
            user_id = user.to_string()

            token = None
            if generate_token:
                token = self.macaroon_gen.generate_access_token(user_id)
            yield self.store.register(
                user_id=user_id,
                token=token,
                password_hash=password_hash,
                was_guest=was_guest,
                make_guest=make_guest,
                create_profile_with_localpart=(
                    # If the user was a guest then they already have a profile
                    None if was_guest else user.localpart
                ),
                admin=admin,
            )

            if self.hs.config.user_directory_search_all_users:
                profile = yield self.store.get_profileinfo(localpart)
                yield self.user_directory_handler.handle_local_profile_change(
                    user_id, profile
                )

        else:
            # autogen a sequential user ID
            attempts = 0
            token = None
            user = None
            while not user:
                localpart = yield self._generate_user_id(attempts > 0)
                user = UserID(localpart, self.hs.hostname)
                user_id = user.to_string()
                yield self.check_user_id_not_appservice_exclusive(user_id)
                if generate_token:
                    token = self.macaroon_gen.generate_access_token(user_id)
                try:
                    yield self.store.register(
                        user_id=user_id,
                        token=token,
                        password_hash=password_hash,
                        make_guest=make_guest,
                        create_profile_with_localpart=user.localpart,
                    )
                except SynapseError:
                    # if user id is taken, just generate another
                    user = None
                    user_id = None
                    token = None
                    attempts += 1

        # auto-join the user to any rooms we're supposed to dump them into
        fake_requester = create_requester(user_id)
        for r in self.hs.config.auto_join_rooms:
            try:
                yield self._join_user_to_room(fake_requester, r)
            except Exception as e:
                logger.error("Failed to join new user to %r: %r", r, e)

        # We used to generate default identicons here, but nowadays
        # we want clients to generate their own as part of their branding
        # rather than there being consistent matrix-wide ones, so we don't.
        defer.returnValue((user_id, token))
Beispiel #25
0
    def register(
        self,
        localpart=None,
        password=None,
        generate_token=True,
        guest_access_token=None,
        make_guest=False,
        admin=False,
        threepid=None,
    ):
        """Registers a new client on the server.

        Args:
            localpart : The local part of the user ID to register. If None,
              one will be generated.
            password (unicode) : The password to assign to this user so they can
              login again. This can be None which means they cannot login again
              via a password (e.g. the user is an application service user).
            generate_token (bool): Whether a new access token should be
              generated. Having this be True should be considered deprecated,
              since it offers no means of associating a device_id with the
              access_token. Instead you should call auth_handler.issue_access_token
              after registration.
        Returns:
            A tuple of (user_id, access_token).
        Raises:
            RegistrationError if there was a problem registering.
        """

        yield self.auth.check_auth_blocking(threepid=threepid)
        password_hash = None
        if password:
            password_hash = yield self.auth_handler().hash(password)

        if localpart:
            yield self.check_username(localpart, guest_access_token=guest_access_token)

            was_guest = guest_access_token is not None

            if not was_guest:
                try:
                    int(localpart)
                    raise RegistrationError(
                        400,
                        "Numeric user IDs are reserved for guest users."
                    )
                except ValueError:
                    pass

            user = UserID(localpart, self.hs.hostname)
            user_id = user.to_string()

            token = None
            if generate_token:
                token = self.macaroon_gen.generate_access_token(user_id)
            yield self.store.register(
                user_id=user_id,
                token=token,
                password_hash=password_hash,
                was_guest=was_guest,
                make_guest=make_guest,
                create_profile_with_localpart=(
                    # If the user was a guest then they already have a profile
                    None if was_guest else user.localpart
                ),
                admin=admin,
            )

            if self.hs.config.user_directory_search_all_users:
                profile = yield self.store.get_profileinfo(localpart)
                yield self.user_directory_handler.handle_local_profile_change(
                    user_id, profile
                )

        else:
            # autogen a sequential user ID
            attempts = 0
            token = None
            user = None
            while not user:
                localpart = yield self._generate_user_id(attempts > 0)
                user = UserID(localpart, self.hs.hostname)
                user_id = user.to_string()
                yield self.check_user_id_not_appservice_exclusive(user_id)
                if generate_token:
                    token = self.macaroon_gen.generate_access_token(user_id)
                try:
                    yield self.store.register(
                        user_id=user_id,
                        token=token,
                        password_hash=password_hash,
                        make_guest=make_guest,
                        create_profile_with_localpart=user.localpart,
                    )
                except SynapseError:
                    # if user id is taken, just generate another
                    user = None
                    user_id = None
                    token = None
                    attempts += 1

        # auto-join the user to any rooms we're supposed to dump them into
        fake_requester = create_requester(user_id)

        # try to create the room if we're the first user on the server
        should_auto_create_rooms = False
        if self.hs.config.autocreate_auto_join_rooms:
            count = yield self.store.count_all_users()
            should_auto_create_rooms = count == 1

        for r in self.hs.config.auto_join_rooms:
            try:
                if should_auto_create_rooms:
                    room_alias = RoomAlias.from_string(r)
                    if self.hs.hostname != room_alias.domain:
                        logger.warning(
                            'Cannot create room alias %s, '
                            'it does not match server domain',
                            r,
                        )
                    else:
                        # create room expects the localpart of the room alias
                        room_alias_localpart = room_alias.localpart

                        # getting the RoomCreationHandler during init gives a dependency
                        # loop
                        yield self.hs.get_room_creation_handler().create_room(
                            fake_requester,
                            config={
                                "preset": "public_chat",
                                "room_alias_name": room_alias_localpart
                            },
                            ratelimit=False,
                        )
                else:
                    yield self._join_user_to_room(fake_requester, r)
            except Exception as e:
                logger.error("Failed to join new user to %r: %r", r, e)

        defer.returnValue((user_id, token))
    async def get_or_create_notice_room_for_user(self, user_id: str) -> str:
        """Get the room for notices for a given user

        If we have not yet created a notice room for this user, create it, but don't
        invite the user to it.

        Args:
            user_id: complete user id for the user we want a room for

        Returns:
            room id of notice room.
        """
        if not self.is_enabled():
            raise Exception("Server notices not enabled")

        assert self._is_mine_id(
            user_id), "Cannot send server notices to remote users"

        rooms = await self._store.get_rooms_for_local_user_where_membership_is(
            user_id, [Membership.INVITE, Membership.JOIN])
        for room in rooms:
            # it's worth noting that there is an asymmetry here in that we
            # expect the user to be invited or joined, but the system user must
            # be joined. This is kinda deliberate, in that if somebody somehow
            # manages to invite the system user to a room, that doesn't make it
            # the server notices room.
            user_ids = await self._store.get_users_in_room(room.room_id)
            if len(user_ids) <= 2 and self.server_notices_mxid in user_ids:
                # we found a room which our user shares with the system notice
                # user
                logger.info(
                    "Using existing server notices room %s for user %s",
                    room.room_id,
                    user_id,
                )
                return room.room_id

        # apparently no existing notice room: create a new one
        logger.info("Creating server notices room for %s", user_id)

        # see if we want to override the profile info for the server user.
        # note that if we want to override either the display name or the
        # avatar, we have to use both.
        join_profile = None
        if (self._config.server_notices_mxid_display_name is not None
                or self._config.server_notices_mxid_avatar_url is not None):
            join_profile = {
                "displayname": self._config.server_notices_mxid_display_name,
                "avatar_url": self._config.server_notices_mxid_avatar_url,
            }

        requester = create_requester(self.server_notices_mxid,
                                     authenticated_entity=self._server_name)
        info, _ = await self._room_creation_handler.create_room(
            requester,
            config={
                "preset": RoomCreationPreset.PRIVATE_CHAT,
                "name": self._config.server_notices_room_name,
                "power_level_content_override": {
                    "users_default": -10
                },
            },
            ratelimit=False,
            creator_join_profile=join_profile,
        )
        room_id = info["room_id"]

        max_id = await self._account_data_handler.add_tag_to_room(
            user_id, room_id, SERVER_NOTICE_ROOM_TAG, {})
        self._notifier.on_new_event("account_data_key",
                                    max_id,
                                    users=[user_id])

        logger.info("Created server notices room %s for %s", room_id, user_id)
        return room_id
Beispiel #27
0
    def test_typing_timeout(self):
        self.room_members = [U_APPLE, U_BANANA]

        self.assertEquals(self.event_source.get_current_key(), 0)

        self.get_success(
            self.handler.started_typing(
                target_user=U_APPLE,
                requester=create_requester(U_APPLE),
                room_id=ROOM_ID,
                timeout=10000,
            )
        )

        self.on_new_event.assert_has_calls([call("typing_key", 1, rooms=[ROOM_ID])])
        self.on_new_event.reset_mock()

        self.assertEquals(self.event_source.get_current_key(), 1)
        events = self.get_success(
            self.event_source.get_new_events(room_ids=[ROOM_ID], from_key=0)
        )
        self.assertEquals(
            events[0],
            [
                {
                    "type": "m.typing",
                    "room_id": ROOM_ID,
                    "content": {"user_ids": [U_APPLE.to_string()]},
                }
            ],
        )

        self.reactor.pump([16])

        self.on_new_event.assert_has_calls([call("typing_key", 2, rooms=[ROOM_ID])])

        self.assertEquals(self.event_source.get_current_key(), 2)
        events = self.get_success(
            self.event_source.get_new_events(room_ids=[ROOM_ID], from_key=1)
        )
        self.assertEquals(
            events[0],
            [{"type": "m.typing", "room_id": ROOM_ID, "content": {"user_ids": []}}],
        )

        # SYN-230 - see if we can still set after timeout

        self.get_success(
            self.handler.started_typing(
                target_user=U_APPLE,
                requester=create_requester(U_APPLE),
                room_id=ROOM_ID,
                timeout=10000,
            )
        )

        self.on_new_event.assert_has_calls([call("typing_key", 3, rooms=[ROOM_ID])])
        self.on_new_event.reset_mock()

        self.assertEquals(self.event_source.get_current_key(), 3)
        events = self.get_success(
            self.event_source.get_new_events(room_ids=[ROOM_ID], from_key=0)
        )
        self.assertEquals(
            events[0],
            [
                {
                    "type": "m.typing",
                    "room_id": ROOM_ID,
                    "content": {"user_ids": [U_APPLE.to_string()]},
                }
            ],
        )
Beispiel #28
0
    def test_auto_create_auto_join_room_preset_invalid_permissions(self):
        """
        Auto-created rooms that are private require an invite, check that
        registration doesn't completely break if the inviter doesn't have proper
        permissions.
        """
        inviter = "@support:test"

        # Register an initial user to create the room and such (essentially this
        # is a subset of test_auto_create_auto_join_room_preset).
        room_alias_str = "#room:test"
        user_id = self.get_success(
            self.handler.register_user(localpart="jeff"))

        # Ensure the room was created.
        directory_handler = self.hs.get_handlers().directory_handler
        room_alias = RoomAlias.from_string(room_alias_str)
        room_id = self.get_success(
            directory_handler.get_association(room_alias))

        # Ensure the room exists.
        self.get_success(self.store.get_room_with_stats(room_id["room_id"]))

        # Both users should be in the room.
        rooms = self.get_success(self.store.get_rooms_for_user(inviter))
        self.assertIn(room_id["room_id"], rooms)
        rooms = self.get_success(self.store.get_rooms_for_user(user_id))
        self.assertIn(room_id["room_id"], rooms)

        # Lower the permissions of the inviter.
        event_creation_handler = self.hs.get_event_creation_handler()
        requester = create_requester(inviter)
        event, context = self.get_success(
            event_creation_handler.create_event(
                requester,
                {
                    "type": "m.room.power_levels",
                    "state_key": "",
                    "room_id": room_id["room_id"],
                    "content": {
                        "invite": 100,
                        "users": {
                            inviter: 0
                        }
                    },
                    "sender": inviter,
                },
            ))
        self.get_success(
            event_creation_handler.send_nonmember_event(
                requester, event, context))

        # Register a second user, which won't be be in the room (or even have an invite)
        # since the inviter no longer has the proper permissions.
        user_id = self.get_success(self.handler.register_user(localpart="bob"))

        # This user should not be in any rooms.
        rooms = self.get_success(self.store.get_rooms_for_user(user_id))
        invited_rooms = self.get_success(
            self.store.get_invited_rooms_for_local_user(user_id))
        self.assertEqual(rooms, set())
        self.assertEqual(invited_rooms, [])
Beispiel #29
0
    async def set_avatar_url(
        self,
        target_user: UserID,
        requester: Requester,
        new_avatar_url: str,
        by_admin: bool = False,
        deactivation: bool = False,
    ) -> None:
        """Set a new avatar URL for a user.

        Args:
            target_user: the user whose avatar URL is to be changed.
            requester: The user attempting to make this change.
            new_avatar_url: The avatar URL to give this user.
            by_admin: Whether this change was made by an administrator.
            deactivation: Whether this change was made while deactivating the user.
        """
        if not self.hs.is_mine(target_user):
            raise SynapseError(400, "User is not hosted on this homeserver")

        if not by_admin and target_user != requester.user:
            raise AuthError(400, "Cannot set another user's avatar_url")

        if not by_admin and not self.hs.config.registration.enable_set_avatar_url:
            profile = await self.store.get_profileinfo(target_user.localpart)
            if profile.avatar_url:
                raise SynapseError(
                    400, "Changing avatar is disabled on this server",
                    Codes.FORBIDDEN)

        if not isinstance(new_avatar_url, str):
            raise SynapseError(400,
                               "'avatar_url' must be a string",
                               errcode=Codes.INVALID_PARAM)

        if len(new_avatar_url) > MAX_AVATAR_URL_LEN:
            raise SynapseError(
                400,
                "Avatar URL is too long (max %i)" % (MAX_AVATAR_URL_LEN, ))

        if not await self.check_avatar_size_and_mime_type(new_avatar_url):
            raise SynapseError(403, "This avatar is not allowed",
                               Codes.FORBIDDEN)

        avatar_url_to_set: Optional[str] = new_avatar_url
        if new_avatar_url == "":
            avatar_url_to_set = None

        # Same like set_displayname
        if by_admin:
            requester = create_requester(
                target_user,
                authenticated_entity=requester.authenticated_entity)

        await self.store.set_profile_avatar_url(target_user.localpart,
                                                avatar_url_to_set)

        profile = await self.store.get_profileinfo(target_user.localpart)
        await self.user_directory_handler.handle_local_profile_change(
            target_user.to_string(), profile)

        await self._third_party_rules.on_profile_update(
            target_user.to_string(), profile, by_admin, deactivation)

        await self._update_join_states(requester, target_user)
Beispiel #30
0
    async def send_membership_event(
        self,
        requester: Optional[Requester],
        event: EventBase,
        context: EventContext,
        ratelimit: bool = True,
    ):
        """
        Change the membership status of a user in a room.

        Args:
            requester: The local user who requested the membership
                event. If None, certain checks, like whether this homeserver can
                act as the sender, will be skipped.
            event: The membership event.
            context: The context of the event.
            ratelimit: Whether to rate limit this request.
        Raises:
            SynapseError if there was a problem changing the membership.
        """
        target_user = UserID.from_string(event.state_key)
        room_id = event.room_id

        if requester is not None:
            sender = UserID.from_string(event.sender)
            assert (sender == requester.user
                    ), "Sender (%s) must be same as requester (%s)" % (
                        sender, requester.user)
            assert self.hs.is_mine(
                sender), "Sender must be our own: %s" % (sender, )
        else:
            requester = types.create_requester(target_user)

        prev_state_ids = await context.get_prev_state_ids()
        if event.membership == Membership.JOIN:
            if requester.is_guest:
                guest_can_join = await self._can_guest_join(prev_state_ids)
                if not guest_can_join:
                    # This should be an auth check, but guests are a local concept,
                    # so don't really fit into the general auth process.
                    raise AuthError(403, "Guest access not allowed")

        if event.membership not in (Membership.LEAVE, Membership.BAN):
            is_blocked = await self.store.is_room_blocked(room_id)
            if is_blocked:
                raise SynapseError(
                    403, "This room has been blocked on this server")

        event = await self.event_creation_handler.handle_new_client_event(
            requester,
            event,
            context,
            extra_users=[target_user],
            ratelimit=ratelimit)

        prev_member_event_id = prev_state_ids.get(
            (EventTypes.Member, event.state_key), None)

        if event.membership == Membership.LEAVE:
            if prev_member_event_id:
                prev_member_event = await self.store.get_event(
                    prev_member_event_id)
                if prev_member_event.membership == Membership.JOIN:
                    await self._user_left_room(target_user, room_id)
Beispiel #31
0
 def get_user_by_req(request, allow_guest=False, rights="access"):
     return create_requester(
         UserID.from_string(self.helper.auth_user_id), 1, False, None
     )
Beispiel #32
0
    def test_sending_events_into_room(self):
        """Tests that a module can send events into a room"""
        # Mock out create_and_send_nonmember_event to check whether events are being sent
        self.event_creation_handler.create_and_send_nonmember_event = Mock(
            spec=[],
            side_effect=self.event_creation_handler.
            create_and_send_nonmember_event,
        )

        # Create a user and room to play with
        user_id = self.register_user("summer", "monkey")
        tok = self.login("summer", "monkey")
        room_id = self.helper.create_room_as(user_id, tok=tok)

        # Create and send a non-state event
        content = {"body": "I am a puppet", "msgtype": "m.text"}
        event_dict = {
            "room_id": room_id,
            "type": "m.room.message",
            "content": content,
            "sender": user_id,
        }
        event: EventBase = self.get_success(
            self.module_api.create_and_send_event_into_room(event_dict))
        self.assertEqual(event.sender, user_id)
        self.assertEqual(event.type, "m.room.message")
        self.assertEqual(event.room_id, room_id)
        self.assertFalse(hasattr(event, "state_key"))
        self.assertDictEqual(event.content, content)

        expected_requester = create_requester(
            user_id, authenticated_entity=self.hs.hostname)

        # Check that the event was sent
        self.event_creation_handler.create_and_send_nonmember_event.assert_called_with(
            expected_requester,
            event_dict,
            ratelimit=False,
            ignore_shadow_ban=True,
        )

        # Create and send a state event
        content = {
            "events_default": 0,
            "users": {
                user_id: 100
            },
            "state_default": 50,
            "users_default": 0,
            "events": {
                "test.event.type": 25
            },
        }
        event_dict = {
            "room_id": room_id,
            "type": "m.room.power_levels",
            "content": content,
            "sender": user_id,
            "state_key": "",
        }
        event: EventBase = self.get_success(
            self.module_api.create_and_send_event_into_room(event_dict))
        self.assertEqual(event.sender, user_id)
        self.assertEqual(event.type, "m.room.power_levels")
        self.assertEqual(event.room_id, room_id)
        self.assertEqual(event.state_key, "")
        self.assertDictEqual(event.content, content)

        # Check that the event was sent
        self.event_creation_handler.create_and_send_nonmember_event.assert_called_with(
            expected_requester,
            {
                "type": "m.room.power_levels",
                "content": content,
                "room_id": room_id,
                "sender": user_id,
                "state_key": "",
            },
            ratelimit=False,
            ignore_shadow_ban=True,
        )

        # Check that we can't send membership events
        content = {
            "membership": "leave",
        }
        event_dict = {
            "room_id": room_id,
            "type": "m.room.member",
            "content": content,
            "sender": user_id,
            "state_key": user_id,
        }
        self.get_failure(
            self.module_api.create_and_send_event_into_room(event_dict),
            Exception)
Beispiel #33
0
    def on_POST(self, request, room_id):
        requester = yield self.auth.get_user_by_req(request)
        yield assert_user_is_admin(self.auth, requester.user)

        content = parse_json_object_from_request(request)
        assert_params_in_dict(content, ["new_room_user_id"])
        new_room_user_id = content["new_room_user_id"]

        room_creator_requester = create_requester(new_room_user_id)

        message = content.get("message", self.DEFAULT_MESSAGE)
        room_name = content.get("room_name", "Content Violation Notification")

        info = yield self._room_creation_handler.create_room(
            room_creator_requester,
            config={
                "preset": "public_chat",
                "name": room_name,
                "power_level_content_override": {
                    "users_default": -10,
                },
            },
            ratelimit=False,
        )
        new_room_id = info["room_id"]

        requester_user_id = requester.user.to_string()

        logger.info(
            "Shutting down room %r, joining to new room: %r",
            room_id, new_room_id,
        )

        # This will work even if the room is already blocked, but that is
        # desirable in case the first attempt at blocking the room failed below.
        yield self.store.block_room(room_id, requester_user_id)

        users = yield self.state.get_current_users_in_room(room_id)
        kicked_users = []
        failed_to_kick_users = []
        for user_id in users:
            if not self.hs.is_mine_id(user_id):
                continue

            logger.info("Kicking %r from %r...", user_id, room_id)

            try:
                target_requester = create_requester(user_id)
                yield self.room_member_handler.update_membership(
                    requester=target_requester,
                    target=target_requester.user,
                    room_id=room_id,
                    action=Membership.LEAVE,
                    content={},
                    ratelimit=False,
                    require_consent=False,
                )

                yield self.room_member_handler.forget(target_requester.user, room_id)

                yield self.room_member_handler.update_membership(
                    requester=target_requester,
                    target=target_requester.user,
                    room_id=new_room_id,
                    action=Membership.JOIN,
                    content={},
                    ratelimit=False,
                    require_consent=False,
                )

                kicked_users.append(user_id)
            except Exception:
                logger.exception(
                    "Failed to leave old room and join new room for %r", user_id,
                )
                failed_to_kick_users.append(user_id)

        yield self.event_creation_handler.create_and_send_nonmember_event(
            room_creator_requester,
            {
                "type": "m.room.message",
                "content": {"body": message, "msgtype": "m.text"},
                "room_id": new_room_id,
                "sender": new_room_user_id,
            },
            ratelimit=False,
        )

        aliases_for_room = yield self.store.get_aliases_for_room(room_id)

        yield self.store.update_aliases_for_room(
            room_id, new_room_id, requester_user_id
        )

        defer.returnValue((200, {
            "kicked_users": kicked_users,
            "failed_to_kick_users": failed_to_kick_users,
            "local_aliases": aliases_for_room,
            "new_room_id": new_room_id,
        }))
Beispiel #34
0
    def send_membership_event(self, requester, event, context, ratelimit=True):
        """
        Change the membership status of a user in a room.

        Args:
            requester (Requester): The local user who requested the membership
                event. If None, certain checks, like whether this homeserver can
                act as the sender, will be skipped.
            event (SynapseEvent): The membership event.
            context: The context of the event.
            ratelimit (bool): Whether to rate limit this request.
        Raises:
            SynapseError if there was a problem changing the membership.
        """
        target_user = UserID.from_string(event.state_key)
        room_id = event.room_id

        if requester is not None:
            sender = UserID.from_string(event.sender)
            assert (sender == requester.user
                    ), "Sender (%s) must be same as requester (%s)" % (
                        sender, requester.user)
            assert self.hs.is_mine(
                sender), "Sender must be our own: %s" % (sender, )
        else:
            requester = types.create_requester(target_user)

        prev_event = yield self.event_creation_handler.deduplicate_state_event(
            event, context)
        if prev_event is not None:
            return

        prev_state_ids = yield context.get_prev_state_ids(self.store)
        if event.membership == Membership.JOIN:
            if requester.is_guest:
                guest_can_join = yield self._can_guest_join(prev_state_ids)
                if not guest_can_join:
                    # This should be an auth check, but guests are a local concept,
                    # so don't really fit into the general auth process.
                    raise AuthError(403, "Guest access not allowed")

        if event.membership not in (Membership.LEAVE, Membership.BAN):
            is_blocked = yield self.store.is_room_blocked(room_id)
            if is_blocked:
                raise SynapseError(
                    403, "This room has been blocked on this server")

        yield self.event_creation_handler.handle_new_client_event(
            requester,
            event,
            context,
            extra_users=[target_user],
            ratelimit=ratelimit)

        prev_member_event_id = prev_state_ids.get(
            (EventTypes.Member, event.state_key), None)

        if event.membership == Membership.JOIN:
            # Only fire user_joined_room if the user has actually joined the
            # room. Don't bother if the user is just changing their profile
            # info.
            newly_joined = True
            if prev_member_event_id:
                prev_member_event = yield self.store.get_event(
                    prev_member_event_id)
                newly_joined = prev_member_event.membership != Membership.JOIN
            if newly_joined:
                yield self._user_joined_room(target_user, room_id)
        elif event.membership == Membership.LEAVE:
            if prev_member_event_id:
                prev_member_event = yield self.store.get_event(
                    prev_member_event_id)
                if prev_member_event.membership == Membership.JOIN:
                    yield self._user_left_room(target_user, room_id)
Beispiel #35
0
    async def on_POST(self, request, room_identifier):
        requester = await self.auth.get_user_by_req(request)
        await assert_user_is_admin(self.auth, requester.user)

        content = parse_json_object_from_request(request)

        assert_params_in_dict(content, ["user_id"])
        target_user = UserID.from_string(content["user_id"])

        if not self.hs.is_mine(target_user):
            raise SynapseError(
                400, "This endpoint can only be used with local users")

        if not await self.admin_handler.get_user(target_user):
            raise NotFoundError("User not found")

        if RoomID.is_valid(room_identifier):
            room_id = room_identifier
            try:
                remote_room_hosts = [
                    x.decode("ascii") for x in request.args[b"server_name"]
                ]  # type: Optional[List[str]]
            except Exception:
                remote_room_hosts = None
        elif RoomAlias.is_valid(room_identifier):
            handler = self.room_member_handler
            room_alias = RoomAlias.from_string(room_identifier)
            room_id, remote_room_hosts = await handler.lookup_room_alias(
                room_alias)
            room_id = room_id.to_string()
        else:
            raise SynapseError(
                400,
                "%s was not legal room ID or room alias" % (room_identifier, ))

        fake_requester = create_requester(target_user)

        # send invite if room has "JoinRules.INVITE"
        room_state = await self.state_handler.get_current_state(room_id)
        join_rules_event = room_state.get((EventTypes.JoinRules, ""))
        if join_rules_event:
            if not (join_rules_event.content.get("join_rule")
                    == JoinRules.PUBLIC):
                await self.room_member_handler.update_membership(
                    requester=requester,
                    target=fake_requester.user,
                    room_id=room_id,
                    action="invite",
                    remote_room_hosts=remote_room_hosts,
                    ratelimit=False,
                )

        await self.room_member_handler.update_membership(
            requester=fake_requester,
            target=fake_requester.user,
            room_id=room_id,
            action="join",
            remote_room_hosts=remote_room_hosts,
            ratelimit=False,
        )

        return 200, {"room_id": room_id}
Beispiel #36
0
    async def complete_sso_login_request(
        self,
        auth_provider_id: str,
        remote_user_id: str,
        request: SynapseRequest,
        client_redirect_url: str,
        sso_to_matrix_id_mapper: Callable[[int], Awaitable[UserAttributes]],
        grandfather_existing_users: Callable[[], Awaitable[Optional[str]]],
        extra_login_attributes: Optional[JsonDict] = None,
        auth_provider_session_id: Optional[str] = None,
    ) -> None:
        """
        Given an SSO ID, retrieve the user ID for it and possibly register the user.

        This first checks if the SSO ID has previously been linked to a matrix ID,
        if it has that matrix ID is returned regardless of the current mapping
        logic.

        If a callable is provided for grandfathering users, it is called and can
        potentially return a matrix ID to use. If it does, the SSO ID is linked to
        this matrix ID for subsequent calls.

        The mapping function is called (potentially multiple times) to generate
        a localpart for the user.

        If an unused localpart is generated, the user is registered from the
        given user-agent and IP address and the SSO ID is linked to this matrix
        ID for subsequent calls.

        Finally, we generate a redirect to the supplied redirect uri, with a login token

        Args:
            auth_provider_id: A unique identifier for this SSO provider, e.g.
                "oidc" or "saml".

            remote_user_id: The unique identifier from the SSO provider.

            request: The request to respond to

            client_redirect_url: The redirect URL passed in by the client.

            sso_to_matrix_id_mapper: A callable to generate the user attributes.
                The only parameter is an integer which represents the amount of
                times the returned mxid localpart mapping has failed.

                It is expected that the mapper can raise two exceptions, which
                will get passed through to the caller:

                    MappingException if there was a problem mapping the response
                        to the user.
                    RedirectException to redirect to an additional page (e.g.
                        to prompt the user for more information).

            grandfather_existing_users: A callable which can return an previously
                existing matrix ID. The SSO ID is then linked to the returned
                matrix ID.

            extra_login_attributes: An optional dictionary of extra
                attributes to be provided to the client in the login response.

            auth_provider_session_id: An optional session ID from the IdP.

        Raises:
            MappingException if there was a problem mapping the response to a user.
            RedirectException: if the mapping provider needs to redirect the user
                to an additional page. (e.g. to prompt for more information)

        """
        new_user = False

        # grab a lock while we try to find a mapping for this user. This seems...
        # optimistic, especially for implementations that end up redirecting to
        # interstitial pages.
        async with self._mapping_lock.queue(auth_provider_id):
            # first of all, check if we already have a mapping for this user
            user_id = await self.get_sso_user_by_remote_user_id(
                auth_provider_id,
                remote_user_id,
            )

            # Check for grandfathering of users.
            if not user_id:
                user_id = await grandfather_existing_users()
                if user_id:
                    # Future logins should also match this user ID.
                    await self._store.record_user_external_id(
                        auth_provider_id, remote_user_id, user_id)

            # Otherwise, generate a new user.
            if not user_id:
                attributes = await self._call_attribute_mapper(
                    sso_to_matrix_id_mapper)

                next_step_url = self._get_url_for_next_new_user_step(
                    attributes=attributes)
                if next_step_url:
                    await self._redirect_to_next_new_user_step(
                        auth_provider_id,
                        remote_user_id,
                        attributes,
                        client_redirect_url,
                        next_step_url,
                        extra_login_attributes,
                    )

                user_id = await self._register_mapped_user(
                    attributes,
                    auth_provider_id,
                    remote_user_id,
                    get_request_user_agent(request),
                    request.getClientAddress().host,
                )
                new_user = True
            elif self._sso_update_profile_information:
                attributes = await self._call_attribute_mapper(
                    sso_to_matrix_id_mapper)
                if attributes.display_name:
                    user_id_obj = UserID.from_string(user_id)
                    profile_display_name = await self._profile_handler.get_displayname(
                        user_id_obj)
                    if profile_display_name != attributes.display_name:
                        requester = create_requester(
                            user_id,
                            authenticated_entity=user_id,
                        )
                        await self._profile_handler.set_displayname(
                            user_id_obj, requester, attributes.display_name,
                            True)

        await self._auth_handler.complete_sso_login(
            user_id,
            auth_provider_id,
            request,
            client_redirect_url,
            extra_login_attributes,
            new_user=new_user,
            auth_provider_session_id=auth_provider_session_id,
        )
    def get_notice_room_for_user(self, user_id):
        """Get the room for notices for a given user

        If we have not yet created a notice room for this user, create it

        Args:
            user_id (str): complete user id for the user we want a room for

        Returns:
            str: room id of notice room.
        """
        if not self.is_enabled():
            raise Exception("Server notices not enabled")

        assert self._is_mine_id(user_id), \
            "Cannot send server notices to remote users"

        rooms = yield self._store.get_rooms_for_user_where_membership_is(
            user_id, [Membership.INVITE, Membership.JOIN],
        )
        system_mxid = self._config.server_notices_mxid
        for room in rooms:
            # it's worth noting that there is an asymmetry here in that we
            # expect the user to be invited or joined, but the system user must
            # be joined. This is kinda deliberate, in that if somebody somehow
            # manages to invite the system user to a room, that doesn't make it
            # the server notices room.
            user_ids = yield self._store.get_users_in_room(room.room_id)
            if system_mxid in user_ids:
                # we found a room which our user shares with the system notice
                # user
                logger.info("Using room %s", room.room_id)
                defer.returnValue(room.room_id)

        # apparently no existing notice room: create a new one
        logger.info("Creating server notices room for %s", user_id)

        # see if we want to override the profile info for the server user.
        # note that if we want to override either the display name or the
        # avatar, we have to use both.
        join_profile = None
        if (
            self._config.server_notices_mxid_display_name is not None or
            self._config.server_notices_mxid_avatar_url is not None
        ):
            join_profile = {
                "displayname": self._config.server_notices_mxid_display_name,
                "avatar_url": self._config.server_notices_mxid_avatar_url,
            }

        requester = create_requester(system_mxid)
        info = yield self._room_creation_handler.create_room(
            requester,
            config={
                "preset": RoomCreationPreset.PRIVATE_CHAT,
                "name": self._config.server_notices_room_name,
                "power_level_content_override": {
                    "users_default": -10,
                },
                "invite": (user_id,)
            },
            ratelimit=False,
            creator_join_profile=join_profile,
        )
        room_id = info['room_id']

        max_id = yield self._store.add_tag_to_room(
            user_id, room_id, SERVER_NOTICE_ROOM_TAG, {},
        )
        self._notifier.on_new_event(
            "account_data_key", max_id, users=[user_id]
        )

        logger.info("Created server notices room %s for %s", room_id, user_id)
        defer.returnValue(room_id)
Beispiel #38
0
    async def on_POST(self, request, room_identifier):
        requester = await self.auth.get_user_by_req(request)
        await assert_user_is_admin(self.auth, requester.user)
        content = parse_json_object_from_request(request,
                                                 allow_empty_body=True)

        # Resolve to a room ID, if necessary.
        if RoomID.is_valid(room_identifier):
            room_id = room_identifier
        elif RoomAlias.is_valid(room_identifier):
            room_alias = RoomAlias.from_string(room_identifier)
            room_id, _ = await self.room_member_handler.lookup_room_alias(
                room_alias)
            room_id = room_id.to_string()
        else:
            raise SynapseError(
                400,
                "%s was not legal room ID or room alias" % (room_identifier, ))

        # Which user to grant room admin rights to.
        user_to_add = content.get("user_id", requester.user.to_string())

        # Figure out which local users currently have power in the room, if any.
        room_state = await self.state_handler.get_current_state(room_id)
        if not room_state:
            raise SynapseError(400, "Server not in room")

        create_event = room_state[(EventTypes.Create, "")]
        power_levels = room_state.get((EventTypes.PowerLevels, ""))

        if power_levels is not None:
            # We pick the local user with the highest power.
            user_power = power_levels.content.get("users", {})
            admin_users = [
                user_id for user_id in user_power if self.is_mine_id(user_id)
            ]
            admin_users.sort(key=lambda user: user_power[user])

            if not admin_users:
                raise SynapseError(400, "No local admin user in room")

            admin_user_id = None

            for admin_user in reversed(admin_users):
                if room_state.get((EventTypes.Member, admin_user)):
                    admin_user_id = admin_user
                    break

            if not admin_user_id:
                raise SynapseError(
                    400,
                    "No local admin user in room",
                )

            pl_content = power_levels.content
        else:
            # If there is no power level events then the creator has rights.
            pl_content = {}
            admin_user_id = create_event.sender
            if not self.is_mine_id(admin_user_id):
                raise SynapseError(
                    400,
                    "No local admin user in room",
                )

        # Grant the user power equal to the room admin by attempting to send an
        # updated power level event.
        new_pl_content = dict(pl_content)
        new_pl_content["users"] = dict(pl_content.get("users", {}))
        new_pl_content["users"][user_to_add] = new_pl_content["users"][
            admin_user_id]

        fake_requester = create_requester(
            admin_user_id,
            authenticated_entity=requester.authenticated_entity,
        )

        try:
            await self.event_creation_handler.create_and_send_nonmember_event(
                fake_requester,
                event_dict={
                    "content": new_pl_content,
                    "sender": admin_user_id,
                    "type": EventTypes.PowerLevels,
                    "state_key": "",
                    "room_id": room_id,
                },
            )
        except AuthError:
            # The admin user we found turned out not to have enough power.
            raise SynapseError(
                400,
                "No local admin user in room with power to update power levels."
            )

        # Now we check if the user we're granting admin rights to is already in
        # the room. If not and it's not a public room we invite them.
        member_event = room_state.get((EventTypes.Member, user_to_add))
        is_joined = False
        if member_event:
            is_joined = member_event.content["membership"] in (
                Membership.JOIN,
                Membership.INVITE,
            )

        if is_joined:
            return 200, {}

        join_rules = room_state.get((EventTypes.JoinRules, ""))
        is_public = False
        if join_rules:
            is_public = join_rules.content.get("join_rule") == JoinRules.PUBLIC

        if is_public:
            return 200, {}

        await self.room_member_handler.update_membership(
            fake_requester,
            target=UserID.from_string(user_to_add),
            room_id=room_id,
            action=Membership.INVITE,
        )

        return 200, {}