Ejemplo n.º 1
0
    async def on_POST(self, request, room_id):
        requester = await self.auth.get_user_by_req(request, allow_guest=False)

        if not requester.app_service:
            raise AuthError(
                403,
                "Only application services can use the /batchsend endpoint",
            )

        body = parse_json_object_from_request(request)
        assert_params_in_dict(body, ["state_events_at_start", "events"])

        prev_events_from_query = parse_strings_from_args(
            request.args, "prev_event")
        chunk_id_from_query = parse_string(request, "chunk_id")

        if prev_events_from_query is None:
            raise SynapseError(
                400,
                "prev_event query parameter is required when inserting historical messages back in time",
                errcode=Codes.MISSING_PARAM,
            )

        # For the event we are inserting next to (`prev_events_from_query`),
        # find the most recent auth events (derived from state events) that
        # allowed that message to be sent. We will use that as a base
        # to auth our historical messages against.
        (
            most_recent_prev_event_id,
            _,
        ) = await self.store.get_max_depth_of(prev_events_from_query)
        # mapping from (type, state_key) -> state_event_id
        prev_state_map = await self.state_store.get_state_ids_for_event(
            most_recent_prev_event_id)
        # List of state event ID's
        prev_state_ids = list(prev_state_map.values())
        auth_event_ids = prev_state_ids

        state_events_at_start = []
        for state_event in body["state_events_at_start"]:
            assert_params_in_dict(
                state_event, ["type", "origin_server_ts", "content", "sender"])

            logger.debug(
                "RoomBatchSendEventRestServlet inserting state_event=%s, auth_event_ids=%s",
                state_event,
                auth_event_ids,
            )

            event_dict = {
                "type": state_event["type"],
                "origin_server_ts": state_event["origin_server_ts"],
                "content": state_event["content"],
                "room_id": room_id,
                "sender": state_event["sender"],
                "state_key": state_event["state_key"],
            }

            # Mark all events as historical
            event_dict["content"][EventContentFields.MSC2716_HISTORICAL] = True

            # Make the state events float off on their own
            fake_prev_event_id = "$" + random_string(43)

            # TODO: This is pretty much the same as some other code to handle inserting state in this file
            if event_dict["type"] == EventTypes.Member:
                membership = event_dict["content"].get("membership", None)
                event_id, _ = await self.room_member_handler.update_membership(
                    await self._create_requester_for_user_id_from_app_service(
                        state_event["sender"], requester.app_service),
                    target=UserID.from_string(event_dict["state_key"]),
                    room_id=room_id,
                    action=membership,
                    content=event_dict["content"],
                    outlier=True,
                    prev_event_ids=[fake_prev_event_id],
                    # Make sure to use a copy of this list because we modify it
                    # later in the loop here. Otherwise it will be the same
                    # reference and also update in the event when we append later.
                    auth_event_ids=auth_event_ids.copy(),
                )
            else:
                # TODO: Add some complement tests that adds state that is not member joins
                # and will use this code path. Maybe we only want to support join state events
                # and can get rid of this `else`?
                (
                    event,
                    _,
                ) = await self.event_creation_handler.create_and_send_nonmember_event(
                    await self._create_requester_for_user_id_from_app_service(
                        state_event["sender"], requester.app_service),
                    event_dict,
                    outlier=True,
                    prev_event_ids=[fake_prev_event_id],
                    # Make sure to use a copy of this list because we modify it
                    # later in the loop here. Otherwise it will be the same
                    # reference and also update in the event when we append later.
                    auth_event_ids=auth_event_ids.copy(),
                )
                event_id = event.event_id

            state_events_at_start.append(event_id)
            auth_event_ids.append(event_id)

        events_to_create = body["events"]

        inherited_depth = await self._inherit_depth_from_prev_ids(
            prev_events_from_query)

        # Figure out which chunk to connect to. If they passed in
        # chunk_id_from_query let's use it. The chunk ID passed in comes
        # from the chunk_id in the "insertion" event from the previous chunk.
        last_event_in_chunk = events_to_create[-1]
        chunk_id_to_connect_to = chunk_id_from_query
        base_insertion_event = None
        if chunk_id_from_query:
            #  All but the first base insertion event should point at a fake
            #  event, which causes the HS to ask for the state at the start of
            #  the chunk later.
            prev_event_ids = [fake_prev_event_id]
            # TODO: Verify the chunk_id_from_query corresponds to an insertion event
            pass
        # Otherwise, create an insertion event to act as a starting point.
        #
        # We don't always have an insertion event to start hanging more history
        # off of (ideally there would be one in the main DAG, but that's not the
        # case if we're wanting to add history to e.g. existing rooms without
        # an insertion event), in which case we just create a new insertion event
        # that can then get pointed to by a "marker" event later.
        else:
            prev_event_ids = prev_events_from_query

            base_insertion_event_dict = self._create_insertion_event_dict(
                sender=requester.user.to_string(),
                room_id=room_id,
                origin_server_ts=last_event_in_chunk["origin_server_ts"],
            )
            base_insertion_event_dict["prev_events"] = prev_event_ids.copy()

            (
                base_insertion_event,
                _,
            ) = await self.event_creation_handler.create_and_send_nonmember_event(
                await self._create_requester_for_user_id_from_app_service(
                    base_insertion_event_dict["sender"],
                    requester.app_service,
                ),
                base_insertion_event_dict,
                prev_event_ids=base_insertion_event_dict.get("prev_events"),
                auth_event_ids=auth_event_ids,
                historical=True,
                depth=inherited_depth,
            )

            chunk_id_to_connect_to = base_insertion_event["content"][
                EventContentFields.MSC2716_NEXT_CHUNK_ID]

        # Connect this current chunk to the insertion event from the previous chunk
        chunk_event = {
            "type": EventTypes.MSC2716_CHUNK,
            "sender": requester.user.to_string(),
            "room_id": room_id,
            "content": {
                EventContentFields.MSC2716_CHUNK_ID: chunk_id_to_connect_to,
                EventContentFields.MSC2716_HISTORICAL: True,
            },
            # Since the chunk event is put at the end of the chunk,
            # where the newest-in-time event is, copy the origin_server_ts from
            # the last event we're inserting
            "origin_server_ts": last_event_in_chunk["origin_server_ts"],
        }
        # Add the chunk event to the end of the chunk (newest-in-time)
        events_to_create.append(chunk_event)

        # Add an "insertion" event to the start of each chunk (next to the oldest-in-time
        # event in the chunk) so the next chunk can be connected to this one.
        insertion_event = self._create_insertion_event_dict(
            sender=requester.user.to_string(),
            room_id=room_id,
            # Since the insertion event is put at the start of the chunk,
            # where the oldest-in-time event is, copy the origin_server_ts from
            # the first event we're inserting
            origin_server_ts=events_to_create[0]["origin_server_ts"],
        )
        # Prepend the insertion event to the start of the chunk (oldest-in-time)
        events_to_create = [insertion_event] + events_to_create

        event_ids = []
        events_to_persist = []
        for ev in events_to_create:
            assert_params_in_dict(
                ev, ["type", "origin_server_ts", "content", "sender"])

            event_dict = {
                "type": ev["type"],
                "origin_server_ts": ev["origin_server_ts"],
                "content": ev["content"],
                "room_id": room_id,
                "sender": ev["sender"],  # requester.user.to_string(),
                "prev_events": prev_event_ids.copy(),
            }

            # Mark all events as historical
            event_dict["content"][EventContentFields.MSC2716_HISTORICAL] = True

            event, context = await self.event_creation_handler.create_event(
                await self._create_requester_for_user_id_from_app_service(
                    ev["sender"], requester.app_service),
                event_dict,
                prev_event_ids=event_dict.get("prev_events"),
                auth_event_ids=auth_event_ids,
                historical=True,
                depth=inherited_depth,
            )
            logger.debug(
                "RoomBatchSendEventRestServlet inserting event=%s, prev_event_ids=%s, auth_event_ids=%s",
                event,
                prev_event_ids,
                auth_event_ids,
            )

            assert self.hs.is_mine_id(
                event.sender), "User must be our own: %s" % (event.sender, )

            events_to_persist.append((event, context))
            event_id = event.event_id

            event_ids.append(event_id)
            prev_event_ids = [event_id]

        # Persist events in reverse-chronological order so they have the
        # correct stream_ordering as they are backfilled (which decrements).
        # Events are sorted by (topological_ordering, stream_ordering)
        # where topological_ordering is just depth.
        for (event, context) in reversed(events_to_persist):
            ev = await self.event_creation_handler.handle_new_client_event(
                await self._create_requester_for_user_id_from_app_service(
                    event["sender"], requester.app_service),
                event=event,
                context=context,
            )

        # Add the base_insertion_event to the bottom of the list we return
        if base_insertion_event is not None:
            event_ids.append(base_insertion_event.event_id)

        return 200, {
            "state_events":
            state_events_at_start,
            "events":
            event_ids,
            "next_chunk_id":
            insertion_event["content"][
                EventContentFields.MSC2716_NEXT_CHUNK_ID],
        }
Ejemplo n.º 2
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)

        new_room_user_id = content.get("new_room_user_id")
        if not new_room_user_id:
            raise SynapseError(400, "Please provide field `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.handlers.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"]

        msg_handler = self.handlers.message_handler
        yield msg_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.handlers.room_member_handler.update_membership(
                requester=target_requester,
                target=target_requester.user,
                room_id=room_id,
                action=Membership.LEAVE,
                content={},
                ratelimit=False
            )

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

            yield self.handlers.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,
        }))
Ejemplo n.º 3
0
    async def on_POST(
        self,
        request: SynapseRequest,
        room_id: str,
        membership_action: str,
        txn_id: Optional[str] = None,
    ) -> Tuple[int, JsonDict]:
        requester = await self.auth.get_user_by_req(request, allow_guest=True)

        if requester.is_guest and membership_action not in {
                Membership.JOIN,
                Membership.LEAVE,
        }:
            raise AuthError(403, "Guest access not allowed")

        try:
            content = parse_json_object_from_request(request)
        except Exception:
            # Turns out we used to ignore the body entirely, and some clients
            # cheekily send invalid bodies.
            content = {}

        if membership_action == "invite" and self._has_3pid_invite_keys(
                content):
            try:
                await self.room_member_handler.do_3pid_invite(
                    room_id,
                    requester.user,
                    content["medium"],
                    content["address"],
                    content["id_server"],
                    requester,
                    txn_id,
                    content.get("id_access_token"),
                )
            except ShadowBanError:
                # Pretend the request succeeded.
                pass
            return 200, {}

        target = requester.user
        if membership_action in ["invite", "ban", "unban", "kick"]:
            assert_params_in_dict(content, ["user_id"])
            target = UserID.from_string(content["user_id"])

        event_content = None
        if "reason" in content:
            event_content = {"reason": content["reason"]}

        try:
            await self.room_member_handler.update_membership(
                requester=requester,
                target=target,
                room_id=room_id,
                action=membership_action,
                txn_id=txn_id,
                third_party_signed=content.get("third_party_signed", None),
                content=event_content,
            )
        except ShadowBanError:
            # Pretend the request succeeded.
            pass

        return_value = {}

        if membership_action == "join":
            return_value["room_id"] = room_id

        return 200, return_value
Ejemplo n.º 4
0
    async def _get_room_hierarchy(
        self,
        requester: str,
        requested_room_id: str,
        suggested_only: bool = False,
        max_depth: Optional[int] = None,
        limit: Optional[int] = None,
        from_token: Optional[str] = None,
    ) -> JsonDict:
        """See docstring for SpaceSummaryHandler.get_room_hierarchy."""

        # First of all, check that the room is accessible.
        if not await self._is_local_room_accessible(requested_room_id,
                                                    requester):
            raise AuthError(
                403,
                "User %s not in room %s, and room previews are disabled" %
                (requester, requested_room_id),
            )

        # If this is continuing a previous session, pull the persisted data.
        if from_token:
            try:
                pagination_session = await self._store.get_session(
                    session_type=self._PAGINATION_SESSION_TYPE,
                    session_id=from_token,
                )
            except StoreError:
                raise SynapseError(400, "Unknown pagination token",
                                   Codes.INVALID_PARAM)

            # If the requester, room ID, suggested-only, or max depth were modified
            # the session is invalid.
            if (requester != pagination_session["requester"]
                    or requested_room_id != pagination_session["room_id"]
                    or suggested_only != pagination_session["suggested_only"]
                    or max_depth != pagination_session["max_depth"]):
                raise SynapseError(400, "Unknown pagination token",
                                   Codes.INVALID_PARAM)

            # Load the previous state.
            room_queue = [
                _RoomQueueEntry(*fields)
                for fields in pagination_session["room_queue"]
            ]
            processed_rooms = set(pagination_session["processed_rooms"])
        else:
            # The queue of rooms to process, the next room is last on the stack.
            room_queue = [_RoomQueueEntry(requested_room_id, ())]

            # Rooms we have already processed.
            processed_rooms = set()

        rooms_result: List[JsonDict] = []

        # Cap the limit to a server-side maximum.
        if limit is None:
            limit = MAX_ROOMS
        else:
            limit = min(limit, MAX_ROOMS)

        # Iterate through the queue until we reach the limit or run out of
        # rooms to include.
        while room_queue and len(rooms_result) < limit:
            queue_entry = room_queue.pop()
            room_id = queue_entry.room_id
            current_depth = queue_entry.depth
            if room_id in processed_rooms:
                # already done this room
                continue

            logger.debug("Processing room %s", room_id)

            # A map of summaries for children rooms that might be returned over
            # federation. The rationale for caching these and *maybe* using them
            # is to prefer any information local to the homeserver before trusting
            # data received over federation.
            children_room_entries: Dict[str, JsonDict] = {}
            # A set of room IDs which are children that did not have information
            # returned over federation and are known to be inaccessible to the
            # current server. We should not reach out over federation to try to
            # summarise these rooms.
            inaccessible_children: Set[str] = set()

            # If the room is known locally, summarise it!
            is_in_room = await self._store.is_host_joined(
                room_id, self._server_name)
            if is_in_room:
                room_entry = await self._summarize_local_room(
                    requester,
                    None,
                    room_id,
                    suggested_only,
                    # TODO Handle max children.
                    max_children=None,
                )

            # Otherwise, attempt to use information for federation.
            else:
                # A previous call might have included information for this room.
                # It can be used if either:
                #
                # 1. The room is not a space.
                # 2. The maximum depth has been achieved (since no children
                #    information is needed).
                if queue_entry.remote_room and (
                        queue_entry.remote_room.get("room_type") !=
                        RoomTypes.SPACE or
                    (max_depth is not None and current_depth >= max_depth)):
                    room_entry = _RoomEntry(queue_entry.room_id,
                                            queue_entry.remote_room)

                # If the above isn't true, attempt to fetch the room
                # information over federation.
                else:
                    (
                        room_entry,
                        children_room_entries,
                        inaccessible_children,
                    ) = await self._summarize_remote_room_hierarchy(
                        queue_entry,
                        suggested_only,
                    )

                # Ensure this room is accessible to the requester (and not just
                # the homeserver).
                if room_entry and not await self._is_remote_room_accessible(
                        requester, queue_entry.room_id, room_entry.room):
                    room_entry = None

            # This room has been processed and should be ignored if it appears
            # elsewhere in the hierarchy.
            processed_rooms.add(room_id)

            # There may or may not be a room entry based on whether it is
            # inaccessible to the requesting user.
            if room_entry:
                # Add the room (including the stripped m.space.child events).
                rooms_result.append(room_entry.as_json())

                # If this room is not at the max-depth, check if there are any
                # children to process.
                if max_depth is None or current_depth < max_depth:
                    # The children get added in reverse order so that the next
                    # room to process, according to the ordering, is the last
                    # item in the list.
                    room_queue.extend(
                        _RoomQueueEntry(
                            ev["state_key"],
                            ev["content"]["via"],
                            current_depth + 1,
                            children_room_entries.get(ev["state_key"]),
                        ) for ev in reversed(room_entry.children_state_events)
                        if ev["type"] == EventTypes.SpaceChild
                        and ev["state_key"] not in inaccessible_children)

        result: JsonDict = {"rooms": rooms_result}

        # If there's additional data, generate a pagination token (and persist state).
        if room_queue:
            result["next_batch"] = await self._store.create_session(
                session_type=self._PAGINATION_SESSION_TYPE,
                value={
                    # Information which must be identical across pagination.
                    "requester":
                    requester,
                    "room_id":
                    requested_room_id,
                    "suggested_only":
                    suggested_only,
                    "max_depth":
                    max_depth,
                    # The stored state.
                    "room_queue":
                    [attr.astuple(room_entry) for room_entry in room_queue],
                    "processed_rooms":
                    list(processed_rooms),
                },
                expiry_ms=self._PAGINATION_SESSION_VALIDITY_PERIOD_MS,
            )

        return result
Ejemplo n.º 5
0
def _check_joined_room(member, user_id, room_id):
    if not member or member.membership != Membership.JOIN:
        raise AuthError(
            403, "User %s not in room %s (%s)" % (user_id, room_id, repr(member))
        )
Ejemplo n.º 6
0
    async def _wrapped_get_user_by_req(
        self,
        request: SynapseRequest,
        allow_guest: bool,
        rights: str,
        allow_expired: bool,
    ) -> Requester:
        """Helper for get_user_by_req

        Once get_user_by_req has set up the opentracing span, this does the actual work.
        """
        try:
            ip_addr = request.getClientIP()
            user_agent = get_request_user_agent(request)

            access_token = self.get_access_token_from_request(request)

            (
                user_id,
                device_id,
                app_service,
            ) = await self._get_appservice_user_id_and_device_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"
                        if device_id is None else device_id,  # stubbed
                    )

                requester = create_requester(user_id,
                                             app_service=app_service,
                                             device_id=device_id)

                request.requester = user_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,
                )
                # Track also the puppeted user client IP if enabled and the user is puppeting
                if (user_info.user_id != user_info.token_owner
                        and self._track_puppeted_user_ips):
                    await self.store.insert_client_ip(
                        user_id=user_info.user_id,
                        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
            return requester
        except KeyError:
            raise MissingClientTokenError()
Ejemplo n.º 7
0
 def check_joined_room(room_id, user_id):
     if user_id not in [u.to_string() for u in self.room_members]:
         raise AuthError(401, "User is not in the room")
Ejemplo n.º 8
0
    def get_state_events(
        self,
        user_id,
        room_id,
        state_filter=StateFilter.all(),
        at_token=None,
        is_guest=False,
    ):
        """Retrieve all state events for a given room. If the user is
        joined to the room then return the current state. If the user has
        left the room return the state events from when they left. If an explicit
        'at' parameter is passed, return the state events as of that event, if
        visible.

        Args:
            user_id(str): The user requesting state events.
            room_id(str): The room ID to get all state events from.
            state_filter (StateFilter): The state filter used to fetch state
                from the database.
            at_token(StreamToken|None): the stream token of the at which we are requesting
                the stats. If the user is not allowed to view the state as of that
                stream token, we raise a 403 SynapseError. If None, returns the current
                state based on the current_state_events table.
            is_guest(bool): whether this user is a guest
        Returns:
            A list of dicts representing state events. [{}, {}, {}]
        Raises:
            NotFoundError (404) if the at token does not yield an event

            AuthError (403) if the user doesn't have permission to view
            members of this room.
        """
        if at_token:
            # FIXME this claims to get the state at a stream position, but
            # get_recent_events_for_room operates by topo ordering. This therefore
            # does not reliably give you the state at the given stream position.
            # (https://github.com/matrix-org/synapse/issues/3305)
            last_events, _ = yield self.store.get_recent_events_for_room(
                room_id, end_token=at_token.room_key, limit=1
            )

            if not last_events:
                raise NotFoundError("Can't find event for token %s" % (at_token,))

            visible_events = yield filter_events_for_client(
                self.storage, user_id, last_events, apply_retention_policies=False
            )

            event = last_events[0]
            if visible_events:
                room_state = yield self.state_store.get_state_for_events(
                    [event.event_id], state_filter=state_filter
                )
                room_state = room_state[event.event_id]
            else:
                raise AuthError(
                    403,
                    "User %s not allowed to view events in room %s at token %s"
                    % (user_id, room_id, at_token),
                )
        else:
            (
                membership,
                membership_event_id,
            ) = yield self.auth.check_in_room_or_world_readable(room_id, user_id)

            if membership == Membership.JOIN:
                state_ids = yield self.store.get_filtered_current_state_ids(
                    room_id, state_filter=state_filter
                )
                room_state = yield self.store.get_events(state_ids.values())
            elif membership == Membership.LEAVE:
                room_state = yield self.state_store.get_state_for_events(
                    [membership_event_id], state_filter=state_filter
                )
                room_state = room_state[membership_event_id]

        now = self.clock.time_msec()
        events = yield self._event_serializer.serialize_events(
            room_state.values(),
            now,
            # We don't bother bundling aggregations in when asked for state
            # events, as clients won't use them.
            bundle_aggregations=False,
        )
        return events
Ejemplo n.º 9
0
    def create_event(
        self,
        requester,
        event_dict,
        token_id=None,
        txn_id=None,
        prev_event_ids: Optional[Collection[str]] = None,
        require_consent=True,
    ):
        """
        Given a dict from a client, create a new event.

        Creates an FrozenEvent object, filling out auth_events, prev_events,
        etc.

        Adds display names to Join membership events.

        Args:
            requester
            event_dict (dict): An entire event
            token_id (str)
            txn_id (str)

            prev_event_ids:
                the forward extremities to use as the prev_events for the
                new event.

                If None, they will be requested from the database.

            require_consent (bool): Whether to check if the requester has
                consented to privacy policy.
        Raises:
            ResourceLimitError if server is blocked to some resource being
            exceeded
        Returns:
            Tuple of created event (FrozenEvent), Context
        """
        yield self.auth.check_auth_blocking(requester.user.to_string())

        if event_dict["type"] == EventTypes.Create and event_dict["state_key"] == "":
            room_version = event_dict["content"]["room_version"]
        else:
            try:
                room_version = yield self.store.get_room_version(event_dict["room_id"])
            except NotFoundError:
                raise AuthError(403, "Unknown room")

        builder = self.event_builder_factory.new(room_version, event_dict)

        self.validator.validate_builder(builder)

        if builder.type == EventTypes.Member:
            membership = builder.content.get("membership", None)
            target = UserID.from_string(builder.state_key)

            if membership in {Membership.JOIN, Membership.INVITE}:
                # If event doesn't include a display name, add one.
                profile = self.profile_handler
                content = builder.content

                try:
                    if "displayname" not in content:
                        content["displayname"] = yield profile.get_displayname(target)
                    if "avatar_url" not in content:
                        content["avatar_url"] = yield profile.get_avatar_url(target)
                except Exception as e:
                    logger.info(
                        "Failed to get profile information for %r: %s", target, e
                    )

        is_exempt = yield self._is_exempt_from_privacy_policy(builder, requester)
        if require_consent and not is_exempt:
            yield self.assert_accepted_privacy_policy(requester)

        if token_id is not None:
            builder.internal_metadata.token_id = token_id

        if txn_id is not None:
            builder.internal_metadata.txn_id = txn_id

        event, context = yield self.create_new_client_event(
            builder=builder, requester=requester, prev_event_ids=prev_event_ids,
        )

        # In an ideal world we wouldn't need the second part of this condition. However,
        # this behaviour isn't spec'd yet, meaning we should be able to deactivate this
        # behaviour. Another reason is that this code is also evaluated each time a new
        # m.room.aliases event is created, which includes hitting a /directory route.
        # Therefore not including this condition here would render the similar one in
        # synapse.handlers.directory pointless.
        if builder.type == EventTypes.Aliases and self.require_membership_for_aliases:
            # Ideally we'd do the membership check in event_auth.check(), which
            # describes a spec'd algorithm for authenticating events received over
            # federation as well as those created locally. As of room v3, aliases events
            # can be created by users that are not in the room, therefore we have to
            # tolerate them in event_auth.check().
            prev_state_ids = yield context.get_prev_state_ids()
            prev_event_id = prev_state_ids.get((EventTypes.Member, event.sender))
            prev_event = (
                yield self.store.get_event(prev_event_id, allow_none=True)
                if prev_event_id
                else None
            )
            if not prev_event or prev_event.membership != Membership.JOIN:
                logger.warning(
                    (
                        "Attempt to send `m.room.aliases` in room %s by user %s but"
                        " membership is %s"
                    ),
                    event.room_id,
                    event.sender,
                    prev_event.membership if prev_event else None,
                )

                raise AuthError(
                    403, "You must be in the room to create an alias for it"
                )

        self.validator.validate_new(event, self.config)

        return (event, context)
Ejemplo n.º 10
0
    def create_association(
        self,
        requester,
        room_alias,
        room_id,
        servers=None,
        send_event=True,
        check_membership=True,
    ):
        """Attempt to create a new alias

        Args:
            requester (Requester)
            room_alias (RoomAlias)
            room_id (str)
            servers (list[str]|None): List of servers that others servers
                should try and join via
            send_event (bool): Whether to send an updated m.room.aliases event
            check_membership (bool): Whether to check if the user is in the room
                before the alias can be set (if the server's config requires it).

        Returns:
            Deferred
        """

        user_id = requester.user.to_string()

        if len(room_alias.to_string()) > MAX_ALIAS_LENGTH:
            raise SynapseError(
                400,
                "Can't create aliases longer than %s characters" % MAX_ALIAS_LENGTH,
                Codes.INVALID_PARAM,
            )

        service = requester.app_service
        if service:
            if not service.is_interested_in_alias(room_alias.to_string()):
                raise SynapseError(
                    400,
                    "This application service has not reserved" " this kind of alias.",
                    errcode=Codes.EXCLUSIVE,
                )
        else:
            if self.require_membership and check_membership:
                rooms_for_user = yield self.store.get_rooms_for_user(user_id)
                if room_id not in rooms_for_user:
                    raise AuthError(
                        403, "You must be in the room to create an alias for it"
                    )

            if not self.spam_checker.user_may_create_room_alias(user_id, room_alias):
                raise AuthError(403, "This user is not permitted to create this alias")

            if not self.config.is_alias_creation_allowed(
                user_id, room_id, room_alias.to_string()
            ):
                # Lets just return a generic message, as there may be all sorts of
                # reasons why we said no. TODO: Allow configurable error messages
                # per alias creation rule?
                raise SynapseError(403, "Not allowed to create alias")

            can_create = yield self.can_modify_alias(room_alias, user_id=user_id)
            if not can_create:
                raise AuthError(
                    400,
                    "This alias is reserved by an application service.",
                    errcode=Codes.EXCLUSIVE,
                )

        yield self._create_association(room_alias, room_id, servers, creator=user_id)
        if send_event:
            yield self.send_room_alias_update_event(requester, room_id)
Ejemplo n.º 11
0
    async def set_displayname(
        self,
        target_user: UserID,
        requester: Requester,
        new_displayname: str,
        by_admin: 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.
        """
        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._update_join_states(requester, target_user)
Ejemplo n.º 12
0
    def _update_membership(
        self,
        requester,
        target,
        room_id,
        action,
        txn_id=None,
        remote_room_hosts=None,
        third_party_signed=None,
        ratelimit=True,
        content=None,
    ):
        content_specified = bool(content)
        if content is None:
            content = {}
        else:
            # We do a copy here as we potentially change some keys
            # later on.
            content = dict(content)

        effective_membership_state = action
        if action in ["kick", "unban"]:
            effective_membership_state = "leave"

        # if this is a join with a 3pid signature, we may need to turn a 3pid
        # invite into a normal invite before we can handle the join.
        if third_party_signed is not None:
            yield self.federation_handler.exchange_third_party_invite(
                third_party_signed["sender"],
                target.to_string(),
                room_id,
                third_party_signed,
            )

        if not remote_room_hosts:
            remote_room_hosts = []

        if effective_membership_state not in (
                "leave",
                "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")

        if effective_membership_state == Membership.INVITE:
            # block any attempts to invite the server notices mxid
            if target.to_string() == self._server_notices_mxid:
                raise SynapseError(
                    http_client.FORBIDDEN,
                    "Cannot invite this user",
                )

            block_invite = False

            if (self._server_notices_mxid is not None and
                    requester.user.to_string() == self._server_notices_mxid):
                # allow the server notices mxid to send invites
                is_requester_admin = True

            else:
                is_requester_admin = yield self.auth.is_server_admin(
                    requester.user, )

            if not is_requester_admin:
                if self.config.block_non_admin_invites:
                    logger.info(
                        "Blocking invite: user is not admin and non-admin "
                        "invites disabled")
                    block_invite = True

                if not self.spam_checker.user_may_invite(
                        requester.user.to_string(),
                        target.to_string(),
                        room_id,
                ):
                    logger.info("Blocking invite due to spam checker")
                    block_invite = True

            if block_invite:
                raise SynapseError(
                    403,
                    "Invites have been disabled on this server",
                )

        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)

        current_state_ids = yield self.state_handler.get_current_state_ids(
            room_id,
            latest_event_ids=latest_event_ids,
        )

        old_state_id = current_state_ids.get(
            (EventTypes.Member, target.to_string()))
        if old_state_id:
            old_state = yield self.store.get_event(old_state_id,
                                                   allow_none=True)
            old_membership = old_state.content.get(
                "membership") if old_state else None
            if action == "unban" and old_membership != "ban":
                raise SynapseError(403,
                                   "Cannot unban user who was not banned"
                                   " (membership=%s)" % old_membership,
                                   errcode=Codes.BAD_STATE)
            if old_membership == "ban" and action != "unban":
                raise SynapseError(403,
                                   "Cannot %s user who was banned" %
                                   (action, ),
                                   errcode=Codes.BAD_STATE)

            if old_state:
                same_content = content == old_state.content
                same_membership = old_membership == effective_membership_state
                same_sender = requester.user.to_string() == old_state.sender
                if same_sender and same_membership and same_content:
                    defer.returnValue(old_state)

            # we don't allow people to reject invites to the server notice
            # room, but they can leave it once they are joined.
            if (old_membership == Membership.INVITE
                    and effective_membership_state == Membership.LEAVE):
                is_blocked = yield self._is_server_notice_room(room_id)
                if is_blocked:
                    raise SynapseError(
                        http_client.FORBIDDEN,
                        "You cannot reject this invite",
                        errcode=Codes.CANNOT_LEAVE_SERVER_NOTICE_ROOM,
                    )

        is_host_in_room = yield self._is_host_in_room(current_state_ids)

        if effective_membership_state == Membership.JOIN:
            if requester.is_guest:
                guest_can_join = yield self._can_guest_join(current_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 not is_host_in_room:
                inviter = yield self._get_inviter(target.to_string(), room_id)
                if inviter and not self.hs.is_mine(inviter):
                    remote_room_hosts.append(inviter.domain)

                content["membership"] = Membership.JOIN

                profile = self.profile_handler
                if not content_specified:
                    content["displayname"] = yield profile.get_displayname(
                        target)
                    content["avatar_url"] = yield profile.get_avatar_url(
                        target)
                    content["dob"] = yield profile.get_dob(target)

                if requester.is_guest:
                    content["kind"] = "guest"

                ret = yield self._remote_join(requester, remote_room_hosts,
                                              room_id, target, content)
                defer.returnValue(ret)

        elif effective_membership_state == Membership.LEAVE:
            if not is_host_in_room:
                # perhaps we've been invited
                inviter = yield self._get_inviter(target.to_string(), room_id)
                if not inviter:
                    raise SynapseError(404, "Not a known room")

                if self.hs.is_mine(inviter):
                    # the inviter was on our server, but has now left. Carry on
                    # with the normal rejection codepath.
                    #
                    # This is a bit of a hack, because the room might still be
                    # active on other servers.
                    pass
                else:
                    # send the rejection to the inviter's HS.
                    remote_room_hosts = remote_room_hosts + [inviter.domain]
                    res = yield self._remote_reject_invite(
                        requester,
                        remote_room_hosts,
                        room_id,
                        target,
                    )
                    defer.returnValue(res)

        res = yield self._local_membership_update(
            requester=requester,
            target=target,
            room_id=room_id,
            membership=effective_membership_state,
            txn_id=txn_id,
            ratelimit=ratelimit,
            prev_events_and_hashes=prev_events_and_hashes,
            content=content,
        )
        defer.returnValue(res)
Ejemplo n.º 13
0
    def get_event_context(self, user, room_id, event_id, limit, event_filter):
        """Retrieves events, pagination tokens and state around a given event
        in a room.

        Args:
            user (UserID)
            room_id (str)
            event_id (str)
            limit (int): The maximum number of events to return in total
                (excluding state).
            event_filter (Filter|None): the filter to apply to the events returned
                (excluding the target event_id)

        Returns:
            dict, or None if the event isn't found
        """
        before_limit = math.floor(limit / 2.0)
        after_limit = limit - before_limit

        users = yield self.store.get_users_in_room(room_id)
        is_peeking = user.to_string() not in users

        def filter_evts(events):
            return filter_events_for_client(self.store,
                                            user.to_string(),
                                            events,
                                            is_peeking=is_peeking)

        event = yield self.store.get_event(event_id,
                                           get_prev_content=True,
                                           allow_none=True)
        if not event:
            return None

        filtered = yield (filter_evts([event]))
        if not filtered:
            raise AuthError(403,
                            "You don't have permission to access that event.")

        results = yield self.store.get_events_around(room_id, event_id,
                                                     before_limit, after_limit,
                                                     event_filter)

        results["events_before"] = yield filter_evts(results["events_before"])
        results["events_after"] = yield filter_evts(results["events_after"])
        results["event"] = event

        if results["events_after"]:
            last_event_id = results["events_after"][-1].event_id
        else:
            last_event_id = event_id

        if event_filter and event_filter.lazy_load_members():
            state_filter = StateFilter.from_lazy_load_member_list(
                ev.sender for ev in itertools.chain(
                    results["events_before"],
                    (results["event"], ),
                    results["events_after"],
                ))
        else:
            state_filter = StateFilter.all()

        # XXX: why do we return the state as of the last event rather than the
        # first? Shouldn't we be consistent with /sync?
        # https://github.com/matrix-org/matrix-doc/issues/687

        state = yield self.store.get_state_for_events(
            [last_event_id], state_filter=state_filter)
        results["state"] = list(state[last_event_id].values())

        # We use a dummy token here as we only care about the room portion of
        # the token, which we replace.
        token = StreamToken.START

        results["start"] = token.copy_and_replace(
            "room_key", results["start"]).to_string()

        results["end"] = token.copy_and_replace("room_key",
                                                results["end"]).to_string()

        return results
Ejemplo n.º 14
0
    def get_user_by_req(
        self,
        request: Request,
        allow_guest: bool = False,
        rights: str = "access",
        allow_expired: bool = False,
    ):
        """ 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:
            defer.Deferred: resolves to a `synapse.types.Requester` object
        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 = self.hs.get_ip_from_request(request)
            user_agent = request.requestHeaders.getRawHeaders(
                b"User-Agent",
                default=[b""])[0].decode("ascii", "surrogateescape")

            access_token = self.get_access_token_from_request(request)

            user_id, app_service = yield self._get_appservice_user_id(request)
            if user_id:
                request.authenticated_entity = user_id
                opentracing.set_tag("authenticated_entity", user_id)
                opentracing.set_tag("appservice_id", app_service.id)

                if ip_addr and self._track_appservice_user_ips:
                    yield 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
                    )

                return synapse.types.create_requester(user_id,
                                                      app_service=app_service)

            user_info = yield self.get_user_by_access_token(
                access_token, rights, allow_expired=allow_expired)
            user = user_info["user"]
            token_id = user_info["token_id"]
            is_guest = user_info["is_guest"]

            # Deny the request if the user account has expired.
            if self._account_validity.enabled and not allow_expired:
                user_id = user.to_string()
                expiration_ts = yield self.store.get_expiration_ts_for_user(
                    user_id)
                if (expiration_ts is not None
                        and self.clock.time_msec() >= expiration_ts):
                    raise AuthError(403,
                                    "User account has expired",
                                    errcode=Codes.EXPIRED_ACCOUNT)

            # device_id may not be present if get_user_by_access_token has been
            # stubbed out.
            device_id = user_info.get("device_id")

            if user and access_token and ip_addr:
                yield self.store.insert_client_ip(
                    user_id=user.to_string(),
                    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,
                )

            request.authenticated_entity = user.to_string()
            opentracing.set_tag("authenticated_entity", user.to_string())
            if device_id:
                opentracing.set_tag("device_id", device_id)

            return synapse.types.create_requester(user,
                                                  token_id,
                                                  is_guest,
                                                  device_id,
                                                  app_service=app_service)
        except KeyError:
            raise MissingClientTokenError()
Ejemplo n.º 15
0
    def _update_membership(
        self,
        requester,
        target,
        room_id,
        action,
        txn_id=None,
        remote_room_hosts=None,
        third_party_signed=None,
        ratelimit=True,
        content=None,
    ):
        content_specified = bool(content)
        if content is None:
            content = {}
        else:
            # We do a copy here as we potentially change some keys
            # later on.
            content = dict(content)

        effective_membership_state = action
        if action in ["kick", "unban"]:
            effective_membership_state = "leave"

        # if this is a join with a 3pid signature, we may need to turn a 3pid
        # invite into a normal invite before we can handle the join.
        if third_party_signed is not None:
            replication = self.hs.get_replication_layer()
            yield replication.exchange_third_party_invite(
                third_party_signed["sender"],
                target.to_string(),
                room_id,
                third_party_signed,
            )

        if not remote_room_hosts:
            remote_room_hosts = []

        if effective_membership_state not in (
                "leave",
                "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")

        if effective_membership_state == "invite":
            block_invite = False
            is_requester_admin = yield self.auth.is_server_admin(
                requester.user, )
            if not is_requester_admin:
                if self.hs.config.block_non_admin_invites:
                    logger.info(
                        "Blocking invite: user is not admin and non-admin "
                        "invites disabled")
                    block_invite = True

                if not self.spam_checker.user_may_invite(
                        requester.user.to_string(),
                        target.to_string(),
                        room_id,
                ):
                    logger.info("Blocking invite due to spam checker")
                    block_invite = True

            if block_invite:
                raise SynapseError(
                    403,
                    "Invites have been disabled on this server",
                )

        latest_event_ids = yield self.store.get_latest_event_ids_in_room(
            room_id)
        current_state_ids = yield self.state_handler.get_current_state_ids(
            room_id,
            latest_event_ids=latest_event_ids,
        )

        old_state_id = current_state_ids.get(
            (EventTypes.Member, target.to_string()))
        if old_state_id:
            old_state = yield self.store.get_event(old_state_id,
                                                   allow_none=True)
            old_membership = old_state.content.get(
                "membership") if old_state else None
            if action == "unban" and old_membership != "ban":
                raise SynapseError(403,
                                   "Cannot unban user who was not banned"
                                   " (membership=%s)" % old_membership,
                                   errcode=Codes.BAD_STATE)
            if old_membership == "ban" and action != "unban":
                raise SynapseError(403,
                                   "Cannot %s user who was banned" %
                                   (action, ),
                                   errcode=Codes.BAD_STATE)

            if old_state:
                same_content = content == old_state.content
                same_membership = old_membership == effective_membership_state
                same_sender = requester.user.to_string() == old_state.sender
                if same_sender and same_membership and same_content:
                    defer.returnValue(old_state)

        is_host_in_room = yield self._is_host_in_room(current_state_ids)

        if effective_membership_state == Membership.JOIN:
            if requester.is_guest:
                guest_can_join = yield self._can_guest_join(current_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 not is_host_in_room:
                inviter = yield self.get_inviter(target.to_string(), room_id)
                if inviter and not self.hs.is_mine(inviter):
                    remote_room_hosts.append(inviter.domain)

                content["membership"] = Membership.JOIN

                profile = self.profile_handler
                if not content_specified:
                    content["displayname"] = yield profile.get_displayname(
                        target)
                    content["avatar_url"] = yield profile.get_avatar_url(
                        target)

                if requester.is_guest:
                    content["kind"] = "guest"

                ret = yield self.remote_join(remote_room_hosts, room_id,
                                             target, content)
                defer.returnValue(ret)

        elif effective_membership_state == Membership.LEAVE:
            if not is_host_in_room:
                # perhaps we've been invited
                inviter = yield self.get_inviter(target.to_string(), room_id)
                if not inviter:
                    raise SynapseError(404, "Not a known room")

                if self.hs.is_mine(inviter):
                    # the inviter was on our server, but has now left. Carry on
                    # with the normal rejection codepath.
                    #
                    # This is a bit of a hack, because the room might still be
                    # active on other servers.
                    pass
                else:
                    # send the rejection to the inviter's HS.
                    remote_room_hosts = remote_room_hosts + [inviter.domain]
                    fed_handler = self.hs.get_handlers().federation_handler
                    try:
                        ret = yield fed_handler.do_remotely_reject_invite(
                            remote_room_hosts,
                            room_id,
                            target.to_string(),
                        )
                        defer.returnValue(ret)
                    except Exception as e:
                        # if we were unable to reject the exception, just mark
                        # it as rejected on our end and plough ahead.
                        #
                        # The 'except' clause is very broad, but we need to
                        # capture everything from DNS failures upwards
                        #
                        logger.warn("Failed to reject invite: %s", e)

                        yield self.store.locally_reject_invite(
                            target.to_string(), room_id)

                        defer.returnValue({})

        res = yield self._local_membership_update(
            requester=requester,
            target=target,
            room_id=room_id,
            membership=effective_membership_state,
            txn_id=txn_id,
            ratelimit=ratelimit,
            prev_event_ids=latest_event_ids,
            content=content,
        )
        defer.returnValue(res)
Ejemplo n.º 16
0
    def persist_and_notify_client_event(
        self, requester, event, context, ratelimit=True, extra_users=[]
    ):
        """Called when we have fully built the event, have already
        calculated the push actions for the event, and checked auth.

        This should only be run on master.
        """
        assert not self.config.worker_app

        if ratelimit:
            # We check if this is a room admin redacting an event so that we
            # can apply different ratelimiting. We do this by simply checking
            # it's not a self-redaction (to avoid having to look up whether the
            # user is actually admin or not).
            is_admin_redaction = False
            if event.type == EventTypes.Redaction:
                original_event = yield self.store.get_event(
                    event.redacts,
                    redact_behaviour=EventRedactBehaviour.AS_IS,
                    get_prev_content=False,
                    allow_rejected=False,
                    allow_none=True,
                )

                is_admin_redaction = (
                    original_event and event.sender != original_event.sender
                )

            yield self.base_handler.ratelimit(
                requester, is_admin_redaction=is_admin_redaction
            )

        yield self.base_handler.maybe_kick_guest_users(event, context)

        if event.type == EventTypes.CanonicalAlias:
            # Check the alias is acually valid (at this time at least)
            room_alias_str = event.content.get("alias", None)
            if room_alias_str:
                room_alias = RoomAlias.from_string(room_alias_str)
                directory_handler = self.hs.get_handlers().directory_handler
                mapping = yield directory_handler.get_association(room_alias)

                if mapping["room_id"] != event.room_id:
                    raise SynapseError(
                        400,
                        "Room alias %s does not point to the room" % (room_alias_str,),
                    )

        federation_handler = self.hs.get_handlers().federation_handler

        if event.type == EventTypes.Member:
            if event.content["membership"] == Membership.INVITE:

                def is_inviter_member_event(e):
                    return e.type == EventTypes.Member and e.sender == event.sender

                current_state_ids = yield context.get_current_state_ids()

                state_to_include_ids = [
                    e_id
                    for k, e_id in iteritems(current_state_ids)
                    if k[0] in self.room_invite_state_types
                    or k == (EventTypes.Member, event.sender)
                ]

                state_to_include = yield self.store.get_events(state_to_include_ids)

                event.unsigned["invite_room_state"] = [
                    {
                        "type": e.type,
                        "state_key": e.state_key,
                        "content": e.content,
                        "sender": e.sender,
                    }
                    for e in itervalues(state_to_include)
                ]

                invitee = UserID.from_string(event.state_key)
                if not self.hs.is_mine(invitee):
                    # TODO: Can we add signature from remote server in a nicer
                    # way? If we have been invited by a remote server, we need
                    # to get them to sign the event.

                    returned_invite = yield federation_handler.send_invite(
                        invitee.domain, event
                    )

                    event.unsigned.pop("room_state", None)

                    # TODO: Make sure the signatures actually are correct.
                    event.signatures.update(returned_invite.signatures)

        if event.type == EventTypes.Redaction:
            original_event = yield self.store.get_event(
                event.redacts,
                redact_behaviour=EventRedactBehaviour.AS_IS,
                get_prev_content=False,
                allow_rejected=False,
                allow_none=True,
            )

            # we can make some additional checks now if we have the original event.
            if original_event:
                if original_event.type == EventTypes.Create:
                    raise AuthError(403, "Redacting create events is not permitted")

                if original_event.room_id != event.room_id:
                    raise SynapseError(400, "Cannot redact event from a different room")

            prev_state_ids = yield context.get_prev_state_ids()
            auth_events_ids = yield self.auth.compute_auth_events(
                event, prev_state_ids, for_verification=True
            )
            auth_events = yield self.store.get_events(auth_events_ids)
            auth_events = {(e.type, e.state_key): e for e in auth_events.values()}
            room_version = yield self.store.get_room_version(event.room_id)

            if event_auth.check_redaction(room_version, event, auth_events=auth_events):
                # this user doesn't have 'redact' rights, so we need to do some more
                # checks on the original event. Let's start by checking the original
                # event exists.
                if not original_event:
                    raise NotFoundError("Could not find event %s" % (event.redacts,))

                if event.user_id != original_event.user_id:
                    raise AuthError(403, "You don't have permission to redact events")

                # all the checks are done.
                event.internal_metadata.recheck_redaction = False

        if event.type == EventTypes.Create:
            prev_state_ids = yield context.get_prev_state_ids()
            if prev_state_ids:
                raise AuthError(403, "Changing the room create event is forbidden")

        event_stream_id, max_stream_id = yield self.storage.persistence.persist_event(
            event, context=context
        )

        if self._ephemeral_events_enabled:
            # If there's an expiry timestamp on the event, schedule its expiry.
            self._message_handler.maybe_schedule_expiry(event)

        yield self.pusher_pool.on_new_notifications(event_stream_id, max_stream_id)

        def _notify():
            try:
                self.notifier.on_new_room_event(
                    event, event_stream_id, max_stream_id, extra_users=extra_users
                )
            except Exception:
                logger.exception("Error notifying about new room event")

        run_in_background(_notify)

        if event.type == EventTypes.Message:
            # We don't want to block sending messages on any presence code. This
            # matters as sometimes presence code can take a while.
            run_in_background(self._bump_active_time, requester.user)
Ejemplo n.º 17
0
    def send_membership_event(
        self,
        requester,
        event,
        context,
        remote_room_hosts=None,
        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.
            is_guest (bool): Whether the sender is a guest.
            room_hosts ([str]): Homeservers which are likely to already be in
                the room, and could be danced with in order to join this
                homeserver for the first time.
            ratelimit (bool): Whether to rate limit this request.
        Raises:
            SynapseError if there was a problem changing the membership.
        """
        remote_room_hosts = remote_room_hosts or []

        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 = synapse.types.create_requester(target_user)

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

        if event.membership == Membership.JOIN:
            if requester.is_guest:
                guest_can_join = yield self._can_guest_join(
                    context.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 message_handler.handle_new_client_event(
            requester,
            event,
            context,
            extra_users=[target_user],
            ratelimit=ratelimit,
        )

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

        if event.membership == Membership.JOIN:
            # Only fire user_joined_room if the user has acutally 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 user_joined_room(self.distributor, 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:
                    user_left_room(self.distributor, target_user, room_id)
Ejemplo n.º 18
0
    async def update_membership_locked(
        self,
        requester: Requester,
        target: UserID,
        room_id: str,
        action: str,
        txn_id: Optional[str] = None,
        remote_room_hosts: Optional[List[str]] = None,
        third_party_signed: Optional[dict] = None,
        ratelimit: bool = True,
        content: Optional[dict] = None,
        require_consent: bool = True,
    ) -> Tuple[str, int]:
        """Helper for update_membership.

        Assumes that the membership linearizer is already held for the room.
        """
        content_specified = bool(content)
        if content is None:
            content = {}
        else:
            # We do a copy here as we potentially change some keys
            # later on.
            content = dict(content)

        # allow the server notices mxid to set room-level profile
        is_requester_server_notices_user = (
            self._server_notices_mxid is not None
            and requester.user.to_string() == self._server_notices_mxid)

        if (not self.allow_per_room_profiles
                and not is_requester_server_notices_user
            ) or requester.shadow_banned:
            # Strip profile data, knowing that new profile data will be added to the
            # event's content in event_creation_handler.create_event() using the target's
            # global profile.
            content.pop("displayname", None)
            content.pop("avatar_url", None)

        effective_membership_state = action
        if action in ["kick", "unban"]:
            effective_membership_state = "leave"

        # if this is a join with a 3pid signature, we may need to turn a 3pid
        # invite into a normal invite before we can handle the join.
        if third_party_signed is not None:
            await self.federation_handler.exchange_third_party_invite(
                third_party_signed["sender"],
                target.to_string(),
                room_id,
                third_party_signed,
            )

        if not remote_room_hosts:
            remote_room_hosts = []

        if effective_membership_state not in ("leave", "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")

        if effective_membership_state == Membership.INVITE:
            target_id = target.to_string()
            if ratelimit:
                # Don't ratelimit application services.
                if not requester.app_service or requester.app_service.is_rate_limited(
                ):
                    self.ratelimit_invite(room_id, target_id)

            # block any attempts to invite the server notices mxid
            if target_id == self._server_notices_mxid:
                raise SynapseError(HTTPStatus.FORBIDDEN,
                                   "Cannot invite this user")

            block_invite = False

            if (self._server_notices_mxid is not None and
                    requester.user.to_string() == self._server_notices_mxid):
                # allow the server notices mxid to send invites
                is_requester_admin = True

            else:
                is_requester_admin = await self.auth.is_server_admin(
                    requester.user)

            if not is_requester_admin:
                if self.config.block_non_admin_invites:
                    logger.info(
                        "Blocking invite: user is not admin and non-admin "
                        "invites disabled")
                    block_invite = True

                if not await self.spam_checker.user_may_invite(
                        requester.user.to_string(), target_id, room_id):
                    logger.info("Blocking invite due to spam checker")
                    block_invite = True

            if block_invite:
                raise SynapseError(
                    403, "Invites have been disabled on this server")

        latest_event_ids = await self.store.get_prev_events_for_room(room_id)

        current_state_ids = await self.state_handler.get_current_state_ids(
            room_id, latest_event_ids=latest_event_ids)

        # TODO: Refactor into dictionary of explicitly allowed transitions
        # between old and new state, with specific error messages for some
        # transitions and generic otherwise
        old_state_id = current_state_ids.get(
            (EventTypes.Member, target.to_string()))
        if old_state_id:
            old_state = await self.store.get_event(old_state_id,
                                                   allow_none=True)
            old_membership = old_state.content.get(
                "membership") if old_state else None
            if action == "unban" and old_membership != "ban":
                raise SynapseError(
                    403,
                    "Cannot unban user who was not banned"
                    " (membership=%s)" % old_membership,
                    errcode=Codes.BAD_STATE,
                )
            if old_membership == "ban" and action != "unban":
                raise SynapseError(
                    403,
                    "Cannot %s user who was banned" % (action, ),
                    errcode=Codes.BAD_STATE,
                )

            if old_state:
                same_content = content == old_state.content
                same_membership = old_membership == effective_membership_state
                same_sender = requester.user.to_string() == old_state.sender
                if same_sender and same_membership and same_content:
                    # duplicate event.
                    # we know it was persisted, so must have a stream ordering.
                    assert old_state.internal_metadata.stream_ordering
                    return (
                        old_state.event_id,
                        old_state.internal_metadata.stream_ordering,
                    )

            if old_membership in ["ban", "leave"] and action == "kick":
                raise AuthError(403, "The target user is not in the room")

            # we don't allow people to reject invites to the server notice
            # room, but they can leave it once they are joined.
            if (old_membership == Membership.INVITE
                    and effective_membership_state == Membership.LEAVE):
                is_blocked = await self._is_server_notice_room(room_id)
                if is_blocked:
                    raise SynapseError(
                        HTTPStatus.FORBIDDEN,
                        "You cannot reject this invite",
                        errcode=Codes.CANNOT_LEAVE_SERVER_NOTICE_ROOM,
                    )
        else:
            if action == "kick":
                raise AuthError(403, "The target user is not in the room")

        is_host_in_room = await self._is_host_in_room(current_state_ids)

        if effective_membership_state == Membership.JOIN:
            if requester.is_guest:
                guest_can_join = await self._can_guest_join(current_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 not is_host_in_room:
                if ratelimit:
                    time_now_s = self.clock.time()
                    (
                        allowed,
                        time_allowed,
                    ) = self._join_rate_limiter_remote.can_requester_do_action(
                        requester, )

                    if not allowed:
                        raise LimitExceededError(
                            retry_after_ms=int(1000 *
                                               (time_allowed - time_now_s)))

                inviter = await self._get_inviter(target.to_string(), room_id)
                if inviter and not self.hs.is_mine(inviter):
                    remote_room_hosts.append(inviter.domain)

                content["membership"] = Membership.JOIN

                profile = self.profile_handler
                if not content_specified:
                    content["displayname"] = await profile.get_displayname(
                        target)
                    content["avatar_url"] = await profile.get_avatar_url(target
                                                                         )

                if requester.is_guest:
                    content["kind"] = "guest"

                remote_join_response = await self._remote_join(
                    requester, remote_room_hosts, room_id, target, content)

                return remote_join_response

        elif effective_membership_state == Membership.LEAVE:
            if not is_host_in_room:
                # perhaps we've been invited
                (
                    current_membership_type,
                    current_membership_event_id,
                ) = await self.store.get_local_current_membership_for_user_in_room(
                    target.to_string(), room_id)
                if (current_membership_type != Membership.INVITE
                        or not current_membership_event_id):
                    logger.info(
                        "%s sent a leave request to %s, but that is not an active room "
                        "on this server, and there is no pending invite",
                        target,
                        room_id,
                    )

                    raise SynapseError(404, "Not a known room")

                invite = await self.store.get_event(current_membership_event_id
                                                    )
                logger.info("%s rejects invite to %s from %s", target, room_id,
                            invite.sender)

                if not self.hs.is_mine_id(invite.sender):
                    # send the rejection to the inviter's HS (with fallback to
                    # local event)
                    return await self.remote_reject_invite(
                        invite.event_id,
                        txn_id,
                        requester,
                        content,
                    )

                # the inviter was on our server, but has now left. Carry on
                # with the normal rejection codepath, which will also send the
                # rejection out to any other servers we believe are still in the room.

                # thanks to overzealous cleaning up of event_forward_extremities in
                # `delete_old_current_state_events`, it's possible to end up with no
                # forward extremities here. If that happens, let's just hang the
                # rejection off the invite event.
                #
                # see: https://github.com/matrix-org/synapse/issues/7139
                if len(latest_event_ids) == 0:
                    latest_event_ids = [invite.event_id]

        return await self._local_membership_update(
            requester=requester,
            target=target,
            room_id=room_id,
            membership=effective_membership_state,
            txn_id=txn_id,
            ratelimit=ratelimit,
            prev_event_ids=latest_event_ids,
            content=content,
            require_consent=require_consent,
        )
Ejemplo n.º 19
0
    async def _get_appservice_user_id_and_device_id(
        self, request: Request
    ) -> Tuple[Optional[str], Optional[str], Optional[ApplicationService]]:
        """
        Given a request, reads the request parameters to determine:
        - whether it's an application service that's making this request
        - what user the application service should be treated as controlling
          (the user_id URI parameter allows an application service to masquerade
          any applicable user in its namespace)
        - what device the application service should be treated as controlling
          (the device_id[^1] URI parameter allows an application service to masquerade
          as any device that exists for the relevant user)

        [^1] Unstable and provided by MSC3202.
             Must use `org.matrix.msc3202.device_id` in place of `device_id` for now.

        Returns:
            3-tuple of
            (user ID?, device ID?, application service?)

        Postconditions:
        - If an application service is returned, so is a user ID
        - A user ID is never returned without an application service
        - A device ID is never returned without a user ID or an application service
        - The returned application service, if present, is permitted to control the
          returned user ID.
        - The returned device ID, if present, has been checked to be a valid device ID
          for the returned user ID.
        """
        DEVICE_ID_ARG_NAME = b"org.matrix.msc3202.device_id"

        app_service = self.store.get_app_service_by_token(
            self.get_access_token_from_request(request))
        if app_service is None:
            return None, None, None

        if app_service.ip_range_whitelist:
            ip_address = IPAddress(request.getClientIP())
            if ip_address not in app_service.ip_range_whitelist:
                return None, None, None

        # This will always be set by the time Twisted calls us.
        assert request.args is not None

        if b"user_id" in request.args:
            effective_user_id = request.args[b"user_id"][0].decode("utf8")
            await self.validate_appservice_can_control_user_id(
                app_service, effective_user_id)
        else:
            effective_user_id = app_service.sender

        effective_device_id: Optional[str] = None

        if (self.hs.config.experimental.msc3202_device_masquerading_enabled
                and DEVICE_ID_ARG_NAME in request.args):
            effective_device_id = request.args[DEVICE_ID_ARG_NAME][0].decode(
                "utf8")
            # We only just set this so it can't be None!
            assert effective_device_id is not None
            device_opt = await self.store.get_device(effective_user_id,
                                                     effective_device_id)
            if device_opt is None:
                # For now, use 400 M_EXCLUSIVE if the device doesn't exist.
                # This is an open thread of discussion on MSC3202 as of 2021-12-09.
                raise AuthError(
                    400,
                    f"Application service trying to use a device that doesn't exist ('{effective_device_id}' for {effective_user_id})",
                    Codes.EXCLUSIVE,
                )

        return effective_user_id, effective_device_id, app_service
Ejemplo n.º 20
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)
Ejemplo n.º 21
0
    async def get_space_summary(
        self,
        requester: str,
        room_id: str,
        suggested_only: bool = False,
        max_rooms_per_space: Optional[int] = None,
    ) -> JsonDict:
        """
        Implementation of the space summary C-S API

        Args:
            requester:  user id of the user making this request

            room_id: room id to start the summary at

            suggested_only: whether we should only return children with the "suggested"
                flag set.

            max_rooms_per_space: an optional limit on the number of child rooms we will
                return. This does not apply to the root room (ie, room_id), and
                is overridden by MAX_ROOMS_PER_SPACE.

        Returns:
            summary dict to return
        """
        # First of all, check that the room is accessible.
        if not await self._is_local_room_accessible(room_id, requester):
            raise AuthError(
                403,
                "User %s not in room %s, and room previews are disabled" %
                (requester, room_id),
            )

        # the queue of rooms to process
        room_queue = deque((_RoomQueueEntry(room_id, ()), ))

        # rooms we have already processed
        processed_rooms: Set[str] = set()

        # events we have already processed. We don't necessarily have their event ids,
        # so instead we key on (room id, state key)
        processed_events: Set[Tuple[str, str]] = set()

        rooms_result: List[JsonDict] = []
        events_result: List[JsonDict] = []

        while room_queue and len(rooms_result) < MAX_ROOMS:
            queue_entry = room_queue.popleft()
            room_id = queue_entry.room_id
            if room_id in processed_rooms:
                # already done this room
                continue

            logger.debug("Processing room %s", room_id)

            is_in_room = await self._store.is_host_joined(
                room_id, self._server_name)

            # The client-specified max_rooms_per_space limit doesn't apply to the
            # room_id specified in the request, so we ignore it if this is the
            # first room we are processing.
            max_children = max_rooms_per_space if processed_rooms else None

            if is_in_room:
                room_entry = await self._summarize_local_room(
                    requester, None, room_id, suggested_only, max_children)

                events: Sequence[JsonDict] = []
                if room_entry:
                    rooms_result.append(room_entry.room)
                    events = room_entry.children_state_events

                logger.debug(
                    "Query of local room %s returned events %s",
                    room_id,
                    [
                        "%s->%s" % (ev["room_id"], ev["state_key"])
                        for ev in events
                    ],
                )
            else:
                fed_rooms = await self._summarize_remote_room(
                    queue_entry,
                    suggested_only,
                    max_children,
                    exclude_rooms=processed_rooms,
                )

                # The results over federation might include rooms that the we,
                # as the requesting server, are allowed to see, but the requesting
                # user is not permitted see.
                #
                # Filter the returned results to only what is accessible to the user.
                events = []
                for room_entry in fed_rooms:
                    room = room_entry.room
                    fed_room_id = room_entry.room_id

                    # The user can see the room, include it!
                    if await self._is_remote_room_accessible(
                            requester, fed_room_id, room):
                        # Before returning to the client, remove the allowed_room_ids
                        # and allowed_spaces keys.
                        room.pop("allowed_room_ids", None)
                        room.pop("allowed_spaces", None)

                        rooms_result.append(room)
                        events.extend(room_entry.children_state_events)

                    # All rooms returned don't need visiting again (even if the user
                    # didn't have access to them).
                    processed_rooms.add(fed_room_id)

                logger.debug(
                    "Query of %s returned rooms %s, events %s",
                    room_id,
                    [
                        room_entry.room.get("room_id")
                        for room_entry in fed_rooms
                    ],
                    [
                        "%s->%s" % (ev["room_id"], ev["state_key"])
                        for ev in events
                    ],
                )

            # the room we queried may or may not have been returned, but don't process
            # it again, anyway.
            processed_rooms.add(room_id)

            # XXX: is it ok that we blindly iterate through any events returned by
            #   a remote server, whether or not they actually link to any rooms in our
            #   tree?
            for ev in events:
                # remote servers might return events we have already processed
                # (eg, Dendrite returns inward pointers as well as outward ones), so
                # we need to filter them out, to avoid returning duplicate links to the
                # client.
                ev_key = (ev["room_id"], ev["state_key"])
                if ev_key in processed_events:
                    continue
                events_result.append(ev)

                # add the child to the queue. we have already validated
                # that the vias are a list of server names.
                room_queue.append(
                    _RoomQueueEntry(ev["state_key"], ev["content"]["via"]))
                processed_events.add(ev_key)

        return {"rooms": rooms_result, "events": events_result}
Ejemplo n.º 22
0
    def get_user_by_access_token(self, token, rights="access"):
        """ Validate access token and get user_id from it

        Args:
            token (str): The access token to get the user by.
            rights (str): The operation being performed; the access token must
                allow this.
        Returns:
            Deferred[dict]: dict that includes:
               `user` (UserID)
               `is_guest` (bool)
               `token_id` (int|None): access token id. May be None if guest
               `device_id` (str|None): device corresponding to access token
        Raises:
            AuthError if no user by that token exists or the token is invalid.
        """
        try:
            user_id, guest = self._parse_and_validate_macaroon(token, rights)
        except _InvalidMacaroonException:
            # doesn't look like a macaroon: treat it as an opaque token which
            # must be in the database.
            # TODO: it would be nice to get rid of this, but apparently some
            # people use access tokens which aren't macaroons
            r = yield self._look_up_user_by_access_token(token)
            defer.returnValue(r)

        try:
            user = UserID.from_string(user_id)

            if guest:
                # Guest access tokens are not stored in the database (there can
                # only be one access token per guest, anyway).
                #
                # In order to prevent guest access tokens being used as regular
                # user access tokens (and hence getting around the invalidation
                # process), we look up the user id and check that it is indeed
                # a guest user.
                #
                # It would of course be much easier to store guest access
                # tokens in the database as well, but that would break existing
                # guest tokens.
                stored_user = yield self.store.get_user_by_id(user_id)
                if not stored_user:
                    raise AuthError(self.TOKEN_NOT_FOUND_HTTP_STATUS,
                                    "Unknown user_id %s" % user_id,
                                    errcode=Codes.UNKNOWN_TOKEN)
                if not stored_user["is_guest"]:
                    raise AuthError(self.TOKEN_NOT_FOUND_HTTP_STATUS,
                                    "Guest access token used for regular user",
                                    errcode=Codes.UNKNOWN_TOKEN)
                ret = {
                    "user": user,
                    "is_guest": True,
                    "token_id": None,
                    # all guests get the same device id
                    "device_id": GUEST_DEVICE_ID,
                }
            elif rights == "delete_pusher":
                # We don't store these tokens in the database
                ret = {
                    "user": user,
                    "is_guest": False,
                    "token_id": None,
                    "device_id": None,
                }
            else:
                # This codepath exists for several reasons:
                #   * so that we can actually return a token ID, which is used
                #     in some parts of the schema (where we probably ought to
                #     use device IDs instead)
                #   * the only way we currently have to invalidate an
                #     access_token is by removing it from the database, so we
                #     have to check here that it is still in the db
                #   * some attributes (notably device_id) aren't stored in the
                #     macaroon. They probably should be.
                # TODO: build the dictionary from the macaroon once the
                # above are fixed
                ret = yield self._look_up_user_by_access_token(token)
                if ret["user"] != user:
                    logger.error("Macaroon user (%s) != DB user (%s)", user,
                                 ret["user"])
                    raise AuthError(self.TOKEN_NOT_FOUND_HTTP_STATUS,
                                    "User mismatch in macaroon",
                                    errcode=Codes.UNKNOWN_TOKEN)
            defer.returnValue(ret)
        except (pymacaroons.exceptions.MacaroonException, TypeError,
                ValueError):
            raise AuthError(self.TOKEN_NOT_FOUND_HTTP_STATUS,
                            "Invalid macaroon passed.",
                            errcode=Codes.UNKNOWN_TOKEN)
Ejemplo n.º 23
0
def check(room_version, event, auth_events, do_sig_check=True, do_size_check=True):
    """ Checks if this event is correctly authed.

    Args:
        room_version (str): the version of the room
        event: the event being checked.
        auth_events (dict: event-key -> event): the existing room state.

    Raises:
        AuthError if the checks fail

    Returns:
         if the auth checks pass.
    """
    if do_size_check:
        _check_size_limits(event)

    if not hasattr(event, "room_id"):
        raise AuthError(500, "Event has no room_id: %s" % event)

    if do_sig_check:
        sender_domain = get_domain_from_id(event.sender)

        is_invite_via_3pid = (
            event.type == EventTypes.Member
            and event.membership == Membership.INVITE
            and "third_party_invite" in event.content
        )

        # Check the sender's domain has signed the event
        if not event.signatures.get(sender_domain):
            # We allow invites via 3pid to have a sender from a different
            # HS, as the sender must match the sender of the original
            # 3pid invite. This is checked further down with the
            # other dedicated membership checks.
            if not is_invite_via_3pid:
                raise AuthError(403, "Event not signed by sender's server")

        if event.format_version in (EventFormatVersions.V1,):
            # Only older room versions have event IDs to check.
            event_id_domain = get_domain_from_id(event.event_id)

            # Check the origin domain has signed the event
            if not event.signatures.get(event_id_domain):
                raise AuthError(403, "Event not signed by sending server")

    if auth_events is None:
        # Oh, we don't know what the state of the room was, so we
        # are trusting that this is allowed (at least for now)
        logger.warn("Trusting event: %s", event.event_id)
        return

    if event.type == EventTypes.Create:
        sender_domain = get_domain_from_id(event.sender)
        room_id_domain = get_domain_from_id(event.room_id)
        if room_id_domain != sender_domain:
            raise AuthError(
                403, "Creation event's room_id domain does not match sender's"
            )

        room_version = event.content.get("room_version", "1")
        if room_version not in KNOWN_ROOM_VERSIONS:
            raise AuthError(
                403, "room appears to have unsupported version %s" % (room_version,)
            )
        # FIXME
        logger.debug("Allowing! %s", event)
        return

    creation_event = auth_events.get((EventTypes.Create, ""), None)

    if not creation_event:
        raise AuthError(403, "No create event in auth events")

    creating_domain = get_domain_from_id(event.room_id)
    originating_domain = get_domain_from_id(event.sender)
    if creating_domain != originating_domain:
        if not _can_federate(event, auth_events):
            raise AuthError(403, "This room has been marked as unfederatable.")

    # FIXME: Temp hack
    if event.type == EventTypes.Aliases:
        if not event.is_state():
            raise AuthError(403, "Alias event must be a state event")
        if not event.state_key:
            raise AuthError(403, "Alias event must have non-empty state_key")
        sender_domain = get_domain_from_id(event.sender)
        if event.state_key != sender_domain:
            raise AuthError(
                403, "Alias event's state_key does not match sender's domain"
            )
        logger.debug("Allowing! %s", event)
        return

    if logger.isEnabledFor(logging.DEBUG):
        logger.debug("Auth events: %s", [a.event_id for a in auth_events.values()])

    if event.type == EventTypes.Member:
        _is_membership_change_allowed(event, auth_events)
        logger.debug("Allowing! %s", event)
        return

    _check_event_sender_in_room(event, auth_events)

    # Special case to allow m.room.third_party_invite events wherever
    # a user is allowed to issue invites.  Fixes
    # https://github.com/vector-im/vector-web/issues/1208 hopefully
    if event.type == EventTypes.ThirdPartyInvite:
        user_level = get_user_power_level(event.user_id, auth_events)
        invite_level = _get_named_level(auth_events, "invite", 0)

        if user_level < invite_level:
            raise AuthError(403, "You don't have permission to invite users")
        else:
            logger.debug("Allowing! %s", event)
            return

    _can_send_event(event, auth_events)

    if event.type == EventTypes.PowerLevels:
        _check_power_levels(event, auth_events)

    if event.type == EventTypes.Redaction:
        check_redaction(room_version, event, auth_events)

    logger.debug("Allowing! %s", event)
Ejemplo n.º 24
0
    def get_user_by_req(self, request, allow_guest=False, rights="access"):
        """ Get a registered user's ID.

        Args:
            request - An HTTP request with an access_token query parameter.
        Returns:
            defer.Deferred: resolves to a ``synapse.types.Requester`` object
        Raises:
            AuthError if no user by that token exists or the token is invalid.
        """
        # Can optionally look elsewhere in the request (e.g. headers)
        try:
            ip_addr = self.hs.get_ip_from_request(request)
            user_agent = request.requestHeaders.getRawHeaders(
                b"User-Agent",
                default=[b""])[0].decode('ascii', 'surrogateescape')

            access_token = self.get_access_token_from_request(
                request, self.TOKEN_NOT_FOUND_HTTP_STATUS)

            user_id, app_service = yield self._get_appservice_user_id(request)
            if user_id:
                request.authenticated_entity = user_id

                if ip_addr and self.hs.config.track_appservice_user_ips:
                    yield 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
                    )

                defer.returnValue(
                    synapse.types.create_requester(user_id,
                                                   app_service=app_service))

            user_info = yield self.get_user_by_access_token(
                access_token, rights)
            user = user_info["user"]
            token_id = user_info["token_id"]
            is_guest = user_info["is_guest"]

            # device_id may not be present if get_user_by_access_token has been
            # stubbed out.
            device_id = user_info.get("device_id")

            if user and access_token and ip_addr:
                yield self.store.insert_client_ip(
                    user_id=user.to_string(),
                    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)

            request.authenticated_entity = user.to_string()

            defer.returnValue(
                synapse.types.create_requester(user,
                                               token_id,
                                               is_guest,
                                               device_id,
                                               app_service=app_service))
        except KeyError:
            raise AuthError(self.TOKEN_NOT_FOUND_HTTP_STATUS,
                            "Missing access token.",
                            errcode=Codes.MISSING_TOKEN)
Ejemplo n.º 25
0
def _check_power_levels(event, auth_events):
    user_list = event.content.get("users", {})
    # Validate users
    for k, v in user_list.items():
        try:
            UserID.from_string(k)
        except Exception:
            raise SynapseError(400, "Not a valid user_id: %s" % (k,))

        try:
            int(v)
        except Exception:
            raise SynapseError(400, "Not a valid power level: %s" % (v,))

    key = (event.type, event.state_key)
    current_state = auth_events.get(key)

    if not current_state:
        return

    user_level = get_user_power_level(event.user_id, auth_events)

    # Check other levels:
    levels_to_check = [
        ("users_default", None),
        ("events_default", None),
        ("state_default", None),
        ("ban", None),
        ("redact", None),
        ("kick", None),
        ("invite", None),
    ]

    old_list = current_state.content.get("users", {})
    for user in set(list(old_list) + list(user_list)):
        levels_to_check.append((user, "users"))

    old_list = current_state.content.get("events", {})
    new_list = event.content.get("events", {})
    for ev_id in set(list(old_list) + list(new_list)):
        levels_to_check.append((ev_id, "events"))

    old_state = current_state.content
    new_state = event.content

    for level_to_check, dir in levels_to_check:
        old_loc = old_state
        new_loc = new_state
        if dir:
            old_loc = old_loc.get(dir, {})
            new_loc = new_loc.get(dir, {})

        if level_to_check in old_loc:
            old_level = int(old_loc[level_to_check])
        else:
            old_level = None

        if level_to_check in new_loc:
            new_level = int(new_loc[level_to_check])
        else:
            new_level = None

        if new_level is not None and old_level is not None:
            if new_level == old_level:
                continue

        if dir == "users" and level_to_check != event.user_id:
            if old_level == user_level:
                raise AuthError(
                    403,
                    "You don't have permission to remove ops level equal "
                    "to your own",
                )

        # Check if the old and new levels are greater than the user level
        # (if defined)
        old_level_too_big = old_level is not None and old_level > user_level
        new_level_too_big = new_level is not None and new_level > user_level
        if old_level_too_big or new_level_too_big:
            raise AuthError(
                403, "You don't have permission to add ops level greater than your own"
            )
Ejemplo n.º 26
0
    def get_user_by_access_token(self, token, rights="access"):
        """ Validate access token and get user_id from it

        Args:
            token (str): The access token to get the user by.
            rights (str): The operation being performed; the access token must
                allow this.
        Returns:
            Deferred[dict]: dict that includes:
               `user` (UserID)
               `is_guest` (bool)
               `token_id` (int|None): access token id. May be None if guest
               `device_id` (str|None): device corresponding to access token
        Raises:
            AuthError if no user by that token exists or the token is invalid.
        """

        if rights == "access":
            # first look in the database
            r = yield self._look_up_user_by_access_token(token)
            if r:
                defer.returnValue(r)

        # otherwise it needs to be a valid macaroon
        try:
            user_id, guest = self._parse_and_validate_macaroon(token, rights)
            user = UserID.from_string(user_id)

            if rights == "access":
                if not guest:
                    # non-guest access tokens must be in the database
                    logger.warning("Unrecognised access token - not in store.")
                    raise AuthError(
                        self.TOKEN_NOT_FOUND_HTTP_STATUS,
                        "Unrecognised access token.",
                        errcode=Codes.UNKNOWN_TOKEN,
                    )

                # Guest access tokens are not stored in the database (there can
                # only be one access token per guest, anyway).
                #
                # In order to prevent guest access tokens being used as regular
                # user access tokens (and hence getting around the invalidation
                # process), we look up the user id and check that it is indeed
                # a guest user.
                #
                # It would of course be much easier to store guest access
                # tokens in the database as well, but that would break existing
                # guest tokens.
                stored_user = yield self.store.get_user_by_id(user_id)
                if not stored_user:
                    raise AuthError(self.TOKEN_NOT_FOUND_HTTP_STATUS,
                                    "Unknown user_id %s" % user_id,
                                    errcode=Codes.UNKNOWN_TOKEN)
                if not stored_user["is_guest"]:
                    raise AuthError(self.TOKEN_NOT_FOUND_HTTP_STATUS,
                                    "Guest access token used for regular user",
                                    errcode=Codes.UNKNOWN_TOKEN)
                ret = {
                    "user": user,
                    "is_guest": True,
                    "token_id": None,
                    # all guests get the same device id
                    "device_id": GUEST_DEVICE_ID,
                }
            elif rights == "delete_pusher":
                # We don't store these tokens in the database
                ret = {
                    "user": user,
                    "is_guest": False,
                    "token_id": None,
                    "device_id": None,
                }
            else:
                raise RuntimeError("Unknown rights setting %s", rights)
            defer.returnValue(ret)
        except (
                _InvalidMacaroonException,
                pymacaroons.exceptions.MacaroonException,
                TypeError,
                ValueError,
        ) as e:
            logger.warning("Invalid macaroon in auth: %s %s", type(e), e)
            raise AuthError(self.TOKEN_NOT_FOUND_HTTP_STATUS,
                            "Invalid macaroon passed.",
                            errcode=Codes.UNKNOWN_TOKEN)
Ejemplo n.º 27
0
    def handle_new_client_event(self,
                                requester,
                                event,
                                context,
                                ratelimit=True,
                                extra_users=[]):
        # We now need to go and hit out to wherever we need to hit out to.

        if ratelimit:
            yield self.ratelimit(requester)

        try:
            yield self.auth.check_from_context(event, context)
        except AuthError as err:
            logger.warn("Denying new event %r because %s", event, err)
            raise err

        # Ensure that we can round trip before trying to persist in db
        try:
            dump = ujson.dumps(event.content)
            ujson.loads(dump)
        except:
            logger.exception("Failed to encode content: %r", event.content)
            raise

        yield self.maybe_kick_guest_users(event, context)

        if event.type == EventTypes.CanonicalAlias:
            # Check the alias is acually valid (at this time at least)
            room_alias_str = event.content.get("alias", None)
            if room_alias_str:
                room_alias = RoomAlias.from_string(room_alias_str)
                directory_handler = self.hs.get_handlers().directory_handler
                mapping = yield directory_handler.get_association(room_alias)

                if mapping["room_id"] != event.room_id:
                    raise SynapseError(
                        400, "Room alias %s does not point to the room" %
                        (room_alias_str, ))

        federation_handler = self.hs.get_handlers().federation_handler

        if event.type == EventTypes.Member:
            if event.content["membership"] == Membership.INVITE:

                def is_inviter_member_event(e):
                    return (e.type == EventTypes.Member
                            and e.sender == event.sender)

                state_to_include_ids = [
                    e_id for k, e_id in context.current_state_ids.iteritems()
                    if k[0] in self.hs.config.room_invite_state_types or k == (
                        EventTypes.Member, event.sender)
                ]

                state_to_include = yield self.store.get_events(
                    state_to_include_ids)

                event.unsigned["invite_room_state"] = [{
                    "type": e.type,
                    "state_key": e.state_key,
                    "content": e.content,
                    "sender": e.sender,
                } for e in state_to_include.itervalues()]

                invitee = UserID.from_string(event.state_key)
                if not self.hs.is_mine(invitee):
                    # TODO: Can we add signature from remote server in a nicer
                    # way? If we have been invited by a remote server, we need
                    # to get them to sign the event.

                    returned_invite = yield federation_handler.send_invite(
                        invitee.domain,
                        event,
                    )

                    event.unsigned.pop("room_state", None)

                    # TODO: Make sure the signatures actually are correct.
                    event.signatures.update(returned_invite.signatures)

        if event.type == EventTypes.Redaction:
            auth_events_ids = yield self.auth.compute_auth_events(
                event,
                context.prev_state_ids,
                for_verification=True,
            )
            auth_events = yield self.store.get_events(auth_events_ids)
            auth_events = {(e.type, e.state_key): e
                           for e in auth_events.values()}
            if self.auth.check_redaction(event, auth_events=auth_events):
                original_event = yield self.store.get_event(
                    event.redacts,
                    check_redacted=False,
                    get_prev_content=False,
                    allow_rejected=False,
                    allow_none=False)
                if event.user_id != original_event.user_id:
                    raise AuthError(
                        403, "You don't have permission to redact events")

        if event.type == EventTypes.Create and context.prev_state_ids:
            raise AuthError(
                403,
                "Changing the room create event is forbidden",
            )

        yield self.action_generator.handle_push_actions_for_event(
            event, context)

        (event_stream_id,
         max_stream_id) = yield self.store.persist_event(event,
                                                         context=context)

        # this intentionally does not yield: we don't care about the result
        # and don't need to wait for it.
        preserve_fn(self.pusher_pool.on_new_notifications)(event_stream_id,
                                                           max_stream_id)

        @defer.inlineCallbacks
        def _notify():
            yield run_on_reactor()
            self.notifier.on_new_room_event(event,
                                            event_stream_id,
                                            max_stream_id,
                                            extra_users=extra_users)

        preserve_fn(_notify)()
Ejemplo n.º 28
0
    def on_POST(self, request, room_id, event_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")

        body = parse_json_object_from_request(request, allow_empty_body=True)

        delete_local_events = bool(body.get("delete_local_events", False))

        # establish the topological ordering we should keep events from. The
        # user can provide an event_id in the URL or the request body, or can
        # provide a timestamp in the request body.
        if event_id is None:
            event_id = body.get('purge_up_to_event_id')

        if event_id is not None:
            event = yield self.store.get_event(event_id)

            if event.room_id != room_id:
                raise SynapseError(400, "Event is for wrong room.")

            token = yield self.store.get_topological_token_for_event(event_id)

            logger.info(
                "[purge] purging up to token %s (event_id %s)",
                token, event_id,
            )
        elif 'purge_up_to_ts' in body:
            ts = body['purge_up_to_ts']
            if not isinstance(ts, int):
                raise SynapseError(
                    400, "purge_up_to_ts must be an int",
                    errcode=Codes.BAD_JSON,
                )

            stream_ordering = (
                yield self.store.find_first_stream_ordering_after_ts(ts)
            )

            r = (
                yield self.store.get_room_event_after_stream_ordering(
                    room_id, stream_ordering,
                )
            )
            if not r:
                logger.warn(
                    "[purge] purging events not possible: No event found "
                    "(received_ts %i => stream_ordering %i)",
                    ts, stream_ordering,
                )
                raise SynapseError(
                    404,
                    "there is no event to be purged",
                    errcode=Codes.NOT_FOUND,
                )
            (stream, topo, _event_id) = r
            token = "t%d-%d" % (topo, stream)
            logger.info(
                "[purge] purging up to token %s (received_ts %i => "
                "stream_ordering %i)",
                token, ts, stream_ordering,
            )
        else:
            raise SynapseError(
                400,
                "must specify purge_up_to_event_id or purge_up_to_ts",
                errcode=Codes.BAD_JSON,
            )

        purge_id = yield self.pagination_handler.start_purge_history(
            room_id, token,
            delete_local_events=delete_local_events,
        )

        defer.returnValue((200, {
            "purge_id": purge_id,
        }))
Ejemplo n.º 29
0
def _is_membership_change_allowed(
    room_version: RoomVersion, event: EventBase, auth_events: StateMap[EventBase]
) -> None:
    """
    Confirms that the event which changes membership is an allowed change.

    Args:
        room_version: The version of the room.
        event: The event to check.
        auth_events: The current auth events of the room.

    Raises:
        AuthError if the event is not allowed.
    """
    membership = event.content["membership"]

    # Check if this is the room creator joining:
    if len(event.prev_event_ids()) == 1 and Membership.JOIN == membership:
        # Get room creation event:
        key = (EventTypes.Create, "")
        create = auth_events.get(key)
        if create and event.prev_event_ids()[0] == create.event_id:
            if create.content["creator"] == event.state_key:
                return

    target_user_id = event.state_key

    creating_domain = get_domain_from_id(event.room_id)
    target_domain = get_domain_from_id(target_user_id)
    if creating_domain != target_domain:
        if not _can_federate(event, auth_events):
            raise AuthError(403, "This room has been marked as unfederatable.")

    # get info about the caller
    key = (EventTypes.Member, event.user_id)
    caller = auth_events.get(key)

    caller_in_room = caller and caller.membership == Membership.JOIN
    caller_invited = caller and caller.membership == Membership.INVITE

    # get info about the target
    key = (EventTypes.Member, target_user_id)
    target = auth_events.get(key)

    target_in_room = target and target.membership == Membership.JOIN
    target_banned = target and target.membership == Membership.BAN

    key = (EventTypes.JoinRules, "")
    join_rule_event = auth_events.get(key)
    if join_rule_event:
        join_rule = join_rule_event.content.get("join_rule", JoinRules.INVITE)
    else:
        join_rule = JoinRules.INVITE

    user_level = get_user_power_level(event.user_id, auth_events)
    target_level = get_user_power_level(target_user_id, auth_events)

    # FIXME (erikj): What should we do here as the default?
    ban_level = _get_named_level(auth_events, "ban", 50)

    logger.debug(
        "_is_membership_change_allowed: %s",
        {
            "caller_in_room": caller_in_room,
            "caller_invited": caller_invited,
            "target_banned": target_banned,
            "target_in_room": target_in_room,
            "membership": membership,
            "join_rule": join_rule,
            "target_user_id": target_user_id,
            "event.user_id": event.user_id,
        },
    )

    if Membership.INVITE == membership and "third_party_invite" in event.content:
        if not _verify_third_party_invite(event, auth_events):
            raise AuthError(403, "You are not invited to this room.")
        if target_banned:
            raise AuthError(403, "%s is banned from the room" % (target_user_id,))
        return

    if Membership.JOIN != membership:
        if (
            caller_invited
            and Membership.LEAVE == membership
            and target_user_id == event.user_id
        ):
            return

        if not caller_in_room:  # caller isn't joined
            raise AuthError(403, "%s not in room %s." % (event.user_id, event.room_id))

    if Membership.INVITE == membership:
        # TODO (erikj): We should probably handle this more intelligently
        # PRIVATE join rules.

        # Invites are valid iff caller is in the room and target isn't.
        if target_banned:
            raise AuthError(403, "%s is banned from the room" % (target_user_id,))
        elif target_in_room:  # the target is already in the room.
            raise AuthError(403, "%s is already in the room." % target_user_id)
        else:
            invite_level = _get_named_level(auth_events, "invite", 0)

            if user_level < invite_level:
                raise AuthError(403, "You don't have permission to invite users")
    elif Membership.JOIN == membership:
        # Joins are valid iff caller == target and:
        # * They are not banned.
        # * They are accepting a previously sent invitation.
        # * They are already joined (it's a NOOP).
        # * The room is public or restricted.
        if event.user_id != target_user_id:
            raise AuthError(403, "Cannot force another user to join.")
        elif target_banned:
            raise AuthError(403, "You are banned from this room")
        elif join_rule == JoinRules.PUBLIC or (
            room_version.msc3083_join_rules
            and join_rule == JoinRules.MSC3083_RESTRICTED
        ):
            pass
        elif join_rule == JoinRules.INVITE:
            if not caller_in_room and not caller_invited:
                raise AuthError(403, "You are not invited to this room.")
        else:
            # TODO (erikj): may_join list
            # TODO (erikj): private rooms
            raise AuthError(403, "You are not allowed to join this room")
    elif Membership.LEAVE == membership:
        # TODO (erikj): Implement kicks.
        if target_banned and user_level < ban_level:
            raise AuthError(403, "You cannot unban user %s." % (target_user_id,))
        elif target_user_id != event.user_id:
            kick_level = _get_named_level(auth_events, "kick", 50)

            if user_level < kick_level or user_level <= target_level:
                raise AuthError(403, "You cannot kick user %s." % target_user_id)
    elif Membership.BAN == membership:
        if user_level < ban_level or user_level <= target_level:
            raise AuthError(403, "You don't have permission to ban")
    else:
        raise AuthError(500, "Unknown membership %s" % membership)
Ejemplo n.º 30
0
    def persist_and_notify_client_event(
        self,
        requester,
        event,
        context,
        ratelimit=True,
        extra_users=[],
    ):
        """Called when we have fully built the event, have already
        calculated the push actions for the event, and checked auth.

        This should only be run on master.
        """
        assert not self.config.worker_app

        if ratelimit:
            yield self.base_handler.ratelimit(requester)

        yield self.base_handler.maybe_kick_guest_users(event, context)

        if event.type == EventTypes.CanonicalAlias:
            # Check the alias is acually valid (at this time at least)
            room_alias_str = event.content.get("alias", None)
            if room_alias_str:
                room_alias = RoomAlias.from_string(room_alias_str)
                directory_handler = self.hs.get_handlers().directory_handler
                mapping = yield directory_handler.get_association(room_alias)

                if mapping["room_id"] != event.room_id:
                    raise SynapseError(
                        400,
                        "Room alias %s does not point to the room" % (
                            room_alias_str,
                        )
                    )

        federation_handler = self.hs.get_handlers().federation_handler

        if event.type == EventTypes.Member:
            if event.content["membership"] == Membership.INVITE:
                def is_inviter_member_event(e):
                    return (
                        e.type == EventTypes.Member and
                        e.sender == event.sender
                    )

                current_state_ids = yield context.get_current_state_ids(self.store)

                state_to_include_ids = [
                    e_id
                    for k, e_id in iteritems(current_state_ids)
                    if k[0] in self.hs.config.room_invite_state_types
                    or k == (EventTypes.Member, event.sender)
                ]

                state_to_include = yield self.store.get_events(state_to_include_ids)

                event.unsigned["invite_room_state"] = [
                    {
                        "type": e.type,
                        "state_key": e.state_key,
                        "content": e.content,
                        "sender": e.sender,
                    }
                    for e in itervalues(state_to_include)
                ]

                invitee = UserID.from_string(event.state_key)
                if not self.hs.is_mine(invitee):
                    # TODO: Can we add signature from remote server in a nicer
                    # way? If we have been invited by a remote server, we need
                    # to get them to sign the event.

                    returned_invite = yield federation_handler.send_invite(
                        invitee.domain,
                        event,
                    )

                    event.unsigned.pop("room_state", None)

                    # TODO: Make sure the signatures actually are correct.
                    event.signatures.update(
                        returned_invite.signatures
                    )

        if event.type == EventTypes.Redaction:
            prev_state_ids = yield context.get_prev_state_ids(self.store)
            auth_events_ids = yield self.auth.compute_auth_events(
                event, prev_state_ids, for_verification=True,
            )
            auth_events = yield self.store.get_events(auth_events_ids)
            auth_events = {
                (e.type, e.state_key): e for e in auth_events.values()
            }
            room_version = yield self.store.get_room_version(event.room_id)
            if self.auth.check_redaction(room_version, event, auth_events=auth_events):
                original_event = yield self.store.get_event(
                    event.redacts,
                    check_redacted=False,
                    get_prev_content=False,
                    allow_rejected=False,
                    allow_none=False
                )
                if event.user_id != original_event.user_id:
                    raise AuthError(
                        403,
                        "You don't have permission to redact events"
                    )

                # We've already checked.
                event.internal_metadata.recheck_redaction = False

        if event.type == EventTypes.Create:
            prev_state_ids = yield context.get_prev_state_ids(self.store)
            if prev_state_ids:
                raise AuthError(
                    403,
                    "Changing the room create event is forbidden",
                )

        (event_stream_id, max_stream_id) = yield self.store.persist_event(
            event, context=context
        )

        yield self.pusher_pool.on_new_notifications(
            event_stream_id, max_stream_id,
        )

        def _notify():
            try:
                self.notifier.on_new_room_event(
                    event, event_stream_id, max_stream_id,
                    extra_users=extra_users
                )
            except Exception:
                logger.exception("Error notifying about new room event")

        run_in_background(_notify)

        if event.type == EventTypes.Message:
            # We don't want to block sending messages on any presence code. This
            # matters as sometimes presence code can take a while.
            run_in_background(self._bump_active_time, requester.user)