예제 #1
0
파일: __init__.py 프로젝트: Fnux/synapse
    def get_state_events_in_room(
        self, room_id: str, types: Iterable[Tuple[str, Optional[str]]]
    ) -> Generator[defer.Deferred, Any, Iterable[EventBase]]:
        """Gets current state events for the given room.

        (This is exposed for compatibility with the old SpamCheckerApi. We should
        probably deprecate it and replace it with an async method in a subclass.)

        Added in Synapse v1.22.0.

        Args:
            room_id: The room ID to get state events in.
            types: The event type and state key (using None
                to represent 'any') of the room state to acquire.

        Returns:
            twisted.internet.defer.Deferred[list(synapse.events.FrozenEvent)]:
                The filtered state events in the room.
        """
        state_ids = yield defer.ensureDeferred(
            self._store.get_filtered_current_state_ids(
                room_id=room_id, state_filter=StateFilter.from_types(types)))
        state = yield defer.ensureDeferred(
            self._store.get_events(state_ids.values()))
        return state.values()
예제 #2
0
파일: message.py 프로젝트: mjvaldez/synapse
    def get_room_data(self,
                      user_id=None,
                      room_id=None,
                      event_type=None,
                      state_key="",
                      is_guest=False):
        """ Get data from a room.

        Args:
            event : The room path event
        Returns:
            The path data content.
        Raises:
            SynapseError if something went wrong.
        """
        (
            membership,
            membership_event_id,
        ) = yield self.auth.check_user_in_room_or_world_readable(
            room_id, user_id, allow_departed_users=True)

        if membership == Membership.JOIN:
            data = yield self.state.get_current_state(room_id, event_type,
                                                      state_key)
        elif membership == Membership.LEAVE:
            key = (event_type, state_key)
            room_state = yield self.state_store.get_state_for_events(
                [membership_event_id], StateFilter.from_types([key]))
            data = room_state[membership_event_id].get(key)

        return data
예제 #3
0
    def is_room_world_readable_or_publicly_joinable(self, room_id):
        """Check if the room is either world_readable or publically joinable
        """

        # Create a state filter that only queries join and history state event
        types_to_filter = (
            (EventTypes.JoinRules, ""),
            (EventTypes.RoomHistoryVisibility, ""),
        )

        current_state_ids = yield self.get_filtered_current_state_ids(
            room_id, StateFilter.from_types(types_to_filter)
        )

        join_rules_id = current_state_ids.get((EventTypes.JoinRules, ""))
        if join_rules_id:
            join_rule_ev = yield self.get_event(join_rules_id, allow_none=True)
            if join_rule_ev:
                if join_rule_ev.content.get("join_rule") == JoinRules.PUBLIC:
                    defer.returnValue(True)

        hist_vis_id = current_state_ids.get((EventTypes.RoomHistoryVisibility, ""))
        if hist_vis_id:
            hist_vis_ev = yield self.get_event(hist_vis_id, allow_none=True)
            if hist_vis_ev:
                if hist_vis_ev.content.get("history_visibility") == "world_readable":
                    defer.returnValue(True)

        defer.returnValue(False)
예제 #4
0
    def get_room_data(self, user_id=None, room_id=None,
                      event_type=None, state_key="", is_guest=False):
        """ Get data from a room.

        Args:
            event : The room path event
        Returns:
            The path data content.
        Raises:
            SynapseError if something went wrong.
        """
        membership, membership_event_id = yield self.auth.check_in_room_or_world_readable(
            room_id, user_id
        )

        if membership == Membership.JOIN:
            data = yield self.state.get_current_state(
                room_id, event_type, state_key
            )
        elif membership == Membership.LEAVE:
            key = (event_type, state_key)
            room_state = yield self.store.get_state_for_events(
                [membership_event_id], StateFilter.from_types([key])
            )
            data = room_state[membership_event_id].get(key)

        defer.returnValue(data)
예제 #5
0
    def is_room_world_readable_or_publicly_joinable(self, room_id):
        """Check if the room is either world_readable or publically joinable
        """

        # Create a state filter that only queries join and history state event
        types_to_filter = (
            (EventTypes.JoinRules, ""),
            (EventTypes.RoomHistoryVisibility, ""),
        )

        current_state_ids = yield self.get_filtered_current_state_ids(
            room_id,
            StateFilter.from_types(types_to_filter),
        )

        join_rules_id = current_state_ids.get((EventTypes.JoinRules, ""))
        if join_rules_id:
            join_rule_ev = yield self.get_event(join_rules_id, allow_none=True)
            if join_rule_ev:
                if join_rule_ev.content.get("join_rule") == JoinRules.PUBLIC:
                    defer.returnValue(True)

        hist_vis_id = current_state_ids.get(
            (EventTypes.RoomHistoryVisibility, ""))
        if hist_vis_id:
            hist_vis_ev = yield self.get_event(hist_vis_id, allow_none=True)
            if hist_vis_ev:
                if hist_vis_ev.content.get(
                        "history_visibility") == "world_readable":
                    defer.returnValue(True)

        defer.returnValue(False)
예제 #6
0
파일: state.py 프로젝트: matrix-org/synapse
    async def get_current_state_event(
        self, room_id: str, event_type: str, state_key: str
    ) -> Optional[EventBase]:
        """Get the current state event for the given type/state_key."""

        key = (event_type, state_key)
        state_map = await self.get_current_state(
            room_id, StateFilter.from_types((key,))
        )
        return state_map.get(key)
예제 #7
0
파일: __init__.py 프로젝트: Fnux/synapse
    async def get_room_state(
        self,
        room_id: str,
        event_filter: Optional[Iterable[Tuple[str, Optional[str]]]] = None,
    ) -> StateMap[EventBase]:
        """Returns the current state of the given room.

        The events are returned as a mapping, in which the key for each event is a tuple
        which first element is the event's type and the second one is its state key.

        Added in Synapse v1.47.0

        Args:
            room_id: The ID of the room to get state from.
            event_filter: A filter to apply when retrieving events. None if no filter
                should be applied. If provided, must be an iterable of tuples. A tuple's
                first element is the event type and the second is the state key, or is
                None if the state key should not be filtered on.
                An example of a filter is:
                    [
                        ("m.room.member", "@alice:example.com"),  # Member event for @alice:example.com
                        ("org.matrix.some_event", ""),  # State event of type "org.matrix.some_event"
                                                        # with an empty string as its state key
                        ("org.matrix.some_other_event", None),  # State events of type "org.matrix.some_other_event"
                                                                # regardless of their state key
                    ]
        """
        if event_filter:
            # If a filter was provided, turn it into a StateFilter and retrieve a filtered
            # view of the state.
            state_filter = StateFilter.from_types(event_filter)
            state_ids = await self._store.get_filtered_current_state_ids(
                room_id,
                state_filter,
            )
        else:
            # If no filter was provided, get the whole state. We could also reuse the call
            # to get_filtered_current_state_ids above, with `state_filter = StateFilter.all()`,
            # but get_filtered_current_state_ids isn't cached and `get_current_state_ids`
            # is, so using the latter when we can is better for perf.
            state_ids = await self._store.get_current_state_ids(room_id)

        state_events = await self._store.get_events(state_ids.values())

        return {
            key: state_events[event_id]
            for key, event_id in state_ids.items()
        }
예제 #8
0
    def get_state_events_in_room(self, room_id, types):
        """Gets state events for the given room.

        Args:
            room_id (string): The room ID to get state events in.
            types (tuple): The event type and state key (using None
                to represent 'any') of the room state to acquire.

        Returns:
            twisted.internet.defer.Deferred[list(synapse.events.FrozenEvent)]:
                The filtered state events in the room.
        """
        state_ids = yield self._store.get_filtered_current_state_ids(
            room_id=room_id, state_filter=StateFilter.from_types(types))
        state = yield self._store.get_events(state_ids.values())
        return state.values()
예제 #9
0
async def _event_to_history_vis(
    storage: StorageControllers, events: Collection[EventBase]
) -> Dict[str, str]:
    """Get the history visibility at each of the given events

    Returns a map from event id to history_visibility setting
    """

    # outliers get special treatment here. We don't have the state at that point in the
    # room (and attempting to look it up will raise an exception), so all we can really
    # do is assume that the requesting server is allowed to see the event. That's
    # equivalent to there not being a history_visibility event, so we just exclude
    # any outliers from the query.
    event_to_state_ids = await storage.state.get_state_ids_for_events(
        frozenset(e.event_id for e in events if not e.internal_metadata.is_outlier()),
        state_filter=StateFilter.from_types(types=(_HISTORY_VIS_KEY,)),
    )

    visibility_ids = {
        vis_event_id
        for vis_event_id in (
            state_ids.get(_HISTORY_VIS_KEY) for state_ids in event_to_state_ids.values()
        )
        if vis_event_id
    }
    vis_events = await storage.main.get_events(visibility_ids)

    result: Dict[str, str] = {}
    for event in events:
        vis = HistoryVisibility.SHARED
        state_ids = event_to_state_ids.get(event.event_id)

        # if we didn't find any state for this event, it's an outlier, and we assume
        # it's open
        visibility_id = None
        if state_ids:
            visibility_id = state_ids.get(_HISTORY_VIS_KEY)

        if visibility_id:
            vis_event = vis_events[visibility_id]
            vis = vis_event.content.get("history_visibility", HistoryVisibility.SHARED)
            assert isinstance(vis, str)

        result[event.event_id] = vis
    return result
예제 #10
0
    def on_GET(self, request, room_id):
        # TODO support Pagination stream API (limit/tokens)
        requester = yield self.auth.get_user_by_req(request)
        handler = self.message_handler

        # request the state as of a given event, as identified by a stream token,
        # for consistency with /messages etc.
        # useful for getting the membership in retrospect as of a given /sync
        # response.
        at_token_string = parse_string(request, "at")
        if at_token_string is None:
            at_token = None
        else:
            at_token = StreamToken.from_string(at_token_string)

        # let you filter down on particular memberships.
        # XXX: this may not be the best shape for this API - we could pass in a filter
        # instead, except filters aren't currently aware of memberships.
        # See https://github.com/matrix-org/matrix-doc/issues/1337 for more details.
        membership = parse_string(request, "membership")
        not_membership = parse_string(request, "not_membership")

        events = yield handler.get_state_events(
            room_id=room_id,
            user_id=requester.user.to_string(),
            at_token=at_token,
            state_filter=StateFilter.from_types([(EventTypes.Member, None)]),
        )
        print("honey singh")
        print(events)
        chunk = []

        for event in events:
            if ((membership
                 and event['content'].get("membership") != membership) or
                (not_membership
                 and event['content'].get("membership") == not_membership)):
                continue
        #     chunk.append(event)
        # print("great")
        # room_members = yield self.message_handler.get_all_members_handler(room_id)
        # print("great 2.0")
        # print(room_members)

        defer.returnValue((200, {"chunk": chunk}))
예제 #11
0
파일: room.py 프로젝트: DoubleMalt/synapse
    def on_GET(self, request, room_id):
        # TODO support Pagination stream API (limit/tokens)
        requester = yield self.auth.get_user_by_req(request)
        handler = self.message_handler

        # request the state as of a given event, as identified by a stream token,
        # for consistency with /messages etc.
        # useful for getting the membership in retrospect as of a given /sync
        # response.
        at_token_string = parse_string(request, "at")
        if at_token_string is None:
            at_token = None
        else:
            at_token = StreamToken.from_string(at_token_string)

        # let you filter down on particular memberships.
        # XXX: this may not be the best shape for this API - we could pass in a filter
        # instead, except filters aren't currently aware of memberships.
        # See https://github.com/matrix-org/matrix-doc/issues/1337 for more details.
        membership = parse_string(request, "membership")
        not_membership = parse_string(request, "not_membership")

        events = yield handler.get_state_events(
            room_id=room_id,
            user_id=requester.user.to_string(),
            at_token=at_token,
            state_filter=StateFilter.from_types([(EventTypes.Member, None)]),
        )

        chunk = []

        for event in events:
            if (
                (membership and event['content'].get("membership") != membership) or
                (not_membership and event['content'].get("membership") == not_membership)
            ):
                continue
            chunk.append(event)

        defer.returnValue((200, {
            "chunk": chunk
        }))
예제 #12
0
    async def on_GET(self, request: SynapseRequest,
                     room_id: str) -> Tuple[int, JsonDict]:
        # TODO support Pagination stream API (limit/tokens)
        requester = await self.auth.get_user_by_req(request, allow_guest=True)
        handler = self.message_handler

        # request the state as of a given event, as identified by a stream token,
        # for consistency with /messages etc.
        # useful for getting the membership in retrospect as of a given /sync
        # response.
        at_token_string = parse_string(request, "at")
        if at_token_string is None:
            at_token = None
        else:
            at_token = await StreamToken.from_string(self.store,
                                                     at_token_string)

        # let you filter down on particular memberships.
        # XXX: this may not be the best shape for this API - we could pass in a filter
        # instead, except filters aren't currently aware of memberships.
        # See https://github.com/matrix-org/matrix-doc/issues/1337 for more details.
        membership = parse_string(request, "membership")
        not_membership = parse_string(request, "not_membership")

        events = await handler.get_state_events(
            room_id=room_id,
            user_id=requester.user.to_string(),
            at_token=at_token,
            state_filter=StateFilter.from_types([(EventTypes.Member, None)]),
        )

        chunk = []

        for event in events:
            if (membership and event["content"].get("membership") != membership
                ) or (not_membership and event["content"].get("membership")
                      == not_membership):
                continue
            chunk.append(event)

        return 200, {"chunk": chunk}
예제 #13
0
    def get_canonical_alias_for_room(self, room_id):
        """Get canonical alias for room, if any

        Args:
            room_id (str)

        Returns:
            Deferred[str|None]: The canonical alias, if any
        """

        state = yield self.get_filtered_current_state_ids(
            room_id, StateFilter.from_types([(EventTypes.CanonicalAlias, "")]))

        event_id = state.get((EventTypes.CanonicalAlias, ""))
        if not event_id:
            return

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

        return event.content.get("canonical_alias")
예제 #14
0
파일: message.py 프로젝트: vishnumg/synapse
    async def get_room_data(
        self,
        user_id: str = None,
        room_id: str = None,
        event_type: Optional[str] = None,
        state_key: str = "",
        is_guest: bool = False,
    ) -> dict:
        """ Get data from a room.

        Args:
            user_id
            room_id
            event_type
            state_key
            is_guest
        Returns:
            The path data content.
        Raises:
            SynapseError if something went wrong.
        """
        (
            membership,
            membership_event_id,
        ) = await self.auth.check_user_in_room_or_world_readable(
            room_id, user_id, allow_departed_users=True
        )

        if membership == Membership.JOIN:
            data = await self.state.get_current_state(room_id, event_type, state_key)
        elif membership == Membership.LEAVE:
            key = (event_type, state_key)
            room_state = await self.state_store.get_state_for_events(
                [membership_event_id], StateFilter.from_types([key])
            )
            data = room_state[membership_event_id].get(key)

        return data
예제 #15
0
    async def get_canonical_alias_for_room(self,
                                           room_id: str) -> Optional[str]:
        """Get canonical alias for room, if any

        Args:
            room_id: The room ID

        Returns:
            The canonical alias, if any
        """

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

        event_id = state.get((EventTypes.CanonicalAlias, ""))
        if not event_id:
            return

        event = await self.get_event(event_id, allow_none=True)
        if not event:
            return

        return event.content.get("canonical_alias")
예제 #16
0
    def clone_existing_room(
        self, requester, old_room_id, new_room_id, new_room_version, tombstone_event_id
    ):
        """Populate a new room based on an old room

        Args:
            requester (synapse.types.Requester): the user requesting the upgrade
            old_room_id (unicode): the id of the room to be replaced
            new_room_id (unicode): the id to give the new room (should already have been
                created with _gemerate_room_id())
            new_room_version (unicode): the new room version to use
            tombstone_event_id (unicode|str): the ID of the tombstone event in the old
                room.
        Returns:
            Deferred[None]
        """
        user_id = requester.user.to_string()

        if not self.spam_checker.user_may_create_room(user_id):
            raise SynapseError(403, "You are not permitted to create rooms")

        creation_content = {
            "room_version": new_room_version,
            "predecessor": {"room_id": old_room_id, "event_id": tombstone_event_id},
        }

        # Check if old room was non-federatable

        # Get old room's create event
        old_room_create_event = yield self.store.get_create_event_for_room(old_room_id)

        # Check if the create event specified a non-federatable room
        if not old_room_create_event.content.get("m.federate", True):
            # If so, mark the new room as non-federatable as well
            creation_content["m.federate"] = False

        initial_state = dict()

        # Replicate relevant room events
        types_to_copy = (
            (EventTypes.JoinRules, ""),
            (EventTypes.Name, ""),
            (EventTypes.Topic, ""),
            (EventTypes.RoomHistoryVisibility, ""),
            (EventTypes.GuestAccess, ""),
            (EventTypes.RoomAvatar, ""),
            (EventTypes.Encryption, ""),
            (EventTypes.ServerACL, ""),
            (EventTypes.RelatedGroups, ""),
        )

        old_room_state_ids = yield self.store.get_filtered_current_state_ids(
            old_room_id, StateFilter.from_types(types_to_copy)
        )
        # map from event_id to BaseEvent
        old_room_state_events = yield self.store.get_events(old_room_state_ids.values())

        for k, old_event_id in iteritems(old_room_state_ids):
            old_event = old_room_state_events.get(old_event_id)
            if old_event:
                initial_state[k] = old_event.content

        yield self._send_events_for_new_room(
            requester,
            new_room_id,
            # we expect to override all the presets with initial_state, so this is
            # somewhat arbitrary.
            preset_config=RoomCreationPreset.PRIVATE_CHAT,
            invite_list=[],
            initial_state=initial_state,
            creation_content=creation_content,
        )

        # Transfer membership events
        old_room_member_state_ids = yield self.store.get_filtered_current_state_ids(
            old_room_id, StateFilter.from_types([(EventTypes.Member, None)])
        )

        # map from event_id to BaseEvent
        old_room_member_state_events = yield self.store.get_events(
            old_room_member_state_ids.values()
        )
        for k, old_event in iteritems(old_room_member_state_events):
            # Only transfer ban events
            if (
                "membership" in old_event.content
                and old_event.content["membership"] == "ban"
            ):
                yield self.room_member_handler.update_membership(
                    requester,
                    UserID.from_string(old_event["state_key"]),
                    new_room_id,
                    "ban",
                    ratelimit=False,
                    content=old_event.content,
                )
예제 #17
0
    def get_messages(self, requester, room_id=None, pagin_config=None,
                     as_client_event=True, event_filter=None):
        """Get messages in a room.

        Args:
            requester (Requester): The user requesting messages.
            room_id (str): The room they want messages from.
            pagin_config (synapse.api.streams.PaginationConfig): The pagination
                config rules to apply, if any.
            as_client_event (bool): True to get events in client-server format.
            event_filter (Filter): Filter to apply to results or None
        Returns:
            dict: Pagination API results
        """
        user_id = requester.user.to_string()

        if pagin_config.from_token:
            room_token = pagin_config.from_token.room_key
        else:
            pagin_config.from_token = (
                yield self.hs.get_event_sources().get_current_token_for_room(
                    room_id=room_id
                )
            )
            room_token = pagin_config.from_token.room_key

        room_token = RoomStreamToken.parse(room_token)

        pagin_config.from_token = pagin_config.from_token.copy_and_replace(
            "room_key", str(room_token)
        )

        source_config = pagin_config.get_source_config("room")

        with (yield self.pagination_lock.read(room_id)):
            membership, member_event_id = yield self.auth.check_in_room_or_world_readable(
                room_id, user_id
            )

            if source_config.direction == 'b':
                # if we're going backwards, we might need to backfill. This
                # requires that we have a topo token.
                if room_token.topological:
                    max_topo = room_token.topological
                else:
                    max_topo = yield self.store.get_max_topological_token(
                        room_id, room_token.stream
                    )

                if membership == Membership.LEAVE:
                    # If they have left the room then clamp the token to be before
                    # they left the room, to save the effort of loading from the
                    # database.
                    leave_token = yield self.store.get_topological_token_for_event(
                        member_event_id
                    )
                    leave_token = RoomStreamToken.parse(leave_token)
                    if leave_token.topological < max_topo:
                        source_config.from_key = str(leave_token)

                yield self.hs.get_handlers().federation_handler.maybe_backfill(
                    room_id, max_topo
                )

            events, next_key = yield self.store.paginate_room_events(
                room_id=room_id,
                from_key=source_config.from_key,
                to_key=source_config.to_key,
                direction=source_config.direction,
                limit=source_config.limit,
                event_filter=event_filter,
            )

            next_token = pagin_config.from_token.copy_and_replace(
                "room_key", next_key
            )

        if events:
            if event_filter:
                events = event_filter.filter(events)

            events = yield filter_events_for_client(
                self.store,
                user_id,
                events,
                is_peeking=(member_event_id is None),
            )

        if not events:
            defer.returnValue({
                "chunk": [],
                "start": pagin_config.from_token.to_string(),
                "end": next_token.to_string(),
            })

        state = None
        if event_filter and event_filter.lazy_load_members():
            # TODO: remove redundant members

            # FIXME: we also care about invite targets etc.
            state_filter = StateFilter.from_types(
                (EventTypes.Member, event.sender)
                for event in events
            )

            state_ids = yield self.store.get_state_ids_for_event(
                events[0].event_id, state_filter=state_filter,
            )

            if state_ids:
                state = yield self.store.get_events(list(state_ids.values()))
                state = state.values()

        time_now = self.clock.time_msec()

        chunk = {
            "chunk": [
                serialize_event(e, time_now, as_client_event)
                for e in events
            ],
            "start": pagin_config.from_token.to_string(),
            "end": next_token.to_string(),
        }

        if state:
            chunk["state"] = [
                serialize_event(e, time_now, as_client_event)
                for e in state
            ]

        defer.returnValue(chunk)
예제 #18
0
파일: search.py 프로젝트: samuelyi/synapse
    async def _calculate_event_contexts(
        self,
        user: UserID,
        allowed_events: List[EventBase],
        before_limit: int,
        after_limit: int,
        include_profile: bool,
    ) -> Dict[str, JsonDict]:
        """
        Calculates the contextual events for any search results.

        Args:
            user: The user performing the search.
            allowed_events: The search results.
            before_limit:
                The number of events before a result to include as context.
            after_limit:
                The number of events after a result to include as context.
            include_profile: True if historical profile information should be
                included in the event context.

        Returns:
            A map of event ID to contextual information.
        """
        now_token = self.hs.get_event_sources().get_current_token()

        contexts = {}
        for event in allowed_events:
            res = await self.store.get_events_around(event.room_id,
                                                     event.event_id,
                                                     before_limit, after_limit)

            logger.info(
                "Context for search returned %d and %d events",
                len(res.events_before),
                len(res.events_after),
            )

            events_before = await filter_events_for_client(
                self._storage_controllers, user.to_string(), res.events_before)

            events_after = await filter_events_for_client(
                self._storage_controllers, user.to_string(), res.events_after)

            context: JsonDict = {
                "events_before":
                events_before,
                "events_after":
                events_after,
                "start":
                await
                now_token.copy_and_replace(StreamKeyType.ROOM,
                                           res.start).to_string(self.store),
                "end":
                await
                now_token.copy_and_replace(StreamKeyType.ROOM,
                                           res.end).to_string(self.store),
            }

            if include_profile:
                senders = {
                    ev.sender
                    for ev in itertools.chain(events_before, [event],
                                              events_after)
                }

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

                state_filter = StateFilter.from_types([
                    (EventTypes.Member, sender) for sender in senders
                ])

                state = await self._state_storage_controller.get_state_for_event(
                    last_event_id, state_filter)

                context["profile_info"] = {
                    s.state_key: {
                        "displayname": s.content.get("displayname", None),
                        "avatar_url": s.content.get("avatar_url", None),
                    }
                    for s in state.values()
                    if s.type == EventTypes.Member and s.state_key in senders
                }

            contexts[event.event_id] = context

        return contexts
예제 #19
0
def filter_events_for_client(store,
                             user_id,
                             events,
                             is_peeking=False,
                             always_include_ids=frozenset()):
    """
    Check which events a user is allowed to see

    Args:
        store (synapse.storage.DataStore): our datastore (can also be a worker
            store)
        user_id(str): user id to be checked
        events(list[synapse.events.EventBase]): sequence of events to be checked
        is_peeking(bool): should be True if:
          * the user is not currently a member of the room, and:
          * the user has not been a member of the room since the given
            events
        always_include_ids (set(event_id)): set of event ids to specifically
            include (unless sender is ignored)

    Returns:
        Deferred[list[synapse.events.EventBase]]
    """
    # Filter out events that have been soft failed so that we don't relay them
    # to clients.
    events = list(e for e in events
                  if not e.internal_metadata.is_soft_failed())

    types = ((EventTypes.RoomHistoryVisibility, ""), (EventTypes.Member,
                                                      user_id))
    event_id_to_state = yield store.get_state_for_events(
        frozenset(e.event_id for e in events),
        state_filter=StateFilter.from_types(types),
    )

    ignore_dict_content = yield store.get_global_account_data_by_type_for_user(
        "m.ignored_user_list", user_id)

    # FIXME: This will explode if people upload something incorrect.
    ignore_list = frozenset(
        ignore_dict_content.get("ignored_users", {}).keys(
        ) if ignore_dict_content else [])

    erased_senders = yield store.are_users_erased((e.sender for e in events))

    def allowed(event):
        """
        Args:
            event (synapse.events.EventBase): event to check

        Returns:
            None|EventBase:
               None if the user cannot see this event at all

               a redacted copy of the event if they can only see a redacted
               version

               the original event if they can see it as normal.
        """
        if not event.is_state() and event.sender in ignore_list:
            return None

        if event.event_id in always_include_ids:
            return event

        state = event_id_to_state[event.event_id]

        # get the room_visibility at the time of the event.
        visibility_event = state.get((EventTypes.RoomHistoryVisibility, ""),
                                     None)
        if visibility_event:
            visibility = visibility_event.content.get("history_visibility",
                                                      "shared")
        else:
            visibility = "shared"

        if visibility not in VISIBILITY_PRIORITY:
            visibility = "shared"

        # Always allow history visibility events on boundaries. This is done
        # by setting the effective visibility to the least restrictive
        # of the old vs new.
        if event.type == EventTypes.RoomHistoryVisibility:
            prev_content = event.unsigned.get("prev_content", {})
            prev_visibility = prev_content.get("history_visibility", None)

            if prev_visibility not in VISIBILITY_PRIORITY:
                prev_visibility = "shared"

            new_priority = VISIBILITY_PRIORITY.index(visibility)
            old_priority = VISIBILITY_PRIORITY.index(prev_visibility)
            if old_priority < new_priority:
                visibility = prev_visibility

        # likewise, if the event is the user's own membership event, use
        # the 'most joined' membership
        membership = None
        if event.type == EventTypes.Member and event.state_key == user_id:
            membership = event.content.get("membership", None)
            if membership not in MEMBERSHIP_PRIORITY:
                membership = "leave"

            prev_content = event.unsigned.get("prev_content", {})
            prev_membership = prev_content.get("membership", None)
            if prev_membership not in MEMBERSHIP_PRIORITY:
                prev_membership = "leave"

            # Always allow the user to see their own leave events, otherwise
            # they won't see the room disappear if they reject the invite
            if membership == "leave" and (prev_membership == "join"
                                          or prev_membership == "invite"):
                return event

            new_priority = MEMBERSHIP_PRIORITY.index(membership)
            old_priority = MEMBERSHIP_PRIORITY.index(prev_membership)
            if old_priority < new_priority:
                membership = prev_membership

        # otherwise, get the user's membership at the time of the event.
        if membership is None:
            membership_event = state.get((EventTypes.Member, user_id), None)
            if membership_event:
                membership = membership_event.membership

        # if the user was a member of the room at the time of the event,
        # they can see it.
        if membership == Membership.JOIN:
            return event

        # otherwise, it depends on the room visibility.

        if visibility == "joined":
            # we weren't a member at the time of the event, so we can't
            # see this event.
            return None

        elif visibility == "invited":
            # user can also see the event if they were *invited* at the time
            # of the event.
            return event if membership == Membership.INVITE else None

        elif visibility == "shared" and is_peeking:
            # if the visibility is shared, users cannot see the event unless
            # they have *subequently* joined the room (or were members at the
            # time, of course)
            #
            # XXX: if the user has subsequently joined and then left again,
            # ideally we would share history up to the point they left. But
            # we don't know when they left. We just treat it as though they
            # never joined, and restrict access.
            return None

        # the visibility is either shared or world_readable, and the user was
        # not a member at the time. We allow it, provided the original sender
        # has not requested their data to be erased, in which case, we return
        # a redacted version.
        if erased_senders[event.sender]:
            return prune_event(event)

        return event

    # check each event: gives an iterable[None|EventBase]
    filtered_events = map(allowed, events)

    # remove the None entries
    filtered_events = filter(operator.truth, filtered_events)

    # we turn it into a list before returning it.
    return list(filtered_events)
예제 #20
0
파일: register.py 프로젝트: xiu/synapse
    async def _join_rooms(self, user_id: str):
        """
        Join or invite the user to the auto-join rooms.

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

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

            try:
                room_alias = RoomAlias.from_string(r)

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

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

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

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

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

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

            except ConsentNotGivenError as e:
                # Technically not necessary to pull out this error though
                # moving away from bare excepts is a good thing to do.
                logger.error("Failed to join new user to %r: %r", r, e)
            except Exception as e:
                logger.error("Failed to join new user to %r: %r", r, e)
예제 #21
0
async def filter_events_for_server(
    storage: Storage,
    server_name,
    events,
    redact=True,
    check_history_visibility_only=False,
):
    """Filter a list of events based on whether given server is allowed to
    see them.

    Args:
        storage
        server_name (str)
        events (iterable[FrozenEvent])
        redact (bool): Whether to return a redacted version of the event, or
            to filter them out entirely.
        check_history_visibility_only (bool): Whether to only check the
            history visibility, rather than things like if the sender has been
            erased. This is used e.g. during pagination to decide whether to
            backfill or not.

    Returns
        list[FrozenEvent]
    """

    def is_sender_erased(event, erased_senders):
        if erased_senders and erased_senders[event.sender]:
            logger.info("Sender of %s has been erased, redacting", event.event_id)
            return True
        return False

    def check_event_is_visible(event, state):
        history = state.get((EventTypes.RoomHistoryVisibility, ""), None)
        if history:
            visibility = history.content.get(
                "history_visibility", HistoryVisibility.SHARED
            )
            if visibility in [HistoryVisibility.INVITED, HistoryVisibility.JOINED]:
                # We now loop through all state events looking for
                # membership states for the requesting server to determine
                # if the server is either in the room or has been invited
                # into the room.
                for ev in state.values():
                    if ev.type != EventTypes.Member:
                        continue
                    try:
                        domain = get_domain_from_id(ev.state_key)
                    except Exception:
                        continue

                    if domain != server_name:
                        continue

                    memtype = ev.membership
                    if memtype == Membership.JOIN:
                        return True
                    elif memtype == Membership.INVITE:
                        if visibility == HistoryVisibility.INVITED:
                            return True
                else:
                    # server has no users in the room: redact
                    return False

        return True

    # Lets check to see if all the events have a history visibility
    # of "shared" or "world_readable". If that's the case then we don't
    # need to check membership (as we know the server is in the room).
    event_to_state_ids = await storage.state.get_state_ids_for_events(
        frozenset(e.event_id for e in events),
        state_filter=StateFilter.from_types(
            types=((EventTypes.RoomHistoryVisibility, ""),)
        ),
    )

    visibility_ids = set()
    for sids in event_to_state_ids.values():
        hist = sids.get((EventTypes.RoomHistoryVisibility, ""))
        if hist:
            visibility_ids.add(hist)

    # If we failed to find any history visibility events then the default
    # is "shared" visibility.
    if not visibility_ids:
        all_open = True
    else:
        event_map = await storage.main.get_events(visibility_ids)
        all_open = all(
            e.content.get("history_visibility")
            in (None, HistoryVisibility.SHARED, HistoryVisibility.WORLD_READABLE)
            for e in event_map.values()
        )

    if not check_history_visibility_only:
        erased_senders = await storage.main.are_users_erased((e.sender for e in events))
    else:
        # We don't want to check whether users are erased, which is equivalent
        # to no users having been erased.
        erased_senders = {}

    if all_open:
        # all the history_visibility state affecting these events is open, so
        # we don't need to filter by membership state. We *do* need to check
        # for user erasure, though.
        if erased_senders:
            to_return = []
            for e in events:
                if not is_sender_erased(e, erased_senders):
                    to_return.append(e)
                elif redact:
                    to_return.append(prune_event(e))

            return to_return

        # If there are no erased users then we can just return the given list
        # of events without having to copy it.
        return events

    # Ok, so we're dealing with events that have non-trivial visibility
    # rules, so we need to also get the memberships of the room.

    # first, for each event we're wanting to return, get the event_ids
    # of the history vis and membership state at those events.
    event_to_state_ids = await storage.state.get_state_ids_for_events(
        frozenset(e.event_id for e in events),
        state_filter=StateFilter.from_types(
            types=((EventTypes.RoomHistoryVisibility, ""), (EventTypes.Member, None))
        ),
    )

    # We only want to pull out member events that correspond to the
    # server's domain.
    #
    # event_to_state_ids contains lots of duplicates, so it turns out to be
    # cheaper to build a complete event_id => (type, state_key) dict, and then
    # filter out the ones we don't want
    #
    event_id_to_state_key = {
        event_id: key
        for key_to_eid in event_to_state_ids.values()
        for key, event_id in key_to_eid.items()
    }

    def include(typ, state_key):
        if typ != EventTypes.Member:
            return True

        # we avoid using get_domain_from_id here for efficiency.
        idx = state_key.find(":")
        if idx == -1:
            return False
        return state_key[idx + 1 :] == server_name

    event_map = await storage.main.get_events(
        [e_id for e_id, key in event_id_to_state_key.items() if include(key[0], key[1])]
    )

    event_to_state = {
        e_id: {
            key: event_map[inner_e_id]
            for key, inner_e_id in key_to_eid.items()
            if inner_e_id in event_map
        }
        for e_id, key_to_eid in event_to_state_ids.items()
    }

    to_return = []
    for e in events:
        erased = is_sender_erased(e, erased_senders)
        visible = check_event_is_visible(e, event_to_state[e.event_id])
        if visible and not erased:
            to_return.append(e)
        elif redact:
            to_return.append(prune_event(e))

    return to_return
예제 #22
0
def filter_events_for_client(store, user_id, events, is_peeking=False,
                             always_include_ids=frozenset()):
    """
    Check which events a user is allowed to see

    Args:
        store (synapse.storage.DataStore): our datastore (can also be a worker
            store)
        user_id(str): user id to be checked
        events(list[synapse.events.EventBase]): sequence of events to be checked
        is_peeking(bool): should be True if:
          * the user is not currently a member of the room, and:
          * the user has not been a member of the room since the given
            events
        always_include_ids (set(event_id)): set of event ids to specifically
            include (unless sender is ignored)

    Returns:
        Deferred[list[synapse.events.EventBase]]
    """
    # Filter out events that have been soft failed so that we don't relay them
    # to clients.
    events = list(e for e in events if not e.internal_metadata.is_soft_failed())

    types = (
        (EventTypes.RoomHistoryVisibility, ""),
        (EventTypes.Member, user_id),
    )
    event_id_to_state = yield store.get_state_for_events(
        frozenset(e.event_id for e in events),
        state_filter=StateFilter.from_types(types),
    )

    ignore_dict_content = yield store.get_global_account_data_by_type_for_user(
        "m.ignored_user_list", user_id,
    )

    # FIXME: This will explode if people upload something incorrect.
    ignore_list = frozenset(
        ignore_dict_content.get("ignored_users", {}).keys()
        if ignore_dict_content else []
    )

    erased_senders = yield store.are_users_erased((e.sender for e in events))

    def allowed(event):
        """
        Args:
            event (synapse.events.EventBase): event to check

        Returns:
            None|EventBase:
               None if the user cannot see this event at all

               a redacted copy of the event if they can only see a redacted
               version

               the original event if they can see it as normal.
        """
        if not event.is_state() and event.sender in ignore_list:
            return None

        if event.event_id in always_include_ids:
            return event

        state = event_id_to_state[event.event_id]

        # get the room_visibility at the time of the event.
        visibility_event = state.get((EventTypes.RoomHistoryVisibility, ""), None)
        if visibility_event:
            visibility = visibility_event.content.get("history_visibility", "shared")
        else:
            visibility = "shared"

        if visibility not in VISIBILITY_PRIORITY:
            visibility = "shared"

        # Always allow history visibility events on boundaries. This is done
        # by setting the effective visibility to the least restrictive
        # of the old vs new.
        if event.type == EventTypes.RoomHistoryVisibility:
            prev_content = event.unsigned.get("prev_content", {})
            prev_visibility = prev_content.get("history_visibility", None)

            if prev_visibility not in VISIBILITY_PRIORITY:
                prev_visibility = "shared"

            new_priority = VISIBILITY_PRIORITY.index(visibility)
            old_priority = VISIBILITY_PRIORITY.index(prev_visibility)
            if old_priority < new_priority:
                visibility = prev_visibility

        # likewise, if the event is the user's own membership event, use
        # the 'most joined' membership
        membership = None
        if event.type == EventTypes.Member and event.state_key == user_id:
            membership = event.content.get("membership", None)
            if membership not in MEMBERSHIP_PRIORITY:
                membership = "leave"

            prev_content = event.unsigned.get("prev_content", {})
            prev_membership = prev_content.get("membership", None)
            if prev_membership not in MEMBERSHIP_PRIORITY:
                prev_membership = "leave"

            # Always allow the user to see their own leave events, otherwise
            # they won't see the room disappear if they reject the invite
            if membership == "leave" and (
                prev_membership == "join" or prev_membership == "invite"
            ):
                return event

            new_priority = MEMBERSHIP_PRIORITY.index(membership)
            old_priority = MEMBERSHIP_PRIORITY.index(prev_membership)
            if old_priority < new_priority:
                membership = prev_membership

        # otherwise, get the user's membership at the time of the event.
        if membership is None:
            membership_event = state.get((EventTypes.Member, user_id), None)
            if membership_event:
                membership = membership_event.membership

        # if the user was a member of the room at the time of the event,
        # they can see it.
        if membership == Membership.JOIN:
            return event

        # otherwise, it depends on the room visibility.

        if visibility == "joined":
            # we weren't a member at the time of the event, so we can't
            # see this event.
            return None

        elif visibility == "invited":
            # user can also see the event if they were *invited* at the time
            # of the event.
            return (
                event if membership == Membership.INVITE else None
            )

        elif visibility == "shared" and is_peeking:
            # if the visibility is shared, users cannot see the event unless
            # they have *subequently* joined the room (or were members at the
            # time, of course)
            #
            # XXX: if the user has subsequently joined and then left again,
            # ideally we would share history up to the point they left. But
            # we don't know when they left. We just treat it as though they
            # never joined, and restrict access.
            return None

        # the visibility is either shared or world_readable, and the user was
        # not a member at the time. We allow it, provided the original sender
        # has not requested their data to be erased, in which case, we return
        # a redacted version.
        if erased_senders[event.sender]:
            return prune_event(event)

        return event

    # check each event: gives an iterable[None|EventBase]
    filtered_events = map(allowed, events)

    # remove the None entries
    filtered_events = filter(operator.truth, filtered_events)

    # we turn it into a list before returning it.
    defer.returnValue(list(filtered_events))
예제 #23
0
    def search(self, user, content, batch=None):
        """Performs a full text search for a user.

        Args:
            user (UserID)
            content (dict): Search parameters
            batch (str): The next_batch parameter. Used for pagination.

        Returns:
            dict to be returned to the client with results of search
        """

        if not self.hs.config.enable_search:
            raise SynapseError(400, "Search is disabled on this homeserver")

        batch_group = None
        batch_group_key = None
        batch_token = None
        if batch:
            try:
                b = decode_base64(batch).decode('ascii')
                batch_group, batch_group_key, batch_token = b.split("\n")

                assert batch_group is not None
                assert batch_group_key is not None
                assert batch_token is not None
            except Exception:
                raise SynapseError(400, "Invalid batch")

        logger.info(
            "Search batch properties: %r, %r, %r",
            batch_group, batch_group_key, batch_token,
        )

        logger.info("Search content: %s", content)

        try:
            room_cat = content["search_categories"]["room_events"]

            # The actual thing to query in FTS
            search_term = room_cat["search_term"]

            # Which "keys" to search over in FTS query
            keys = room_cat.get("keys", [
                "content.body", "content.name", "content.topic",
            ])

            # Filter to apply to results
            filter_dict = room_cat.get("filter", {})

            # What to order results by (impacts whether pagination can be doen)
            order_by = room_cat.get("order_by", "rank")

            # Return the current state of the rooms?
            include_state = room_cat.get("include_state", False)

            # Include context around each event?
            event_context = room_cat.get(
                "event_context", None
            )

            # Group results together? May allow clients to paginate within a
            # group
            group_by = room_cat.get("groupings", {}).get("group_by", {})
            group_keys = [g["key"] for g in group_by]

            if event_context is not None:
                before_limit = int(event_context.get(
                    "before_limit", 5
                ))
                after_limit = int(event_context.get(
                    "after_limit", 5
                ))

                # Return the historic display name and avatar for the senders
                # of the events?
                include_profile = bool(event_context.get("include_profile", False))
        except KeyError:
            raise SynapseError(400, "Invalid search query")

        if order_by not in ("rank", "recent"):
            raise SynapseError(400, "Invalid order by: %r" % (order_by,))

        if set(group_keys) - {"room_id", "sender"}:
            raise SynapseError(
                400,
                "Invalid group by keys: %r" % (set(group_keys) - {"room_id", "sender"},)
            )

        search_filter = Filter(filter_dict)

        # TODO: Search through left rooms too
        rooms = yield self.store.get_rooms_for_user_where_membership_is(
            user.to_string(),
            membership_list=[Membership.JOIN],
            # membership_list=[Membership.JOIN, Membership.LEAVE, Membership.Ban],
        )
        room_ids = set(r.room_id for r in rooms)

        # If doing a subset of all rooms seearch, check if any of the rooms
        # are from an upgraded room, and search their contents as well
        if search_filter.rooms:
            historical_room_ids = []
            for room_id in search_filter.rooms:
                # Add any previous rooms to the search if they exist
                ids = yield self.get_old_rooms_from_upgraded_room(room_id)
                historical_room_ids += ids

            # Prevent any historical events from being filtered
            search_filter = search_filter.with_room_ids(historical_room_ids)

        room_ids = search_filter.filter_rooms(room_ids)

        if batch_group == "room_id":
            room_ids.intersection_update({batch_group_key})

        if not room_ids:
            defer.returnValue({
                "search_categories": {
                    "room_events": {
                        "results": [],
                        "count": 0,
                        "highlights": [],
                    }
                }
            })

        rank_map = {}  # event_id -> rank of event
        allowed_events = []
        room_groups = {}  # Holds result of grouping by room, if applicable
        sender_group = {}  # Holds result of grouping by sender, if applicable

        # Holds the next_batch for the entire result set if one of those exists
        global_next_batch = None

        highlights = set()

        count = None

        if order_by == "rank":
            search_result = yield self.store.search_msgs(
                room_ids, search_term, keys
            )

            count = search_result["count"]

            if search_result["highlights"]:
                highlights.update(search_result["highlights"])

            results = search_result["results"]

            results_map = {r["event"].event_id: r for r in results}

            rank_map.update({r["event"].event_id: r["rank"] for r in results})

            filtered_events = search_filter.filter([r["event"] for r in results])

            events = yield filter_events_for_client(
                self.store, user.to_string(), filtered_events
            )

            events.sort(key=lambda e: -rank_map[e.event_id])
            allowed_events = events[:search_filter.limit()]

            for e in allowed_events:
                rm = room_groups.setdefault(e.room_id, {
                    "results": [],
                    "order": rank_map[e.event_id],
                })
                rm["results"].append(e.event_id)

                s = sender_group.setdefault(e.sender, {
                    "results": [],
                    "order": rank_map[e.event_id],
                })
                s["results"].append(e.event_id)

        elif order_by == "recent":
            room_events = []
            i = 0

            pagination_token = batch_token

            # We keep looping and we keep filtering until we reach the limit
            # or we run out of things.
            # But only go around 5 times since otherwise synapse will be sad.
            while len(room_events) < search_filter.limit() and i < 5:
                i += 1
                search_result = yield self.store.search_rooms(
                    room_ids, search_term, keys, search_filter.limit() * 2,
                    pagination_token=pagination_token,
                )

                if search_result["highlights"]:
                    highlights.update(search_result["highlights"])

                count = search_result["count"]

                results = search_result["results"]

                results_map = {r["event"].event_id: r for r in results}

                rank_map.update({r["event"].event_id: r["rank"] for r in results})

                filtered_events = search_filter.filter([
                    r["event"] for r in results
                ])

                events = yield filter_events_for_client(
                    self.store, user.to_string(), filtered_events
                )

                room_events.extend(events)
                room_events = room_events[:search_filter.limit()]

                if len(results) < search_filter.limit() * 2:
                    pagination_token = None
                    break
                else:
                    pagination_token = results[-1]["pagination_token"]

            for event in room_events:
                group = room_groups.setdefault(event.room_id, {
                    "results": [],
                })
                group["results"].append(event.event_id)

            if room_events and len(room_events) >= search_filter.limit():
                last_event_id = room_events[-1].event_id
                pagination_token = results_map[last_event_id]["pagination_token"]

                # We want to respect the given batch group and group keys so
                # that if people blindly use the top level `next_batch` token
                # it returns more from the same group (if applicable) rather
                # than reverting to searching all results again.
                if batch_group and batch_group_key:
                    global_next_batch = encode_base64(("%s\n%s\n%s" % (
                        batch_group, batch_group_key, pagination_token
                    )).encode('ascii'))
                else:
                    global_next_batch = encode_base64(("%s\n%s\n%s" % (
                        "all", "", pagination_token
                    )).encode('ascii'))

                for room_id, group in room_groups.items():
                    group["next_batch"] = encode_base64(("%s\n%s\n%s" % (
                        "room_id", room_id, pagination_token
                    )).encode('ascii'))

            allowed_events.extend(room_events)

        else:
            # We should never get here due to the guard earlier.
            raise NotImplementedError()

        logger.info("Found %d events to return", len(allowed_events))

        # If client has asked for "context" for each event (i.e. some surrounding
        # events and state), fetch that
        if event_context is not None:
            now_token = yield self.hs.get_event_sources().get_current_token()

            contexts = {}
            for event in allowed_events:
                res = yield self.store.get_events_around(
                    event.room_id, event.event_id, before_limit, after_limit,
                )

                logger.info(
                    "Context for search returned %d and %d events",
                    len(res["events_before"]), len(res["events_after"]),
                )

                res["events_before"] = yield filter_events_for_client(
                    self.store, user.to_string(), res["events_before"]
                )

                res["events_after"] = yield filter_events_for_client(
                    self.store, user.to_string(), res["events_after"]
                )

                res["start"] = now_token.copy_and_replace(
                    "room_key", res["start"]
                ).to_string()

                res["end"] = now_token.copy_and_replace(
                    "room_key", res["end"]
                ).to_string()

                if include_profile:
                    senders = set(
                        ev.sender
                        for ev in itertools.chain(
                            res["events_before"], [event], res["events_after"]
                        )
                    )

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

                    state_filter = StateFilter.from_types(
                        [(EventTypes.Member, sender) for sender in senders]
                    )

                    state = yield self.store.get_state_for_event(
                        last_event_id, state_filter
                    )

                    res["profile_info"] = {
                        s.state_key: {
                            "displayname": s.content.get("displayname", None),
                            "avatar_url": s.content.get("avatar_url", None),
                        }
                        for s in state.values()
                        if s.type == EventTypes.Member and s.state_key in senders
                    }

                contexts[event.event_id] = res
        else:
            contexts = {}

        # TODO: Add a limit

        time_now = self.clock.time_msec()

        for context in contexts.values():
            context["events_before"] = (
                yield self._event_serializer.serialize_events(
                    context["events_before"], time_now,
                )
            )
            context["events_after"] = (
                yield self._event_serializer.serialize_events(
                    context["events_after"], time_now,
                )
            )

        state_results = {}
        if include_state:
            rooms = set(e.room_id for e in allowed_events)
            for room_id in rooms:
                state = yield self.state_handler.get_current_state(room_id)
                state_results[room_id] = list(state.values())

            state_results.values()

        # We're now about to serialize the events. We should not make any
        # blocking calls after this. Otherwise the 'age' will be wrong

        results = []
        for e in allowed_events:
            results.append({
                "rank": rank_map[e.event_id],
                "result": (yield self._event_serializer.serialize_event(e, time_now)),
                "context": contexts.get(e.event_id, {}),
            })

        rooms_cat_res = {
            "results": results,
            "count": count,
            "highlights": list(highlights),
        }

        if state_results:
            s = {}
            for room_id, state in state_results.items():
                s[room_id] = yield self._event_serializer.serialize_events(
                    state, time_now,
                )

            rooms_cat_res["state"] = s

        if room_groups and "room_id" in group_keys:
            rooms_cat_res.setdefault("groups", {})["room_id"] = room_groups

        if sender_group and "sender" in group_keys:
            rooms_cat_res.setdefault("groups", {})["sender"] = sender_group

        if global_next_batch:
            rooms_cat_res["next_batch"] = global_next_batch

        defer.returnValue({
            "search_categories": {
                "room_events": rooms_cat_res
            }
        })
예제 #24
0
async def _event_to_memberships(
    storage: StorageControllers, events: Collection[EventBase], server_name: str
) -> Dict[str, StateMap[EventBase]]:
    """Get the remote membership list at each of the given events

    Returns a map from event id to state map, which will contain only membership events
    for the given server.
    """

    if not events:
        return {}

    # for each event, get the event_ids of the membership state at those events.
    #
    # TODO: this means that we request the entire membership list. If there  are only
    #   one or two users on this server, and the room is huge, this is very wasteful
    #   (it means more db work, and churns the *stateGroupMembersCache*).
    #   It might be that we could extend StateFilter to specify "give me keys matching
    #   *:<server_name>", to avoid this.

    event_to_state_ids = await storage.state.get_state_ids_for_events(
        frozenset(e.event_id for e in events),
        state_filter=StateFilter.from_types(types=((EventTypes.Member, None),)),
    )

    # We only want to pull out member events that correspond to the
    # server's domain.
    #
    # event_to_state_ids contains lots of duplicates, so it turns out to be
    # cheaper to build a complete event_id => (type, state_key) dict, and then
    # filter out the ones we don't want
    #
    event_id_to_state_key = {
        event_id: key
        for key_to_eid in event_to_state_ids.values()
        for key, event_id in key_to_eid.items()
    }

    def include(state_key: str) -> bool:
        # we avoid using get_domain_from_id here for efficiency.
        idx = state_key.find(":")
        if idx == -1:
            return False
        return state_key[idx + 1 :] == server_name

    event_map = await storage.main.get_events(
        [
            e_id
            for e_id, (_, state_key) in event_id_to_state_key.items()
            if include(state_key)
        ]
    )

    return {
        e_id: {
            key: event_map[inner_e_id]
            for key, inner_e_id in key_to_eid.items()
            if inner_e_id in event_map
        }
        for e_id, key_to_eid in event_to_state_ids.items()
    }
예제 #25
0
async def filter_events_for_client(
    storage: StorageControllers,
    user_id: str,
    events: List[EventBase],
    is_peeking: bool = False,
    always_include_ids: FrozenSet[str] = frozenset(),
    filter_send_to_client: bool = True,
) -> List[EventBase]:
    """
    Check which events a user is allowed to see. If the user can see the event but its
    sender asked for their data to be erased, prune the content of the event.

    Args:
        storage
        user_id: user id to be checked
        events: sequence of events to be checked
        is_peeking: should be True if:
          * the user is not currently a member of the room, and:
          * the user has not been a member of the room since the given
            events
        always_include_ids: set of event ids to specifically
            include (unless sender is ignored)
        filter_send_to_client: Whether we're checking an event that's going to be
            sent to a client. This might not always be the case since this function can
            also be called to check whether a user can see the state at a given point.

    Returns:
        The filtered events.
    """
    # Filter out events that have been soft failed so that we don't relay them
    # to clients.
    events = [e for e in events if not e.internal_metadata.is_soft_failed()]

    types = (_HISTORY_VIS_KEY, (EventTypes.Member, user_id))

    # we exclude outliers at this point, and then handle them separately later
    event_id_to_state = await storage.state.get_state_for_events(
        frozenset(e.event_id for e in events if not e.internal_metadata.outlier),
        state_filter=StateFilter.from_types(types),
    )

    # Get the users who are ignored by the requesting user.
    ignore_list = await storage.main.ignored_users(user_id)

    erased_senders = await storage.main.are_users_erased(e.sender for e in events)

    if filter_send_to_client:
        room_ids = {e.room_id for e in events}
        retention_policies: Dict[str, RetentionPolicy] = {}

        for room_id in room_ids:
            retention_policies[
                room_id
            ] = await storage.main.get_retention_policy_for_room(room_id)

    def allowed(event: EventBase) -> Optional[EventBase]:
        """
        Args:
            event: event to check

        Returns:
           None if the user cannot see this event at all

           a redacted copy of the event if they can only see a redacted
           version

           the original event if they can see it as normal.
        """
        # Only run some checks if these events aren't about to be sent to clients. This is
        # because, if this is not the case, we're probably only checking if the users can
        # see events in the room at that point in the DAG, and that shouldn't be decided
        # on those checks.
        if filter_send_to_client:
            if event.type == EventTypes.Dummy:
                return None

            if not event.is_state() and event.sender in ignore_list:
                return None

            # Until MSC2261 has landed we can't redact malicious alias events, so for
            # now we temporarily filter out m.room.aliases entirely to mitigate
            # abuse, while we spec a better solution to advertising aliases
            # on rooms.
            if event.type == EventTypes.Aliases:
                return None

            # Don't try to apply the room's retention policy if the event is a state
            # event, as MSC1763 states that retention is only considered for non-state
            # events.
            if not event.is_state():
                retention_policy = retention_policies[event.room_id]
                max_lifetime = retention_policy.max_lifetime

                if max_lifetime is not None:
                    oldest_allowed_ts = storage.main.clock.time_msec() - max_lifetime

                    if event.origin_server_ts < oldest_allowed_ts:
                        return None

        if event.event_id in always_include_ids:
            return event

        # we need to handle outliers separately, since we don't have the room state.
        if event.internal_metadata.outlier:
            # Normally these can't be seen by clients, but we make an exception for
            # for out-of-band membership events (eg, incoming invites, or rejections of
            # said invite) for the user themselves.
            if event.type == EventTypes.Member and event.state_key == user_id:
                logger.debug("Returning out-of-band-membership event %s", event)
                return event

            return None

        state = event_id_to_state[event.event_id]

        # get the room_visibility at the time of the event.
        visibility = get_effective_room_visibility_from_state(state)

        # Always allow history visibility events on boundaries. This is done
        # by setting the effective visibility to the least restrictive
        # of the old vs new.
        if event.type == EventTypes.RoomHistoryVisibility:
            prev_content = event.unsigned.get("prev_content", {})
            prev_visibility = prev_content.get("history_visibility", None)

            if prev_visibility not in VISIBILITY_PRIORITY:
                prev_visibility = HistoryVisibility.SHARED

            new_priority = VISIBILITY_PRIORITY.index(visibility)
            old_priority = VISIBILITY_PRIORITY.index(prev_visibility)
            if old_priority < new_priority:
                visibility = prev_visibility

        # likewise, if the event is the user's own membership event, use
        # the 'most joined' membership
        membership = None
        if event.type == EventTypes.Member and event.state_key == user_id:
            membership = event.content.get("membership", None)
            if membership not in MEMBERSHIP_PRIORITY:
                membership = "leave"

            prev_content = event.unsigned.get("prev_content", {})
            prev_membership = prev_content.get("membership", None)
            if prev_membership not in MEMBERSHIP_PRIORITY:
                prev_membership = "leave"

            # Always allow the user to see their own leave events, otherwise
            # they won't see the room disappear if they reject the invite
            #
            # (Note this doesn't work for out-of-band invite rejections, which don't
            # have prev_state populated. They are handled above in the outlier code.)
            if membership == "leave" and (
                prev_membership == "join" or prev_membership == "invite"
            ):
                return event

            new_priority = MEMBERSHIP_PRIORITY.index(membership)
            old_priority = MEMBERSHIP_PRIORITY.index(prev_membership)
            if old_priority < new_priority:
                membership = prev_membership

        # otherwise, get the user's membership at the time of the event.
        if membership is None:
            membership_event = state.get((EventTypes.Member, user_id), None)
            if membership_event:
                membership = membership_event.membership

        # if the user was a member of the room at the time of the event,
        # they can see it.
        if membership == Membership.JOIN:
            return event

        # otherwise, it depends on the room visibility.

        if visibility == HistoryVisibility.JOINED:
            # we weren't a member at the time of the event, so we can't
            # see this event.
            return None

        elif visibility == HistoryVisibility.INVITED:
            # user can also see the event if they were *invited* at the time
            # of the event.
            return event if membership == Membership.INVITE else None

        elif visibility == HistoryVisibility.SHARED and is_peeking:
            # if the visibility is shared, users cannot see the event unless
            # they have *subsequently* joined the room (or were members at the
            # time, of course)
            #
            # XXX: if the user has subsequently joined and then left again,
            # ideally we would share history up to the point they left. But
            # we don't know when they left. We just treat it as though they
            # never joined, and restrict access.
            return None

        # the visibility is either shared or world_readable, and the user was
        # not a member at the time. We allow it, provided the original sender
        # has not requested their data to be erased, in which case, we return
        # a redacted version.
        if erased_senders[event.sender]:
            return prune_event(event)

        return event

    # Check each event: gives an iterable of None or (a potentially modified)
    # EventBase.
    filtered_events = map(allowed, events)

    # Turn it into a list and remove None entries before returning.
    return [ev for ev in filtered_events if ev]
예제 #26
0
파일: room.py 프로젝트: matrix-org/synapse
    def clone_existing_room(
            self, requester, old_room_id, new_room_id, new_room_version,
            tombstone_event_id,
    ):
        """Populate a new room based on an old room

        Args:
            requester (synapse.types.Requester): the user requesting the upgrade
            old_room_id (unicode): the id of the room to be replaced
            new_room_id (unicode): the id to give the new room (should already have been
                created with _gemerate_room_id())
            new_room_version (unicode): the new room version to use
            tombstone_event_id (unicode|str): the ID of the tombstone event in the old
                room.
        Returns:
            Deferred[None]
        """
        user_id = requester.user.to_string()

        if not self.spam_checker.user_may_create_room(user_id):
            raise SynapseError(403, "You are not permitted to create rooms")

        creation_content = {
            "room_version": new_room_version,
            "predecessor": {
                "room_id": old_room_id,
                "event_id": tombstone_event_id,
            }
        }

        # Check if old room was non-federatable

        # Get old room's create event
        old_room_create_event = yield self.store.get_create_event_for_room(old_room_id)

        # Check if the create event specified a non-federatable room
        if not old_room_create_event.content.get("m.federate", True):
            # If so, mark the new room as non-federatable as well
            creation_content["m.federate"] = False

        initial_state = dict()

        # Replicate relevant room events
        types_to_copy = (
            (EventTypes.JoinRules, ""),
            (EventTypes.Name, ""),
            (EventTypes.Topic, ""),
            (EventTypes.RoomHistoryVisibility, ""),
            (EventTypes.GuestAccess, ""),
            (EventTypes.RoomAvatar, ""),
            (EventTypes.Encryption, ""),
            (EventTypes.ServerACL, ""),
            (EventTypes.RelatedGroups, ""),
        )

        old_room_state_ids = yield self.store.get_filtered_current_state_ids(
            old_room_id, StateFilter.from_types(types_to_copy),
        )
        # map from event_id to BaseEvent
        old_room_state_events = yield self.store.get_events(old_room_state_ids.values())

        for k, old_event_id in iteritems(old_room_state_ids):
            old_event = old_room_state_events.get(old_event_id)
            if old_event:
                initial_state[k] = old_event.content

        yield self._send_events_for_new_room(
            requester,
            new_room_id,

            # we expect to override all the presets with initial_state, so this is
            # somewhat arbitrary.
            preset_config=RoomCreationPreset.PRIVATE_CHAT,

            invite_list=[],
            initial_state=initial_state,
            creation_content=creation_content,
        )

        # Transfer membership events
        old_room_member_state_ids = yield self.store.get_filtered_current_state_ids(
            old_room_id, StateFilter.from_types([(EventTypes.Member, None)]),
        )

        # map from event_id to BaseEvent
        old_room_member_state_events = yield self.store.get_events(
            old_room_member_state_ids.values(),
        )
        for k, old_event in iteritems(old_room_member_state_events):
            # Only transfer ban events
            if ("membership" in old_event.content and
                    old_event.content["membership"] == "ban"):
                yield self.room_member_handler.update_membership(
                    requester,
                    UserID.from_string(old_event['state_key']),
                    new_room_id,
                    "ban",
                    ratelimit=False,
                    content=old_event.content,
                )
예제 #27
0
    def search(self, user, content, batch=None):
        """Performs a full text search for a user.

        Args:
            user (UserID)
            content (dict): Search parameters
            batch (str): The next_batch parameter. Used for pagination.

        Returns:
            dict to be returned to the client with results of search
        """

        if not self.hs.config.enable_search:
            raise SynapseError(400, "Search is disabled on this homeserver")

        batch_group = None
        batch_group_key = None
        batch_token = None
        if batch:
            try:
                b = decode_base64(batch).decode('ascii')
                batch_group, batch_group_key, batch_token = b.split("\n")

                assert batch_group is not None
                assert batch_group_key is not None
                assert batch_token is not None
            except Exception:
                raise SynapseError(400, "Invalid batch")

        logger.info(
            "Search batch properties: %r, %r, %r",
            batch_group,
            batch_group_key,
            batch_token,
        )

        logger.info("Search content: %s", content)

        try:
            room_cat = content["search_categories"]["room_events"]

            # The actual thing to query in FTS
            search_term = room_cat["search_term"]

            # Which "keys" to search over in FTS query
            keys = room_cat.get("keys", [
                "content.body",
                "content.name",
                "content.topic",
            ])

            # Filter to apply to results
            filter_dict = room_cat.get("filter", {})

            # What to order results by (impacts whether pagination can be doen)
            order_by = room_cat.get("order_by", "rank")

            # Return the current state of the rooms?
            include_state = room_cat.get("include_state", False)

            # Include context around each event?
            event_context = room_cat.get("event_context", None)

            # Group results together? May allow clients to paginate within a
            # group
            group_by = room_cat.get("groupings", {}).get("group_by", {})
            group_keys = [g["key"] for g in group_by]

            if event_context is not None:
                before_limit = int(event_context.get("before_limit", 5))
                after_limit = int(event_context.get("after_limit", 5))

                # Return the historic display name and avatar for the senders
                # of the events?
                include_profile = bool(
                    event_context.get("include_profile", False))
        except KeyError:
            raise SynapseError(400, "Invalid search query")

        if order_by not in ("rank", "recent"):
            raise SynapseError(400, "Invalid order by: %r" % (order_by, ))

        if set(group_keys) - {"room_id", "sender"}:
            raise SynapseError(
                400, "Invalid group by keys: %r" %
                (set(group_keys) - {"room_id", "sender"}, ))

        search_filter = Filter(filter_dict)

        # TODO: Search through left rooms too
        rooms = yield self.store.get_rooms_for_user_where_membership_is(
            user.to_string(),
            membership_list=[Membership.JOIN],
            # membership_list=[Membership.JOIN, Membership.LEAVE, Membership.Ban],
        )
        room_ids = set(r.room_id for r in rooms)

        room_ids = search_filter.filter_rooms(room_ids)

        if batch_group == "room_id":
            room_ids.intersection_update({batch_group_key})

        if not room_ids:
            defer.returnValue({
                "search_categories": {
                    "room_events": {
                        "results": [],
                        "count": 0,
                        "highlights": [],
                    }
                }
            })

        rank_map = {}  # event_id -> rank of event
        allowed_events = []
        room_groups = {}  # Holds result of grouping by room, if applicable
        sender_group = {}  # Holds result of grouping by sender, if applicable

        # Holds the next_batch for the entire result set if one of those exists
        global_next_batch = None

        highlights = set()

        count = None

        if order_by == "rank":
            search_result = yield self.store.search_msgs(
                room_ids, search_term, keys)

            count = search_result["count"]

            if search_result["highlights"]:
                highlights.update(search_result["highlights"])

            results = search_result["results"]

            results_map = {r["event"].event_id: r for r in results}

            rank_map.update({r["event"].event_id: r["rank"] for r in results})

            filtered_events = search_filter.filter(
                [r["event"] for r in results])

            events = yield filter_events_for_client(self.store,
                                                    user.to_string(),
                                                    filtered_events)

            events.sort(key=lambda e: -rank_map[e.event_id])
            allowed_events = events[:search_filter.limit()]

            for e in allowed_events:
                rm = room_groups.setdefault(e.room_id, {
                    "results": [],
                    "order": rank_map[e.event_id],
                })
                rm["results"].append(e.event_id)

                s = sender_group.setdefault(e.sender, {
                    "results": [],
                    "order": rank_map[e.event_id],
                })
                s["results"].append(e.event_id)

        elif order_by == "recent":
            room_events = []
            i = 0

            pagination_token = batch_token

            # We keep looping and we keep filtering until we reach the limit
            # or we run out of things.
            # But only go around 5 times since otherwise synapse will be sad.
            while len(room_events) < search_filter.limit() and i < 5:
                i += 1
                search_result = yield self.store.search_rooms(
                    room_ids,
                    search_term,
                    keys,
                    search_filter.limit() * 2,
                    pagination_token=pagination_token,
                )

                if search_result["highlights"]:
                    highlights.update(search_result["highlights"])

                count = search_result["count"]

                results = search_result["results"]

                results_map = {r["event"].event_id: r for r in results}

                rank_map.update(
                    {r["event"].event_id: r["rank"]
                     for r in results})

                filtered_events = search_filter.filter(
                    [r["event"] for r in results])

                events = yield filter_events_for_client(
                    self.store, user.to_string(), filtered_events)

                room_events.extend(events)
                room_events = room_events[:search_filter.limit()]

                if len(results) < search_filter.limit() * 2:
                    pagination_token = None
                    break
                else:
                    pagination_token = results[-1]["pagination_token"]

            for event in room_events:
                group = room_groups.setdefault(event.room_id, {
                    "results": [],
                })
                group["results"].append(event.event_id)

            if room_events and len(room_events) >= search_filter.limit():
                last_event_id = room_events[-1].event_id
                pagination_token = results_map[last_event_id][
                    "pagination_token"]

                # We want to respect the given batch group and group keys so
                # that if people blindly use the top level `next_batch` token
                # it returns more from the same group (if applicable) rather
                # than reverting to searching all results again.
                if batch_group and batch_group_key:
                    global_next_batch = encode_base64(
                        ("%s\n%s\n%s" % (batch_group, batch_group_key,
                                         pagination_token)).encode('ascii'))
                else:
                    global_next_batch = encode_base64(
                        ("%s\n%s\n%s" %
                         ("all", "", pagination_token)).encode('ascii'))

                for room_id, group in room_groups.items():
                    group["next_batch"] = encode_base64(
                        ("%s\n%s\n%s" % ("room_id", room_id,
                                         pagination_token)).encode('ascii'))

            allowed_events.extend(room_events)

        else:
            # We should never get here due to the guard earlier.
            raise NotImplementedError()

        logger.info("Found %d events to return", len(allowed_events))

        # If client has asked for "context" for each event (i.e. some surrounding
        # events and state), fetch that
        if event_context is not None:
            now_token = yield self.hs.get_event_sources().get_current_token()

            contexts = {}
            for event in allowed_events:
                res = yield self.store.get_events_around(
                    event.room_id,
                    event.event_id,
                    before_limit,
                    after_limit,
                )

                logger.info(
                    "Context for search returned %d and %d events",
                    len(res["events_before"]),
                    len(res["events_after"]),
                )

                res["events_before"] = yield filter_events_for_client(
                    self.store, user.to_string(), res["events_before"])

                res["events_after"] = yield filter_events_for_client(
                    self.store, user.to_string(), res["events_after"])

                res["start"] = now_token.copy_and_replace(
                    "room_key", res["start"]).to_string()

                res["end"] = now_token.copy_and_replace(
                    "room_key", res["end"]).to_string()

                if include_profile:
                    senders = set(ev.sender for ev in itertools.chain(
                        res["events_before"], [event], res["events_after"]))

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

                    state_filter = StateFilter.from_types([
                        (EventTypes.Member, sender) for sender in senders
                    ])

                    state = yield self.store.get_state_for_event(
                        last_event_id, state_filter)

                    res["profile_info"] = {
                        s.state_key: {
                            "displayname": s.content.get("displayname", None),
                            "avatar_url": s.content.get("avatar_url", None),
                        }
                        for s in state.values() if s.type == EventTypes.Member
                        and s.state_key in senders
                    }

                contexts[event.event_id] = res
        else:
            contexts = {}

        # TODO: Add a limit

        time_now = self.clock.time_msec()

        for context in contexts.values():
            context["events_before"] = [
                serialize_event(e, time_now) for e in context["events_before"]
            ]
            context["events_after"] = [
                serialize_event(e, time_now) for e in context["events_after"]
            ]

        state_results = {}
        if include_state:
            rooms = set(e.room_id for e in allowed_events)
            for room_id in rooms:
                state = yield self.state_handler.get_current_state(room_id)
                state_results[room_id] = list(state.values())

            state_results.values()

        # We're now about to serialize the events. We should not make any
        # blocking calls after this. Otherwise the 'age' will be wrong

        results = [{
            "rank": rank_map[e.event_id],
            "result": serialize_event(e, time_now),
            "context": contexts.get(e.event_id, {}),
        } for e in allowed_events]

        rooms_cat_res = {
            "results": results,
            "count": count,
            "highlights": list(highlights),
        }

        if state_results:
            rooms_cat_res["state"] = {
                room_id: [serialize_event(e, time_now) for e in state]
                for room_id, state in state_results.items()
            }

        if room_groups and "room_id" in group_keys:
            rooms_cat_res.setdefault("groups", {})["room_id"] = room_groups

        if sender_group and "sender" in group_keys:
            rooms_cat_res.setdefault("groups", {})["sender"] = sender_group

        if global_next_batch:
            rooms_cat_res["next_batch"] = global_next_batch

        defer.returnValue(
            {"search_categories": {
                "room_events": rooms_cat_res
            }})
예제 #28
0
파일: pagination.py 프로젝트: velas/synapse
    async def get_messages(
        self,
        requester: Requester,
        room_id: str,
        pagin_config: PaginationConfig,
        as_client_event: bool = True,
        event_filter: Optional[Filter] = None,
    ) -> Dict[str, Any]:
        """Get messages in a room.

        Args:
            requester: The user requesting messages.
            room_id: The room they want messages from.
            pagin_config: The pagination config rules to apply, if any.
            as_client_event: True to get events in client-server format.
            event_filter: Filter to apply to results or None
        Returns:
            Pagination API results
        """
        user_id = requester.user.to_string()

        if pagin_config.from_token:
            from_token = pagin_config.from_token
        else:
            from_token = self.hs.get_event_sources().get_current_token_for_pagination()

        if pagin_config.limit is None:
            # This shouldn't happen as we've set a default limit before this
            # gets called.
            raise Exception("limit not set")

        room_token = from_token.room_key

        with await self.pagination_lock.read(room_id):
            (
                membership,
                member_event_id,
            ) = await self.auth.check_user_in_room_or_world_readable(
                room_id, user_id, allow_departed_users=True
            )

            if pagin_config.direction == "b":
                # if we're going backwards, we might need to backfill. This
                # requires that we have a topo token.
                if room_token.topological:
                    curr_topo = room_token.topological
                else:
                    curr_topo = await self.store.get_current_topological_token(
                        room_id, room_token.stream
                    )

                if membership == Membership.LEAVE:
                    # If they have left the room then clamp the token to be before
                    # they left the room, to save the effort of loading from the
                    # database.

                    # This is only None if the room is world_readable, in which
                    # case "JOIN" would have been returned.
                    assert member_event_id

                    leave_token = await self.store.get_topological_token_for_event(
                        member_event_id
                    )
                    assert leave_token.topological is not None

                    if leave_token.topological < curr_topo:
                        from_token = from_token.copy_and_replace(
                            "room_key", leave_token
                        )

                await self.hs.get_federation_handler().maybe_backfill(
                    room_id,
                    curr_topo,
                    limit=pagin_config.limit,
                )

            to_room_key = None
            if pagin_config.to_token:
                to_room_key = pagin_config.to_token.room_key

            events, next_key = await self.store.paginate_room_events(
                room_id=room_id,
                from_key=from_token.room_key,
                to_key=to_room_key,
                direction=pagin_config.direction,
                limit=pagin_config.limit,
                event_filter=event_filter,
            )

            next_token = from_token.copy_and_replace("room_key", next_key)

        if events:
            if event_filter:
                events = event_filter.filter(events)

            events = await filter_events_for_client(
                self.storage, user_id, events, is_peeking=(member_event_id is None)
            )

        if not events:
            return {
                "chunk": [],
                "start": await from_token.to_string(self.store),
                "end": await next_token.to_string(self.store),
            }

        state = None
        if event_filter and event_filter.lazy_load_members() and len(events) > 0:
            # TODO: remove redundant members

            # FIXME: we also care about invite targets etc.
            state_filter = StateFilter.from_types(
                (EventTypes.Member, event.sender) for event in events
            )

            state_ids = await self.state_store.get_state_ids_for_event(
                events[0].event_id, state_filter=state_filter
            )

            if state_ids:
                state_dict = await self.store.get_events(list(state_ids.values()))
                state = state_dict.values()

        time_now = self.clock.time_msec()

        chunk = {
            "chunk": (
                await self._event_serializer.serialize_events(
                    events, time_now, as_client_event=as_client_event
                )
            ),
            "start": await from_token.to_string(self.store),
            "end": await next_token.to_string(self.store),
        }

        if state:
            chunk["state"] = await self._event_serializer.serialize_events(
                state, time_now, as_client_event=as_client_event
            )

        return chunk
예제 #29
0
def filter_events_for_server(store, server_name, events):
    # Whatever else we do, we need to check for senders which have requested
    # erasure of their data.
    erased_senders = yield store.are_users_erased(
        (e.sender for e in events),
    )

    def redact_disallowed(event, state):
        # if the sender has been gdpr17ed, always return a redacted
        # copy of the event.
        if erased_senders[event.sender]:
            logger.info(
                "Sender of %s has been erased, redacting",
                event.event_id,
            )
            return prune_event(event)

        # state will be None if we decided we didn't need to filter by
        # room membership.
        if not state:
            return event

        history = state.get((EventTypes.RoomHistoryVisibility, ''), None)
        if history:
            visibility = history.content.get("history_visibility", "shared")
            if visibility in ["invited", "joined"]:
                # We now loop through all state events looking for
                # membership states for the requesting server to determine
                # if the server is either in the room or has been invited
                # into the room.
                for ev in itervalues(state):
                    if ev.type != EventTypes.Member:
                        continue
                    try:
                        domain = get_domain_from_id(ev.state_key)
                    except Exception:
                        continue

                    if domain != server_name:
                        continue

                    memtype = ev.membership
                    if memtype == Membership.JOIN:
                        return event
                    elif memtype == Membership.INVITE:
                        if visibility == "invited":
                            return event
                else:
                    # server has no users in the room: redact
                    return prune_event(event)

        return event

    # Next lets check to see if all the events have a history visibility
    # of "shared" or "world_readable". If thats the case then we don't
    # need to check membership (as we know the server is in the room).
    event_to_state_ids = yield store.get_state_ids_for_events(
        frozenset(e.event_id for e in events),
        state_filter=StateFilter.from_types(
            types=((EventTypes.RoomHistoryVisibility, ""),),
        )
    )

    visibility_ids = set()
    for sids in itervalues(event_to_state_ids):
        hist = sids.get((EventTypes.RoomHistoryVisibility, ""))
        if hist:
            visibility_ids.add(hist)

    # If we failed to find any history visibility events then the default
    # is "shared" visiblity.
    if not visibility_ids:
        all_open = True
    else:
        event_map = yield store.get_events(visibility_ids)
        all_open = all(
            e.content.get("history_visibility") in (None, "shared", "world_readable")
            for e in itervalues(event_map)
        )

    if all_open:
        # all the history_visibility state affecting these events is open, so
        # we don't need to filter by membership state. We *do* need to check
        # for user erasure, though.
        if erased_senders:
            events = [
                redact_disallowed(e, None)
                for e in events
            ]

        defer.returnValue(events)

    # Ok, so we're dealing with events that have non-trivial visibility
    # rules, so we need to also get the memberships of the room.

    # first, for each event we're wanting to return, get the event_ids
    # of the history vis and membership state at those events.
    event_to_state_ids = yield store.get_state_ids_for_events(
        frozenset(e.event_id for e in events),
        state_filter=StateFilter.from_types(
            types=(
                (EventTypes.RoomHistoryVisibility, ""),
                (EventTypes.Member, None),
            ),
        )
    )

    # We only want to pull out member events that correspond to the
    # server's domain.
    #
    # event_to_state_ids contains lots of duplicates, so it turns out to be
    # cheaper to build a complete event_id => (type, state_key) dict, and then
    # filter out the ones we don't want
    #
    event_id_to_state_key = {
        event_id: key
        for key_to_eid in itervalues(event_to_state_ids)
        for key, event_id in iteritems(key_to_eid)
    }

    def include(typ, state_key):
        if typ != EventTypes.Member:
            return True

        # we avoid using get_domain_from_id here for efficiency.
        idx = state_key.find(":")
        if idx == -1:
            return False
        return state_key[idx + 1:] == server_name

    event_map = yield store.get_events([
        e_id
        for e_id, key in iteritems(event_id_to_state_key)
        if include(key[0], key[1])
    ])

    event_to_state = {
        e_id: {
            key: event_map[inner_e_id]
            for key, inner_e_id in iteritems(key_to_eid)
            if inner_e_id in event_map
        }
        for e_id, key_to_eid in iteritems(event_to_state_ids)
    }

    defer.returnValue([
        redact_disallowed(e, event_to_state[e.event_id])
        for e in events
    ])
예제 #30
0
파일: register.py 프로젝트: Fnux/synapse
    async def _join_rooms(self, user_id: str) -> None:
        """
        Join or invite the user to the auto-join rooms.

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

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

            try:
                room_alias = RoomAlias.from_string(r)

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

                # Calculate whether the room requires an invite or can be
                # joined directly. By default, we consider the room as requiring an
                # invite if the homeserver is in the room (unless told otherwise by the
                # join rules). Otherwise we consider it as being joinable, at the risk of
                # failing to join, but in this case there's little more we can do since
                # we don't have a local user in the room to craft up an invite with.
                requires_invite = await self.store.is_host_joined(
                    room_id,
                    self._server_name,
                )

                if requires_invite:
                    # If the server is in the room, check if the room is public.
                    state = await self.store.get_filtered_current_state_ids(
                        room_id,
                        StateFilter.from_types([(EventTypes.JoinRules, "")]))

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

                # Send the invite, if necessary.
                if requires_invite:
                    # If an invite is required, there must be a auto-join user ID.
                    assert self.hs.config.registration.auto_join_user_id

                    await room_member_handler.update_membership(
                        requester=create_requester(
                            self.hs.config.registration.auto_join_user_id,
                            authenticated_entity=self._server_name,
                        ),
                        target=UserID.from_string(user_id),
                        room_id=room_id,
                        remote_room_hosts=remote_room_hosts,
                        action="invite",
                        ratelimit=False,
                    )

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

            except ConsentNotGivenError as e:
                # Technically not necessary to pull out this error though
                # moving away from bare excepts is a good thing to do.
                logger.error("Failed to join new user to %r: %r", r, e)
            except Exception as e:
                logger.error("Failed to join new user to %r: %r", r, e)
예제 #31
0
def filter_events_for_server(store, server_name, events):
    # Whatever else we do, we need to check for senders which have requested
    # erasure of their data.
    erased_senders = yield store.are_users_erased((e.sender for e in events), )

    def redact_disallowed(event, state):
        # if the sender has been gdpr17ed, always return a redacted
        # copy of the event.
        if erased_senders[event.sender]:
            logger.info(
                "Sender of %s has been erased, redacting",
                event.event_id,
            )
            return prune_event(event)

        # state will be None if we decided we didn't need to filter by
        # room membership.
        if not state:
            return event

        history = state.get((EventTypes.RoomHistoryVisibility, ''), None)
        if history:
            visibility = history.content.get("history_visibility", "shared")
            if visibility in ["invited", "joined"]:
                # We now loop through all state events looking for
                # membership states for the requesting server to determine
                # if the server is either in the room or has been invited
                # into the room.
                for ev in itervalues(state):
                    if ev.type != EventTypes.Member:
                        continue
                    try:
                        domain = get_domain_from_id(ev.state_key)
                    except Exception:
                        continue

                    if domain != server_name:
                        continue

                    memtype = ev.membership
                    if memtype == Membership.JOIN:
                        return event
                    elif memtype == Membership.INVITE:
                        if visibility == "invited":
                            return event
                else:
                    # server has no users in the room: redact
                    return prune_event(event)

        return event

    # Next lets check to see if all the events have a history visibility
    # of "shared" or "world_readable". If thats the case then we don't
    # need to check membership (as we know the server is in the room).
    event_to_state_ids = yield store.get_state_ids_for_events(
        frozenset(e.event_id for e in events),
        state_filter=StateFilter.from_types(
            types=((EventTypes.RoomHistoryVisibility, ""), ), ))

    visibility_ids = set()
    for sids in itervalues(event_to_state_ids):
        hist = sids.get((EventTypes.RoomHistoryVisibility, ""))
        if hist:
            visibility_ids.add(hist)

    # If we failed to find any history visibility events then the default
    # is "shared" visiblity.
    if not visibility_ids:
        all_open = True
    else:
        event_map = yield store.get_events(visibility_ids)
        all_open = all(
            e.content.get("history_visibility") in (None, "shared",
                                                    "world_readable")
            for e in itervalues(event_map))

    if all_open:
        # all the history_visibility state affecting these events is open, so
        # we don't need to filter by membership state. We *do* need to check
        # for user erasure, though.
        if erased_senders:
            events = [redact_disallowed(e, None) for e in events]

        defer.returnValue(events)

    # Ok, so we're dealing with events that have non-trivial visibility
    # rules, so we need to also get the memberships of the room.

    # first, for each event we're wanting to return, get the event_ids
    # of the history vis and membership state at those events.
    event_to_state_ids = yield store.get_state_ids_for_events(
        frozenset(e.event_id for e in events),
        state_filter=StateFilter.from_types(types=(
            (EventTypes.RoomHistoryVisibility, ""),
            (EventTypes.Member, None),
        ), ))

    # We only want to pull out member events that correspond to the
    # server's domain.
    #
    # event_to_state_ids contains lots of duplicates, so it turns out to be
    # cheaper to build a complete event_id => (type, state_key) dict, and then
    # filter out the ones we don't want
    #
    event_id_to_state_key = {
        event_id: key
        for key_to_eid in itervalues(event_to_state_ids)
        for key, event_id in iteritems(key_to_eid)
    }

    def include(typ, state_key):
        if typ != EventTypes.Member:
            return True

        # we avoid using get_domain_from_id here for efficiency.
        idx = state_key.find(":")
        if idx == -1:
            return False
        return state_key[idx + 1:] == server_name

    event_map = yield store.get_events([
        e_id for e_id, key in iteritems(event_id_to_state_key)
        if include(key[0], key[1])
    ])

    event_to_state = {
        e_id: {
            key: event_map[inner_e_id]
            for key, inner_e_id in iteritems(key_to_eid)
            if inner_e_id in event_map
        }
        for e_id, key_to_eid in iteritems(event_to_state_ids)
    }

    defer.returnValue(
        [redact_disallowed(e, event_to_state[e.event_id]) for e in events])
예제 #32
0
def filter_events_for_server(store, server_name, events, redact=True,
                             check_history_visibility_only=False):
    """Filter a list of events based on whether given server is allowed to
    see them.

    Args:
        store (DataStore)
        server_name (str)
        events (iterable[FrozenEvent])
        redact (bool): Whether to return a redacted version of the event, or
            to filter them out entirely.
        check_history_visibility_only (bool): Whether to only check the
            history visibility, rather than things like if the sender has been
            erased. This is used e.g. during pagination to decide whether to
            backfill or not.

    Returns
        Deferred[list[FrozenEvent]]
    """

    def is_sender_erased(event, erased_senders):
        if erased_senders and erased_senders[event.sender]:
            logger.info(
                "Sender of %s has been erased, redacting",
                event.event_id,
            )
            return True
        return False

    def check_event_is_visible(event, state):
        history = state.get((EventTypes.RoomHistoryVisibility, ''), None)
        if history:
            visibility = history.content.get("history_visibility", "shared")
            if visibility in ["invited", "joined"]:
                # We now loop through all state events looking for
                # membership states for the requesting server to determine
                # if the server is either in the room or has been invited
                # into the room.
                for ev in itervalues(state):
                    if ev.type != EventTypes.Member:
                        continue
                    try:
                        domain = get_domain_from_id(ev.state_key)
                    except Exception:
                        continue

                    if domain != server_name:
                        continue

                    memtype = ev.membership
                    if memtype == Membership.JOIN:
                        return True
                    elif memtype == Membership.INVITE:
                        if visibility == "invited":
                            return True
                else:
                    # server has no users in the room: redact
                    return False

        return True

    # Lets check to see if all the events have a history visibility
    # of "shared" or "world_readable". If thats the case then we don't
    # need to check membership (as we know the server is in the room).
    event_to_state_ids = yield store.get_state_ids_for_events(
        frozenset(e.event_id for e in events),
        state_filter=StateFilter.from_types(
            types=((EventTypes.RoomHistoryVisibility, ""),),
        )
    )

    visibility_ids = set()
    for sids in itervalues(event_to_state_ids):
        hist = sids.get((EventTypes.RoomHistoryVisibility, ""))
        if hist:
            visibility_ids.add(hist)

    # If we failed to find any history visibility events then the default
    # is "shared" visiblity.
    if not visibility_ids:
        all_open = True
    else:
        event_map = yield store.get_events(visibility_ids)
        all_open = all(
            e.content.get("history_visibility") in (None, "shared", "world_readable")
            for e in itervalues(event_map)
        )

    if not check_history_visibility_only:
        erased_senders = yield store.are_users_erased(
            (e.sender for e in events),
        )
    else:
        # We don't want to check whether users are erased, which is equivalent
        # to no users having been erased.
        erased_senders = {}

    if all_open:
        # all the history_visibility state affecting these events is open, so
        # we don't need to filter by membership state. We *do* need to check
        # for user erasure, though.
        if erased_senders:
            to_return = []
            for e in events:
                if not is_sender_erased(e, erased_senders):
                    to_return.append(e)
                elif redact:
                    to_return.append(prune_event(e))

            defer.returnValue(to_return)

        # If there are no erased users then we can just return the given list
        # of events without having to copy it.
        defer.returnValue(events)

    # Ok, so we're dealing with events that have non-trivial visibility
    # rules, so we need to also get the memberships of the room.

    # first, for each event we're wanting to return, get the event_ids
    # of the history vis and membership state at those events.
    event_to_state_ids = yield store.get_state_ids_for_events(
        frozenset(e.event_id for e in events),
        state_filter=StateFilter.from_types(
            types=(
                (EventTypes.RoomHistoryVisibility, ""),
                (EventTypes.Member, None),
            ),
        )
    )

    # We only want to pull out member events that correspond to the
    # server's domain.
    #
    # event_to_state_ids contains lots of duplicates, so it turns out to be
    # cheaper to build a complete event_id => (type, state_key) dict, and then
    # filter out the ones we don't want
    #
    event_id_to_state_key = {
        event_id: key
        for key_to_eid in itervalues(event_to_state_ids)
        for key, event_id in iteritems(key_to_eid)
    }

    def include(typ, state_key):
        if typ != EventTypes.Member:
            return True

        # we avoid using get_domain_from_id here for efficiency.
        idx = state_key.find(":")
        if idx == -1:
            return False
        return state_key[idx + 1:] == server_name

    event_map = yield store.get_events([
        e_id
        for e_id, key in iteritems(event_id_to_state_key)
        if include(key[0], key[1])
    ])

    event_to_state = {
        e_id: {
            key: event_map[inner_e_id]
            for key, inner_e_id in iteritems(key_to_eid)
            if inner_e_id in event_map
        }
        for e_id, key_to_eid in iteritems(event_to_state_ids)
    }

    to_return = []
    for e in events:
        erased = is_sender_erased(e, erased_senders)
        visible = check_event_is_visible(e, event_to_state[e.event_id])
        if visible and not erased:
            to_return.append(e)
        elif redact:
            to_return.append(prune_event(e))

    defer.returnValue(to_return)
예제 #33
0
    def test_get_state_for_event(self):

        # this defaults to a linear DAG as each new injection defaults to whatever
        # forward extremities are currently in the DB for this room.
        e1 = yield self.inject_state_event(
            self.room, self.u_alice, EventTypes.Create, '', {}
        )
        e2 = yield self.inject_state_event(
            self.room, self.u_alice, EventTypes.Name, '', {"name": "test room"}
        )
        e3 = yield self.inject_state_event(
            self.room,
            self.u_alice,
            EventTypes.Member,
            self.u_alice.to_string(),
            {"membership": Membership.JOIN},
        )
        e4 = yield self.inject_state_event(
            self.room,
            self.u_bob,
            EventTypes.Member,
            self.u_bob.to_string(),
            {"membership": Membership.JOIN},
        )
        e5 = yield self.inject_state_event(
            self.room,
            self.u_bob,
            EventTypes.Member,
            self.u_bob.to_string(),
            {"membership": Membership.LEAVE},
        )

        # check we get the full state as of the final event
        state = yield self.store.get_state_for_event(e5.event_id)

        self.assertIsNotNone(e4)

        self.assertStateMapEqual(
            {
                (e1.type, e1.state_key): e1,
                (e2.type, e2.state_key): e2,
                (e3.type, e3.state_key): e3,
                # e4 is overwritten by e5
                (e5.type, e5.state_key): e5,
            },
            state,
        )

        # check we can filter to the m.room.name event (with a '' state key)
        state = yield self.store.get_state_for_event(
            e5.event_id, StateFilter.from_types([(EventTypes.Name, '')])
        )

        self.assertStateMapEqual({(e2.type, e2.state_key): e2}, state)

        # check we can filter to the m.room.name event (with a wildcard None state key)
        state = yield self.store.get_state_for_event(
            e5.event_id, StateFilter.from_types([(EventTypes.Name, None)])
        )

        self.assertStateMapEqual({(e2.type, e2.state_key): e2}, state)

        # check we can grab the m.room.member events (with a wildcard None state key)
        state = yield self.store.get_state_for_event(
            e5.event_id, StateFilter.from_types([(EventTypes.Member, None)])
        )

        self.assertStateMapEqual(
            {(e3.type, e3.state_key): e3, (e5.type, e5.state_key): e5}, state
        )

        # check we can grab a specific room member without filtering out the
        # other event types
        state = yield self.store.get_state_for_event(
            e5.event_id,
            state_filter=StateFilter(
                types={EventTypes.Member: {self.u_alice.to_string()}},
                include_others=True,
            ),
        )

        self.assertStateMapEqual(
            {
                (e1.type, e1.state_key): e1,
                (e2.type, e2.state_key): e2,
                (e3.type, e3.state_key): e3,
            },
            state,
        )

        # check that we can grab everything except members
        state = yield self.store.get_state_for_event(
            e5.event_id,
            state_filter=StateFilter(
                types={EventTypes.Member: set()}, include_others=True
            ),
        )

        self.assertStateMapEqual(
            {(e1.type, e1.state_key): e1, (e2.type, e2.state_key): e2}, state
        )

        #######################################################
        # _get_state_for_group_using_cache tests against a full cache
        #######################################################

        room_id = self.room.to_string()
        group_ids = yield self.store.get_state_groups_ids(room_id, [e5.event_id])
        group = list(group_ids.keys())[0]

        # test _get_state_for_group_using_cache correctly filters out members
        # with types=[]
        (state_dict, is_all) = yield self.store._get_state_for_group_using_cache(
            self.store._state_group_cache,
            group,
            state_filter=StateFilter(
                types={EventTypes.Member: set()}, include_others=True
            ),
        )

        self.assertEqual(is_all, True)
        self.assertDictEqual(
            {
                (e1.type, e1.state_key): e1.event_id,
                (e2.type, e2.state_key): e2.event_id,
            },
            state_dict,
        )

        (state_dict, is_all) = yield self.store._get_state_for_group_using_cache(
            self.store._state_group_members_cache,
            group,
            state_filter=StateFilter(
                types={EventTypes.Member: set()}, include_others=True
            ),
        )

        self.assertEqual(is_all, True)
        self.assertDictEqual({}, state_dict)

        # test _get_state_for_group_using_cache correctly filters in members
        # with wildcard types
        (state_dict, is_all) = yield self.store._get_state_for_group_using_cache(
            self.store._state_group_cache,
            group,
            state_filter=StateFilter(
                types={EventTypes.Member: None}, include_others=True
            ),
        )

        self.assertEqual(is_all, True)
        self.assertDictEqual(
            {
                (e1.type, e1.state_key): e1.event_id,
                (e2.type, e2.state_key): e2.event_id,
            },
            state_dict,
        )

        (state_dict, is_all) = yield self.store._get_state_for_group_using_cache(
            self.store._state_group_members_cache,
            group,
            state_filter=StateFilter(
                types={EventTypes.Member: None}, include_others=True
            ),
        )

        self.assertEqual(is_all, True)
        self.assertDictEqual(
            {
                (e3.type, e3.state_key): e3.event_id,
                # e4 is overwritten by e5
                (e5.type, e5.state_key): e5.event_id,
            },
            state_dict,
        )

        # test _get_state_for_group_using_cache correctly filters in members
        # with specific types
        (state_dict, is_all) = yield self.store._get_state_for_group_using_cache(
            self.store._state_group_cache,
            group,
            state_filter=StateFilter(
                types={EventTypes.Member: {e5.state_key}}, include_others=True
            ),
        )

        self.assertEqual(is_all, True)
        self.assertDictEqual(
            {
                (e1.type, e1.state_key): e1.event_id,
                (e2.type, e2.state_key): e2.event_id,
            },
            state_dict,
        )

        (state_dict, is_all) = yield self.store._get_state_for_group_using_cache(
            self.store._state_group_members_cache,
            group,
            state_filter=StateFilter(
                types={EventTypes.Member: {e5.state_key}}, include_others=True
            ),
        )

        self.assertEqual(is_all, True)
        self.assertDictEqual({(e5.type, e5.state_key): e5.event_id}, state_dict)

        # test _get_state_for_group_using_cache correctly filters in members
        # with specific types
        (state_dict, is_all) = yield self.store._get_state_for_group_using_cache(
            self.store._state_group_members_cache,
            group,
            state_filter=StateFilter(
                types={EventTypes.Member: {e5.state_key}}, include_others=False
            ),
        )

        self.assertEqual(is_all, True)
        self.assertDictEqual({(e5.type, e5.state_key): e5.event_id}, state_dict)

        #######################################################
        # deliberately remove e2 (room name) from the _state_group_cache

        (is_all, known_absent, state_dict_ids) = self.store._state_group_cache.get(
            group
        )

        self.assertEqual(is_all, True)
        self.assertEqual(known_absent, set())
        self.assertDictEqual(
            state_dict_ids,
            {
                (e1.type, e1.state_key): e1.event_id,
                (e2.type, e2.state_key): e2.event_id,
            },
        )

        state_dict_ids.pop((e2.type, e2.state_key))
        self.store._state_group_cache.invalidate(group)
        self.store._state_group_cache.update(
            sequence=self.store._state_group_cache.sequence,
            key=group,
            value=state_dict_ids,
            # list fetched keys so it knows it's partial
            fetched_keys=((e1.type, e1.state_key),),
        )

        (is_all, known_absent, state_dict_ids) = self.store._state_group_cache.get(
            group
        )

        self.assertEqual(is_all, False)
        self.assertEqual(known_absent, set([(e1.type, e1.state_key)]))
        self.assertDictEqual(state_dict_ids, {(e1.type, e1.state_key): e1.event_id})

        ############################################
        # test that things work with a partial cache

        # test _get_state_for_group_using_cache correctly filters out members
        # with types=[]
        room_id = self.room.to_string()
        (state_dict, is_all) = yield self.store._get_state_for_group_using_cache(
            self.store._state_group_cache,
            group,
            state_filter=StateFilter(
                types={EventTypes.Member: set()}, include_others=True
            ),
        )

        self.assertEqual(is_all, False)
        self.assertDictEqual({(e1.type, e1.state_key): e1.event_id}, state_dict)

        room_id = self.room.to_string()
        (state_dict, is_all) = yield self.store._get_state_for_group_using_cache(
            self.store._state_group_members_cache,
            group,
            state_filter=StateFilter(
                types={EventTypes.Member: set()}, include_others=True
            ),
        )

        self.assertEqual(is_all, True)
        self.assertDictEqual({}, state_dict)

        # test _get_state_for_group_using_cache correctly filters in members
        # wildcard types
        (state_dict, is_all) = yield self.store._get_state_for_group_using_cache(
            self.store._state_group_cache,
            group,
            state_filter=StateFilter(
                types={EventTypes.Member: None}, include_others=True
            ),
        )

        self.assertEqual(is_all, False)
        self.assertDictEqual({(e1.type, e1.state_key): e1.event_id}, state_dict)

        (state_dict, is_all) = yield self.store._get_state_for_group_using_cache(
            self.store._state_group_members_cache,
            group,
            state_filter=StateFilter(
                types={EventTypes.Member: None}, include_others=True
            ),
        )

        self.assertEqual(is_all, True)
        self.assertDictEqual(
            {
                (e3.type, e3.state_key): e3.event_id,
                (e5.type, e5.state_key): e5.event_id,
            },
            state_dict,
        )

        # test _get_state_for_group_using_cache correctly filters in members
        # with specific types
        (state_dict, is_all) = yield self.store._get_state_for_group_using_cache(
            self.store._state_group_cache,
            group,
            state_filter=StateFilter(
                types={EventTypes.Member: {e5.state_key}}, include_others=True
            ),
        )

        self.assertEqual(is_all, False)
        self.assertDictEqual({(e1.type, e1.state_key): e1.event_id}, state_dict)

        (state_dict, is_all) = yield self.store._get_state_for_group_using_cache(
            self.store._state_group_members_cache,
            group,
            state_filter=StateFilter(
                types={EventTypes.Member: {e5.state_key}}, include_others=True
            ),
        )

        self.assertEqual(is_all, True)
        self.assertDictEqual({(e5.type, e5.state_key): e5.event_id}, state_dict)

        # test _get_state_for_group_using_cache correctly filters in members
        # with specific types
        (state_dict, is_all) = yield self.store._get_state_for_group_using_cache(
            self.store._state_group_cache,
            group,
            state_filter=StateFilter(
                types={EventTypes.Member: {e5.state_key}}, include_others=False
            ),
        )

        self.assertEqual(is_all, False)
        self.assertDictEqual({}, state_dict)

        (state_dict, is_all) = yield self.store._get_state_for_group_using_cache(
            self.store._state_group_members_cache,
            group,
            state_filter=StateFilter(
                types={EventTypes.Member: {e5.state_key}}, include_others=False
            ),
        )

        self.assertEqual(is_all, True)
        self.assertDictEqual({(e5.type, e5.state_key): e5.event_id}, state_dict)
예제 #34
0
    def test_get_state_for_event(self):
        # this defaults to a linear DAG as each new injection defaults to whatever
        # forward extremities are currently in the DB for this room.
        e1 = self.inject_state_event(self.room, self.u_alice,
                                     EventTypes.Create, "", {})
        e2 = self.inject_state_event(self.room, self.u_alice, EventTypes.Name,
                                     "", {"name": "test room"})
        e3 = self.inject_state_event(
            self.room,
            self.u_alice,
            EventTypes.Member,
            self.u_alice.to_string(),
            {"membership": Membership.JOIN},
        )
        e4 = self.inject_state_event(
            self.room,
            self.u_bob,
            EventTypes.Member,
            self.u_bob.to_string(),
            {"membership": Membership.JOIN},
        )
        e5 = self.inject_state_event(
            self.room,
            self.u_bob,
            EventTypes.Member,
            self.u_bob.to_string(),
            {"membership": Membership.LEAVE},
        )

        # check we get the full state as of the final event
        state = self.get_success(
            self.storage.state.get_state_for_event(e5.event_id))

        self.assertIsNotNone(e4)

        self.assertStateMapEqual(
            {
                (e1.type, e1.state_key): e1,
                (e2.type, e2.state_key): e2,
                (e3.type, e3.state_key): e3,
                # e4 is overwritten by e5
                (e5.type, e5.state_key): e5,
            },
            state,
        )

        # check we can filter to the m.room.name event (with a '' state key)
        state = self.get_success(
            self.storage.state.get_state_for_event(
                e5.event_id, StateFilter.from_types([(EventTypes.Name, "")])))

        self.assertStateMapEqual({(e2.type, e2.state_key): e2}, state)

        # check we can filter to the m.room.name event (with a wildcard None state key)
        state = self.get_success(
            self.storage.state.get_state_for_event(
                e5.event_id,
                StateFilter.from_types([(EventTypes.Name, None)])))

        self.assertStateMapEqual({(e2.type, e2.state_key): e2}, state)

        # check we can grab the m.room.member events (with a wildcard None state key)
        state = self.get_success(
            self.storage.state.get_state_for_event(
                e5.event_id,
                StateFilter.from_types([(EventTypes.Member, None)])))

        self.assertStateMapEqual(
            {
                (e3.type, e3.state_key): e3,
                (e5.type, e5.state_key): e5
            }, state)

        # check we can grab a specific room member without filtering out the
        # other event types
        state = self.get_success(
            self.storage.state.get_state_for_event(
                e5.event_id,
                state_filter=StateFilter(
                    types=frozendict({
                        EventTypes.Member:
                        frozenset({self.u_alice.to_string()})
                    }),
                    include_others=True,
                ),
            ))

        self.assertStateMapEqual(
            {
                (e1.type, e1.state_key): e1,
                (e2.type, e2.state_key): e2,
                (e3.type, e3.state_key): e3,
            },
            state,
        )

        # check that we can grab everything except members
        state = self.get_success(
            self.storage.state.get_state_for_event(
                e5.event_id,
                state_filter=StateFilter(
                    types=frozendict({EventTypes.Member: frozenset()}),
                    include_others=True,
                ),
            ))

        self.assertStateMapEqual(
            {
                (e1.type, e1.state_key): e1,
                (e2.type, e2.state_key): e2
            }, state)

        #######################################################
        # _get_state_for_group_using_cache tests against a full cache
        #######################################################

        room_id = self.room.to_string()
        group_ids = self.get_success(
            self.storage.state.get_state_groups_ids(room_id, [e5.event_id]))
        group = list(group_ids.keys())[0]

        # test _get_state_for_group_using_cache correctly filters out members
        # with types=[]
        (
            state_dict,
            is_all,
        ) = self.state_datastore._get_state_for_group_using_cache(
            self.state_datastore._state_group_cache,
            group,
            state_filter=StateFilter(types=frozendict(
                {EventTypes.Member: frozenset()}),
                                     include_others=True),
        )

        self.assertEqual(is_all, True)
        self.assertDictEqual(
            {
                (e1.type, e1.state_key): e1.event_id,
                (e2.type, e2.state_key): e2.event_id,
            },
            state_dict,
        )

        (
            state_dict,
            is_all,
        ) = self.state_datastore._get_state_for_group_using_cache(
            self.state_datastore._state_group_members_cache,
            group,
            state_filter=StateFilter(types=frozendict(
                {EventTypes.Member: frozenset()}),
                                     include_others=True),
        )

        self.assertEqual(is_all, True)
        self.assertDictEqual({}, state_dict)

        # test _get_state_for_group_using_cache correctly filters in members
        # with wildcard types
        (
            state_dict,
            is_all,
        ) = self.state_datastore._get_state_for_group_using_cache(
            self.state_datastore._state_group_cache,
            group,
            state_filter=StateFilter(types=frozendict(
                {EventTypes.Member: None}),
                                     include_others=True),
        )

        self.assertEqual(is_all, True)
        self.assertDictEqual(
            {
                (e1.type, e1.state_key): e1.event_id,
                (e2.type, e2.state_key): e2.event_id,
            },
            state_dict,
        )

        (
            state_dict,
            is_all,
        ) = self.state_datastore._get_state_for_group_using_cache(
            self.state_datastore._state_group_members_cache,
            group,
            state_filter=StateFilter(types=frozendict(
                {EventTypes.Member: None}),
                                     include_others=True),
        )

        self.assertEqual(is_all, True)
        self.assertDictEqual(
            {
                (e3.type, e3.state_key): e3.event_id,
                # e4 is overwritten by e5
                (e5.type, e5.state_key): e5.event_id,
            },
            state_dict,
        )

        # test _get_state_for_group_using_cache correctly filters in members
        # with specific types
        (
            state_dict,
            is_all,
        ) = self.state_datastore._get_state_for_group_using_cache(
            self.state_datastore._state_group_cache,
            group,
            state_filter=StateFilter(
                types=frozendict(
                    {EventTypes.Member: frozenset({e5.state_key})}),
                include_others=True,
            ),
        )

        self.assertEqual(is_all, True)
        self.assertDictEqual(
            {
                (e1.type, e1.state_key): e1.event_id,
                (e2.type, e2.state_key): e2.event_id,
            },
            state_dict,
        )

        (
            state_dict,
            is_all,
        ) = self.state_datastore._get_state_for_group_using_cache(
            self.state_datastore._state_group_members_cache,
            group,
            state_filter=StateFilter(
                types=frozendict(
                    {EventTypes.Member: frozenset({e5.state_key})}),
                include_others=True,
            ),
        )

        self.assertEqual(is_all, True)
        self.assertDictEqual({(e5.type, e5.state_key): e5.event_id},
                             state_dict)

        # test _get_state_for_group_using_cache correctly filters in members
        # with specific types
        (
            state_dict,
            is_all,
        ) = self.state_datastore._get_state_for_group_using_cache(
            self.state_datastore._state_group_members_cache,
            group,
            state_filter=StateFilter(
                types=frozendict(
                    {EventTypes.Member: frozenset({e5.state_key})}),
                include_others=False,
            ),
        )

        self.assertEqual(is_all, True)
        self.assertDictEqual({(e5.type, e5.state_key): e5.event_id},
                             state_dict)

        #######################################################
        # deliberately remove e2 (room name) from the _state_group_cache

        cache_entry = self.state_datastore._state_group_cache.get(group)
        state_dict_ids = cache_entry.value

        self.assertEqual(cache_entry.full, True)
        self.assertEqual(cache_entry.known_absent, set())
        self.assertDictEqual(
            state_dict_ids,
            {
                (e1.type, e1.state_key): e1.event_id,
                (e2.type, e2.state_key): e2.event_id,
            },
        )

        state_dict_ids.pop((e2.type, e2.state_key))
        self.state_datastore._state_group_cache.invalidate(group)
        self.state_datastore._state_group_cache.update(
            sequence=self.state_datastore._state_group_cache.sequence,
            key=group,
            value=state_dict_ids,
            # list fetched keys so it knows it's partial
            fetched_keys=((e1.type, e1.state_key), ),
        )

        cache_entry = self.state_datastore._state_group_cache.get(group)
        state_dict_ids = cache_entry.value

        self.assertEqual(cache_entry.full, False)
        self.assertEqual(cache_entry.known_absent, {(e1.type, e1.state_key)})
        self.assertDictEqual(state_dict_ids,
                             {(e1.type, e1.state_key): e1.event_id})

        ############################################
        # test that things work with a partial cache

        # test _get_state_for_group_using_cache correctly filters out members
        # with types=[]
        room_id = self.room.to_string()
        (
            state_dict,
            is_all,
        ) = self.state_datastore._get_state_for_group_using_cache(
            self.state_datastore._state_group_cache,
            group,
            state_filter=StateFilter(types=frozendict(
                {EventTypes.Member: frozenset()}),
                                     include_others=True),
        )

        self.assertEqual(is_all, False)
        self.assertDictEqual({(e1.type, e1.state_key): e1.event_id},
                             state_dict)

        room_id = self.room.to_string()
        (
            state_dict,
            is_all,
        ) = self.state_datastore._get_state_for_group_using_cache(
            self.state_datastore._state_group_members_cache,
            group,
            state_filter=StateFilter(types=frozendict(
                {EventTypes.Member: frozenset()}),
                                     include_others=True),
        )

        self.assertEqual(is_all, True)
        self.assertDictEqual({}, state_dict)

        # test _get_state_for_group_using_cache correctly filters in members
        # wildcard types
        (
            state_dict,
            is_all,
        ) = self.state_datastore._get_state_for_group_using_cache(
            self.state_datastore._state_group_cache,
            group,
            state_filter=StateFilter(types=frozendict(
                {EventTypes.Member: None}),
                                     include_others=True),
        )

        self.assertEqual(is_all, False)
        self.assertDictEqual({(e1.type, e1.state_key): e1.event_id},
                             state_dict)

        (
            state_dict,
            is_all,
        ) = self.state_datastore._get_state_for_group_using_cache(
            self.state_datastore._state_group_members_cache,
            group,
            state_filter=StateFilter(types=frozendict(
                {EventTypes.Member: None}),
                                     include_others=True),
        )

        self.assertEqual(is_all, True)
        self.assertDictEqual(
            {
                (e3.type, e3.state_key): e3.event_id,
                (e5.type, e5.state_key): e5.event_id,
            },
            state_dict,
        )

        # test _get_state_for_group_using_cache correctly filters in members
        # with specific types
        (
            state_dict,
            is_all,
        ) = self.state_datastore._get_state_for_group_using_cache(
            self.state_datastore._state_group_cache,
            group,
            state_filter=StateFilter(
                types=frozendict(
                    {EventTypes.Member: frozenset({e5.state_key})}),
                include_others=True,
            ),
        )

        self.assertEqual(is_all, False)
        self.assertDictEqual({(e1.type, e1.state_key): e1.event_id},
                             state_dict)

        (
            state_dict,
            is_all,
        ) = self.state_datastore._get_state_for_group_using_cache(
            self.state_datastore._state_group_members_cache,
            group,
            state_filter=StateFilter(
                types=frozendict(
                    {EventTypes.Member: frozenset({e5.state_key})}),
                include_others=True,
            ),
        )

        self.assertEqual(is_all, True)
        self.assertDictEqual({(e5.type, e5.state_key): e5.event_id},
                             state_dict)

        # test _get_state_for_group_using_cache correctly filters in members
        # with specific types
        (
            state_dict,
            is_all,
        ) = self.state_datastore._get_state_for_group_using_cache(
            self.state_datastore._state_group_cache,
            group,
            state_filter=StateFilter(
                types=frozendict(
                    {EventTypes.Member: frozenset({e5.state_key})}),
                include_others=False,
            ),
        )

        self.assertEqual(is_all, False)
        self.assertDictEqual({}, state_dict)

        (
            state_dict,
            is_all,
        ) = self.state_datastore._get_state_for_group_using_cache(
            self.state_datastore._state_group_members_cache,
            group,
            state_filter=StateFilter(
                types=frozendict(
                    {EventTypes.Member: frozenset({e5.state_key})}),
                include_others=False,
            ),
        )

        self.assertEqual(is_all, True)
        self.assertDictEqual({(e5.type, e5.state_key): e5.event_id},
                             state_dict)
예제 #35
0
파일: room.py 프로젝트: skylord123/synapse
    def clone_existing_room(
            self, requester, old_room_id, new_room_id, new_room_version,
            tombstone_event_id,
    ):
        """Populate a new room based on an old room

        Args:
            requester (synapse.types.Requester): the user requesting the upgrade
            old_room_id (unicode): the id of the room to be replaced
            new_room_id (unicode): the id to give the new room (should already have been
                created with _gemerate_room_id())
            new_room_version (unicode): the new room version to use
            tombstone_event_id (unicode|str): the ID of the tombstone event in the old
                room.
        Returns:
            Deferred[None]
        """
        user_id = requester.user.to_string()

        if not self.spam_checker.user_may_create_room(user_id):
            raise SynapseError(403, "You are not permitted to create rooms")

        creation_content = {
            "room_version": new_room_version,
            "predecessor": {
                "room_id": old_room_id,
                "event_id": tombstone_event_id,
            }
        }

        initial_state = dict()

        # Replicate relevant room events
        types_to_copy = (
            (EventTypes.JoinRules, ""),
            (EventTypes.Name, ""),
            (EventTypes.Topic, ""),
            (EventTypes.RoomHistoryVisibility, ""),
            (EventTypes.GuestAccess, ""),
            (EventTypes.RoomAvatar, ""),
            (EventTypes.Encryption, ""),
        )

        old_room_state_ids = yield self.store.get_filtered_current_state_ids(
            old_room_id, StateFilter.from_types(types_to_copy),
        )
        # map from event_id to BaseEvent
        old_room_state_events = yield self.store.get_events(old_room_state_ids.values())

        for k, old_event_id in iteritems(old_room_state_ids):
            old_event = old_room_state_events.get(old_event_id)
            if old_event:
                initial_state[k] = old_event.content

        yield self._send_events_for_new_room(
            requester,
            new_room_id,

            # we expect to override all the presets with initial_state, so this is
            # somewhat arbitrary.
            preset_config=RoomCreationPreset.PRIVATE_CHAT,

            invite_list=[],
            initial_state=initial_state,
            creation_content=creation_content,
        )
예제 #36
0
    def get_messages(self,
                     requester,
                     room_id=None,
                     pagin_config=None,
                     as_client_event=True,
                     event_filter=None):
        """Get messages in a room.

        Args:
            requester (Requester): The user requesting messages.
            room_id (str): The room they want messages from.
            pagin_config (synapse.api.streams.PaginationConfig): The pagination
                config rules to apply, if any.
            as_client_event (bool): True to get events in client-server format.
            event_filter (Filter): Filter to apply to results or None
        Returns:
            dict: Pagination API results
        """
        user_id = requester.user.to_string()

        if pagin_config.from_token:
            room_token = pagin_config.from_token.room_key
        else:
            pagin_config.from_token = (
                yield self.hs.get_event_sources().get_current_token_for_room(
                    room_id=room_id))
            room_token = pagin_config.from_token.room_key

        room_token = RoomStreamToken.parse(room_token)

        pagin_config.from_token = pagin_config.from_token.copy_and_replace(
            "room_key", str(room_token))

        source_config = pagin_config.get_source_config("room")

        with (yield self.pagination_lock.read(room_id)):
            membership, member_event_id = yield self.auth.check_in_room_or_world_readable(
                room_id, user_id)

            if source_config.direction == 'b':
                # if we're going backwards, we might need to backfill. This
                # requires that we have a topo token.
                if room_token.topological:
                    max_topo = room_token.topological
                else:
                    max_topo = yield self.store.get_max_topological_token(
                        room_id, room_token.stream)

                if membership == Membership.LEAVE:
                    # If they have left the room then clamp the token to be before
                    # they left the room, to save the effort of loading from the
                    # database.
                    leave_token = yield self.store.get_topological_token_for_event(
                        member_event_id)
                    leave_token = RoomStreamToken.parse(leave_token)
                    if leave_token.topological < max_topo:
                        source_config.from_key = str(leave_token)

                yield self.hs.get_handlers().federation_handler.maybe_backfill(
                    room_id, max_topo)

            events, next_key = yield self.store.paginate_room_events(
                room_id=room_id,
                from_key=source_config.from_key,
                to_key=source_config.to_key,
                direction=source_config.direction,
                limit=source_config.limit,
                event_filter=event_filter,
            )

            next_token = pagin_config.from_token.copy_and_replace(
                "room_key", next_key)

        if events:
            if event_filter:
                events = event_filter.filter(events)

            events = yield filter_events_for_client(
                self.store,
                user_id,
                events,
                is_peeking=(member_event_id is None),
            )

        if not events:
            defer.returnValue({
                "chunk": [],
                "start": pagin_config.from_token.to_string(),
                "end": next_token.to_string(),
            })

        state = None
        if event_filter and event_filter.lazy_load_members(
        ) and len(events) > 0:
            # TODO: remove redundant members

            # FIXME: we also care about invite targets etc.
            state_filter = StateFilter.from_types(
                (EventTypes.Member, event.sender) for event in events)

            state_ids = yield self.store.get_state_ids_for_event(
                events[0].event_id,
                state_filter=state_filter,
            )

            if state_ids:
                state = yield self.store.get_events(list(state_ids.values()))
                state = state.values()

        time_now = self.clock.time_msec()

        chunk = {
            "chunk":
            [serialize_event(e, time_now, as_client_event) for e in events],
            "start":
            pagin_config.from_token.to_string(),
            "end":
            next_token.to_string(),
        }

        if state:
            chunk["state"] = [
                serialize_event(e, time_now, as_client_event) for e in state
            ]

        defer.returnValue(chunk)
예제 #37
0
파일: mailer.py 프로젝트: samuelyi/synapse
    async def _get_message_vars(
            self, notif: EmailPushAction, event: EventBase,
            room_state_ids: StateMap[str]) -> Optional[MessageVars]:
        """
        Generate the variables for a single event, if possible.

        Args:
            notif: The outstanding notification for this room.
            event: The event under consideration.
            room_state_ids: The event IDs of the current room state.

        Returns:
             A dictionary to be added to the template context, or None if the
             event cannot be processed.
        """
        if event.type != EventTypes.Message and event.type != EventTypes.Encrypted:
            return None

        # Get the sender's name and avatar from the room state.
        type_state_key = ("m.room.member", event.sender)
        sender_state_event_id = room_state_ids.get(type_state_key)
        if sender_state_event_id:
            sender_state_event: Optional[
                EventBase] = await self.store.get_event(sender_state_event_id)
        else:
            # Attempt to check the historical state for the room.
            historical_state = await self._state_storage_controller.get_state_for_event(
                event.event_id, StateFilter.from_types((type_state_key, )))
            sender_state_event = historical_state.get(type_state_key)

        if sender_state_event:
            sender_name = name_from_member_event(sender_state_event)
            sender_avatar_url: Optional[str] = sender_state_event.content.get(
                "avatar_url")
        else:
            # No state could be found, fallback to the MXID.
            sender_name = event.sender
            sender_avatar_url = None

        # 'hash' for deterministically picking default images: use
        # sender_hash % the number of default images to choose from
        sender_hash = string_ordinal_total(event.sender)

        ret: MessageVars = {
            "event_type": event.type,
            "is_historical": event.event_id != notif.event_id,
            "id": event.event_id,
            "ts": event.origin_server_ts,
            "sender_name": sender_name,
            "sender_avatar_url": sender_avatar_url,
            "sender_hash": sender_hash,
        }

        # Encrypted messages don't have any additional useful information.
        if event.type == EventTypes.Encrypted:
            return ret

        msgtype = event.content.get("msgtype")
        if not isinstance(msgtype, str):
            msgtype = None

        ret["msgtype"] = msgtype

        if msgtype == "m.text":
            self._add_text_message_vars(ret, event)
        elif msgtype == "m.image":
            self._add_image_message_vars(ret, event)

        if "body" in event.content:
            ret["body_text_plain"] = event.content["body"]

        return ret
예제 #38
0
async def filter_events_for_client(
    storage: Storage,
    user_id,
    events,
    is_peeking=False,
    always_include_ids=frozenset(),
    filter_send_to_client=True,
):
    """
    Check which events a user is allowed to see. If the user can see the event but its
    sender asked for their data to be erased, prune the content of the event.

    Args:
        storage
        user_id(str): user id to be checked
        events(list[synapse.events.EventBase]): sequence of events to be checked
        is_peeking(bool): should be True if:
          * the user is not currently a member of the room, and:
          * the user has not been a member of the room since the given
            events
        always_include_ids (set(event_id)): set of event ids to specifically
            include (unless sender is ignored)
        filter_send_to_client (bool): Whether we're checking an event that's going to be
            sent to a client. This might not always be the case since this function can
            also be called to check whether a user can see the state at a given point.

    Returns:
        list[synapse.events.EventBase]
    """
    # Filter out events that have been soft failed so that we don't relay them
    # to clients.
    events = [e for e in events if not e.internal_metadata.is_soft_failed()]

    types = ((EventTypes.RoomHistoryVisibility, ""), (EventTypes.Member, user_id))
    event_id_to_state = await storage.state.get_state_for_events(
        frozenset(e.event_id for e in events),
        state_filter=StateFilter.from_types(types),
    )

    ignore_dict_content = await storage.main.get_global_account_data_by_type_for_user(
        AccountDataTypes.IGNORED_USER_LIST, user_id
    )

    ignore_list = frozenset()
    if ignore_dict_content:
        ignored_users_dict = ignore_dict_content.get("ignored_users", {})
        if isinstance(ignored_users_dict, dict):
            ignore_list = frozenset(ignored_users_dict.keys())

    erased_senders = await storage.main.are_users_erased((e.sender for e in events))

    if filter_send_to_client:
        room_ids = {e.room_id for e in events}
        retention_policies = {}

        for room_id in room_ids:
            retention_policies[
                room_id
            ] = await storage.main.get_retention_policy_for_room(room_id)

    def allowed(event):
        """
        Args:
            event (synapse.events.EventBase): event to check

        Returns:
            None|EventBase:
               None if the user cannot see this event at all

               a redacted copy of the event if they can only see a redacted
               version

               the original event if they can see it as normal.
        """
        # Only run some checks if these events aren't about to be sent to clients. This is
        # because, if this is not the case, we're probably only checking if the users can
        # see events in the room at that point in the DAG, and that shouldn't be decided
        # on those checks.
        if filter_send_to_client:
            if event.type == EventTypes.Dummy:
                return None

            if not event.is_state() and event.sender in ignore_list:
                return None

            # Until MSC2261 has landed we can't redact malicious alias events, so for
            # now we temporarily filter out m.room.aliases entirely to mitigate
            # abuse, while we spec a better solution to advertising aliases
            # on rooms.
            if event.type == EventTypes.Aliases:
                return None

            # Don't try to apply the room's retention policy if the event is a state
            # event, as MSC1763 states that retention is only considered for non-state
            # events.
            if not event.is_state():
                retention_policy = retention_policies[event.room_id]
                max_lifetime = retention_policy.get("max_lifetime")

                if max_lifetime is not None:
                    oldest_allowed_ts = storage.main.clock.time_msec() - max_lifetime

                    if event.origin_server_ts < oldest_allowed_ts:
                        return None

        if event.event_id in always_include_ids:
            return event

        state = event_id_to_state[event.event_id]

        # get the room_visibility at the time of the event.
        visibility_event = state.get((EventTypes.RoomHistoryVisibility, ""), None)
        if visibility_event:
            visibility = visibility_event.content.get(
                "history_visibility", HistoryVisibility.SHARED
            )
        else:
            visibility = HistoryVisibility.SHARED

        if visibility not in VISIBILITY_PRIORITY:
            visibility = HistoryVisibility.SHARED

        # Always allow history visibility events on boundaries. This is done
        # by setting the effective visibility to the least restrictive
        # of the old vs new.
        if event.type == EventTypes.RoomHistoryVisibility:
            prev_content = event.unsigned.get("prev_content", {})
            prev_visibility = prev_content.get("history_visibility", None)

            if prev_visibility not in VISIBILITY_PRIORITY:
                prev_visibility = HistoryVisibility.SHARED

            new_priority = VISIBILITY_PRIORITY.index(visibility)
            old_priority = VISIBILITY_PRIORITY.index(prev_visibility)
            if old_priority < new_priority:
                visibility = prev_visibility

        # likewise, if the event is the user's own membership event, use
        # the 'most joined' membership
        membership = None
        if event.type == EventTypes.Member and event.state_key == user_id:
            membership = event.content.get("membership", None)
            if membership not in MEMBERSHIP_PRIORITY:
                membership = "leave"

            prev_content = event.unsigned.get("prev_content", {})
            prev_membership = prev_content.get("membership", None)
            if prev_membership not in MEMBERSHIP_PRIORITY:
                prev_membership = "leave"

            # Always allow the user to see their own leave events, otherwise
            # they won't see the room disappear if they reject the invite
            if membership == "leave" and (
                prev_membership == "join" or prev_membership == "invite"
            ):
                return event

            new_priority = MEMBERSHIP_PRIORITY.index(membership)
            old_priority = MEMBERSHIP_PRIORITY.index(prev_membership)
            if old_priority < new_priority:
                membership = prev_membership

        # otherwise, get the user's membership at the time of the event.
        if membership is None:
            membership_event = state.get((EventTypes.Member, user_id), None)
            if membership_event:
                membership = membership_event.membership

        # if the user was a member of the room at the time of the event,
        # they can see it.
        if membership == Membership.JOIN:
            return event

        # otherwise, it depends on the room visibility.

        if visibility == HistoryVisibility.JOINED:
            # we weren't a member at the time of the event, so we can't
            # see this event.
            return None

        elif visibility == HistoryVisibility.INVITED:
            # user can also see the event if they were *invited* at the time
            # of the event.
            return event if membership == Membership.INVITE else None

        elif visibility == HistoryVisibility.SHARED and is_peeking:
            # if the visibility is shared, users cannot see the event unless
            # they have *subequently* joined the room (or were members at the
            # time, of course)
            #
            # XXX: if the user has subsequently joined and then left again,
            # ideally we would share history up to the point they left. But
            # we don't know when they left. We just treat it as though they
            # never joined, and restrict access.
            return None

        # the visibility is either shared or world_readable, and the user was
        # not a member at the time. We allow it, provided the original sender
        # has not requested their data to be erased, in which case, we return
        # a redacted version.
        if erased_senders[event.sender]:
            return prune_event(event)

        return event

    # check each event: gives an iterable[None|EventBase]
    filtered_events = map(allowed, events)

    # remove the None entries
    filtered_events = filter(operator.truth, filtered_events)

    # we turn it into a list before returning it.
    return list(filtered_events)
예제 #39
0
파일: mailer.py 프로젝트: samuelyi/synapse
    async def _make_summary_text_from_member_events(
        self,
        room_id: str,
        notifs: List[EmailPushAction],
        room_state_ids: StateMap[str],
        notif_events: Dict[str, EventBase],
    ) -> str:
        """
        Make a summary text for the email when only a single room has notifications.

        Args:
            room_id: The ID of the room.
            notifs: The push actions for this room.
            room_state_ids: The state map for the room.
            notif_events: A map of event ID -> notification event.

        Returns:
            The summary text.
        """
        # If the room doesn't have a name, say who the messages
        # are from explicitly to avoid, "messages in the Bob room"

        # Find the latest event ID for each sender, note that the notifications
        # are already in descending received_ts.
        sender_ids = {}
        for n in notifs:
            sender = notif_events[n.event_id].sender
            if sender not in sender_ids:
                sender_ids[sender] = n.event_id

        # Get the actual member events (in order to calculate a pretty name for
        # the room).
        member_event_ids = []
        member_events = {}
        for sender_id, event_id in sender_ids.items():
            type_state_key = ("m.room.member", sender_id)
            sender_state_event_id = room_state_ids.get(type_state_key)
            if sender_state_event_id:
                member_event_ids.append(sender_state_event_id)
            else:
                # Attempt to check the historical state for the room.
                historical_state = (
                    await self._state_storage_controller.get_state_for_event(
                        event_id, StateFilter.from_types((type_state_key, ))))
                sender_state_event = historical_state.get(type_state_key)
                if sender_state_event:
                    member_events[event_id] = sender_state_event
        member_events.update(await self.store.get_events(member_event_ids))

        if not member_events:
            # No member events were found! Maybe the room is empty?
            # Fallback to the room ID (note that if there was a room name this
            # would already have been used previously).
            return self.email_subjects.messages_in_room % {
                "room": room_id,
                "app": self.app_name,
            }

        # There was a single sender.
        if len(member_events) == 1:
            return self.email_subjects.messages_from_person % {
                "person": descriptor_from_member_events(
                    member_events.values()),
                "app": self.app_name,
            }

        # There was more than one sender, use the first one and a tweaked template.
        return self.email_subjects.messages_from_person_and_others % {
            "person":
            descriptor_from_member_events(list(member_events.values())[:1]),
            "app":
            self.app_name,
        }
예제 #40
0
파일: room.py 프로젝트: skarrrr/synapse
    def clone_existing_room(self, requester, old_room_id, new_room_id,
                            new_room_version, tombstone_event_id):
        """Populate a new room based on an old room

        Args:
            requester (synapse.types.Requester): the user requesting the upgrade
            old_room_id (unicode): the id of the room to be replaced
            new_room_id (unicode): the id to give the new room (should already have been
                created with _gemerate_room_id())
            new_room_version (unicode): the new room version to use
            tombstone_event_id (unicode|str): the ID of the tombstone event in the old
                room.
        Returns:
            Deferred
        """
        user_id = requester.user.to_string()

        if not self.spam_checker.user_may_create_room(user_id):
            raise SynapseError(403, "You are not permitted to create rooms")

        creation_content = {
            "room_version": new_room_version,
            "predecessor": {
                "room_id": old_room_id,
                "event_id": tombstone_event_id
            },
        }

        # Check if old room was non-federatable

        # Get old room's create event
        old_room_create_event = yield self.store.get_create_event_for_room(
            old_room_id)

        # Check if the create event specified a non-federatable room
        if not old_room_create_event.content.get("m.federate", True):
            # If so, mark the new room as non-federatable as well
            creation_content["m.federate"] = False

        initial_state = dict()

        # Replicate relevant room events
        types_to_copy = (
            (EventTypes.JoinRules, ""),
            (EventTypes.Name, ""),
            (EventTypes.Topic, ""),
            (EventTypes.RoomHistoryVisibility, ""),
            (EventTypes.GuestAccess, ""),
            (EventTypes.RoomAvatar, ""),
            (EventTypes.Encryption, ""),
            (EventTypes.ServerACL, ""),
            (EventTypes.RelatedGroups, ""),
            (EventTypes.PowerLevels, ""),
        )

        old_room_state_ids = yield self.store.get_filtered_current_state_ids(
            old_room_id, StateFilter.from_types(types_to_copy))
        # map from event_id to BaseEvent
        old_room_state_events = yield self.store.get_events(
            old_room_state_ids.values())

        for k, old_event_id in iteritems(old_room_state_ids):
            old_event = old_room_state_events.get(old_event_id)
            if old_event:
                initial_state[k] = old_event.content

        # Resolve the minimum power level required to send any state event
        # We will give the upgrading user this power level temporarily (if necessary) such that
        # they are able to copy all of the state events over, then revert them back to their
        # original power level afterwards in _update_upgraded_room_pls

        # Copy over user power levels now as this will not be possible with >100PL users once
        # the room has been created

        power_levels = initial_state[(EventTypes.PowerLevels, "")]

        # Calculate the minimum power level needed to clone the room
        event_power_levels = power_levels.get("events", {})
        state_default = power_levels.get("state_default", 0)
        ban = power_levels.get("ban")
        needed_power_level = max(state_default, ban,
                                 max(event_power_levels.values()))

        # Raise the requester's power level in the new room if necessary
        current_power_level = power_levels["users"][requester.user.to_string()]
        if current_power_level < needed_power_level:
            # Assign this power level to the requester
            power_levels["users"][
                requester.user.to_string()] = needed_power_level

        # Set the power levels to the modified state
        initial_state[(EventTypes.PowerLevels, "")] = power_levels

        yield self._send_events_for_new_room(
            requester,
            new_room_id,
            # we expect to override all the presets with initial_state, so this is
            # somewhat arbitrary.
            preset_config=RoomCreationPreset.PRIVATE_CHAT,
            invite_list=[],
            initial_state=initial_state,
            creation_content=creation_content,
        )

        # Transfer membership events
        old_room_member_state_ids = yield self.store.get_filtered_current_state_ids(
            old_room_id, StateFilter.from_types([(EventTypes.Member, None)]))

        # map from event_id to BaseEvent
        old_room_member_state_events = yield self.store.get_events(
            old_room_member_state_ids.values())
        for k, old_event in iteritems(old_room_member_state_events):
            # Only transfer ban events
            if ("membership" in old_event.content
                    and old_event.content["membership"] == "ban"):
                yield self.room_member_handler.update_membership(
                    requester,
                    UserID.from_string(old_event["state_key"]),
                    new_room_id,
                    "ban",
                    ratelimit=False,
                    content=old_event.content,
                )
예제 #41
0
파일: room.py 프로젝트: DoubleMalt/synapse
    def clone_exiting_room(
            self, requester, old_room_id, new_room_id, new_room_version,
            tombstone_event_id,
    ):
        """Populate a new room based on an old room

        Args:
            requester (synapse.types.Requester): the user requesting the upgrade
            old_room_id (unicode): the id of the room to be replaced
            new_room_id (unicode): the id to give the new room (should already have been
                created with _gemerate_room_id())
            new_room_version (unicode): the new room version to use
            tombstone_event_id (unicode|str): the ID of the tombstone event in the old
                room.
        Returns:
            Deferred[None]
        """
        user_id = requester.user.to_string()

        if not self.spam_checker.user_may_create_room(user_id):
            raise SynapseError(403, "You are not permitted to create rooms")

        creation_content = {
            "room_version": new_room_version,
            "predecessor": {
                "room_id": old_room_id,
                "event_id": tombstone_event_id,
            }
        }

        initial_state = dict()

        types_to_copy = (
            (EventTypes.JoinRules, ""),
            (EventTypes.Name, ""),
            (EventTypes.Topic, ""),
            (EventTypes.RoomHistoryVisibility, ""),
            (EventTypes.GuestAccess, ""),
            (EventTypes.RoomAvatar, ""),
        )

        old_room_state_ids = yield self.store.get_filtered_current_state_ids(
            old_room_id, StateFilter.from_types(types_to_copy),
        )
        # map from event_id to BaseEvent
        old_room_state_events = yield self.store.get_events(old_room_state_ids.values())

        for k, old_event_id in iteritems(old_room_state_ids):
            old_event = old_room_state_events.get(old_event_id)
            if old_event:
                initial_state[k] = old_event.content

        yield self._send_events_for_new_room(
            requester,
            new_room_id,

            # we expect to override all the presets with initial_state, so this is
            # somewhat arbitrary.
            preset_config=RoomCreationPreset.PRIVATE_CHAT,

            invite_list=[],
            initial_state=initial_state,
            creation_content=creation_content,
        )