Exemplo n.º 1
0
                async def handle_event(event: EventBase) -> None:
                    # Only send events for this server.
                    send_on_behalf_of = event.internal_metadata.get_send_on_behalf_of(
                    )
                    is_mine = self.is_mine_id(event.sender)
                    if not is_mine and send_on_behalf_of is None:
                        return

                    if not event.internal_metadata.should_proactively_send():
                        return

                    try:
                        # Get the state from before the event.
                        # We need to make sure that this is the state from before
                        # the event and not from after it.
                        # Otherwise if the last member on a server in a room is
                        # banned then it won't receive the event because it won't
                        # be in the room after the ban.
                        destinations = await self.state.get_hosts_in_room_at_events(
                            event.room_id, event_ids=event.prev_event_ids())
                    except Exception:
                        logger.exception(
                            "Failed to calculate hosts in room for event: %s",
                            event.event_id,
                        )
                        return

                    destinations = {
                        d
                        for d in destinations
                        if self._federation_shard_config.should_handle(
                            self._instance_name, d)
                    }

                    if send_on_behalf_of is not None:
                        # If we are sending the event on behalf of another server
                        # then it already has the event and there is no reason to
                        # send the event to it.
                        destinations.discard(send_on_behalf_of)

                    logger.debug("Sending %s to %r", event, destinations)

                    if destinations:
                        self._send_pdu(event, destinations)

                        now = self.clock.time_msec()
                        ts = await self.store.get_received_ts(event.event_id)

                        synapse.metrics.event_processing_lag_by_event.labels(
                            "federation_sender").observe((now - ts) / 1000)
Exemplo n.º 2
0
                async def handle_event(event: EventBase) -> None:
                    # Only send events for this server.
                    send_on_behalf_of = event.internal_metadata.get_send_on_behalf_of(
                    )
                    is_mine = self.is_mine_id(event.sender)
                    if not is_mine and send_on_behalf_of is None:
                        return

                    if not event.internal_metadata.should_proactively_send():
                        return

                    try:
                        # Get the state from before the event.
                        # We need to make sure that this is the state from before
                        # the event and not from after it.
                        # Otherwise if the last member on a server in a room is
                        # banned then it won't receive the event because it won't
                        # be in the room after the ban.
                        destinations = await self.state.get_hosts_in_room_at_events(
                            event.room_id, event_ids=event.prev_event_ids())
                    except Exception:
                        logger.exception(
                            "Failed to calculate hosts in room for event: %s",
                            event.event_id,
                        )
                        return

                    destinations = set(destinations)

                    if send_on_behalf_of is not None:
                        # If we are sending the event on behalf of another server
                        # then it already has the event and there is no reason to
                        # send the event to it.
                        destinations.discard(send_on_behalf_of)

                    logger.debug("Sending %s to %r", event, destinations)

                    self._send_pdu(event, destinations)
Exemplo n.º 3
0
def _is_membership_change_allowed(event: EventBase,
                                  auth_events: StateMap[EventBase]) -> None:
    membership = event.content["membership"]

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

    target_user_id = event.state_key

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            if user_level < kick_level or user_level <= target_level:
                raise AuthError(403,
                                "You cannot kick user %s." % target_user_id)
    elif Membership.BAN == membership:
        if user_level < ban_level or user_level <= target_level:
            raise AuthError(403, "You don't have permission to ban")
    else:
        raise AuthError(500, "Unknown membership %s" % membership)
Exemplo n.º 4
0
def _is_membership_change_allowed(
    room_version: RoomVersion, event: EventBase, auth_events: StateMap[EventBase]
) -> None:
    """
    Confirms that the event which changes membership is an allowed change.

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

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

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

    target_user_id = event.state_key

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

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

    caller_in_room = caller and caller.membership == Membership.JOIN
    caller_invited = caller and caller.membership == Membership.INVITE
    caller_knocked = (
        caller
        and room_version.msc2403_knocking
        and caller.membership == Membership.KNOCK
    )

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

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

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

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

    invite_level = get_named_level(auth_events, "invite", 0)
    ban_level = get_named_level(auth_events, "ban", 50)

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

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

    # Require the user to be in the room for membership changes other than join/knock.
    if Membership.JOIN != membership and (
        RoomVersion.msc2403_knocking and Membership.KNOCK != membership
    ):
        # If the user has been invited or has knocked, they are allowed to change their
        # membership event to leave
        if (
            (caller_invited or caller_knocked)
            and Membership.LEAVE == membership
            and target_user_id == event.user_id
        ):
            return

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

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

        # Invites are valid iff caller is in the room and target isn't.
        if target_banned:
            raise AuthError(403, "%s is banned from the room" % (target_user_id,))
        elif target_in_room:  # the target is already in the room.
            raise AuthError(403, "%s is already in the room." % target_user_id)
        else:
            if user_level < invite_level:
                raise AuthError(403, "You don't have permission to invite users")
    elif Membership.JOIN == membership:
        # Joins are valid iff caller == target and:
        # * They are not banned.
        # * They are accepting a previously sent invitation.
        # * They are already joined (it's a NOOP).
        # * The room is public.
        # * The room is restricted and the user meets the allows rules.
        if event.user_id != target_user_id:
            raise AuthError(403, "Cannot force another user to join.")
        elif target_banned:
            raise AuthError(403, "You are banned from this room")
        elif join_rule == JoinRules.PUBLIC:
            pass
        elif room_version.msc3083_join_rules and join_rule == JoinRules.RESTRICTED:
            # This is the same as public, but the event must contain a reference
            # to the server who authorised the join. If the event does not contain
            # the proper content it is rejected.
            #
            # Note that if the caller is in the room or invited, then they do
            # not need to meet the allow rules.
            if not caller_in_room and not caller_invited:
                authorising_user = event.content.get(
                    EventContentFields.AUTHORISING_USER
                )

                if authorising_user is None:
                    raise AuthError(403, "Join event is missing authorising user.")

                # The authorising user must be in the room.
                key = (EventTypes.Member, authorising_user)
                member_event = auth_events.get(key)
                _check_joined_room(member_event, authorising_user, event.room_id)

                authorising_user_level = get_user_power_level(
                    authorising_user, auth_events
                )
                if authorising_user_level < invite_level:
                    raise AuthError(403, "Join event authorised by invalid server.")

        elif join_rule == JoinRules.INVITE or (
            room_version.msc2403_knocking and join_rule == JoinRules.KNOCK
        ):
            if not caller_in_room and not caller_invited:
                raise AuthError(403, "You are not invited to this room.")
        else:
            # TODO (erikj): may_join list
            # TODO (erikj): private rooms
            raise AuthError(403, "You are not allowed to join this room")
    elif Membership.LEAVE == membership:
        # TODO (erikj): Implement kicks.
        if target_banned and user_level < ban_level:
            raise AuthError(403, "You cannot unban user %s." % (target_user_id,))
        elif target_user_id != event.user_id:
            kick_level = get_named_level(auth_events, "kick", 50)

            if user_level < kick_level or user_level <= target_level:
                raise AuthError(403, "You cannot kick user %s." % target_user_id)
    elif Membership.BAN == membership:
        if user_level < ban_level or user_level <= target_level:
            raise AuthError(403, "You don't have permission to ban")
    elif room_version.msc2403_knocking and Membership.KNOCK == membership:
        if join_rule != JoinRules.KNOCK:
            raise AuthError(403, "You don't have permission to knock")
        elif target_user_id != event.user_id:
            raise AuthError(403, "You cannot knock for other users")
        elif target_in_room:
            raise AuthError(403, "You cannot knock on a room you are already in")
        elif caller_invited:
            raise AuthError(403, "You are already invited to this room")
        elif target_banned:
            raise AuthError(403, "You are banned from this room")
    else:
        raise AuthError(500, "Unknown membership %s" % membership)
Exemplo n.º 5
0
    def compute_event_context(self,
                              event: EventBase,
                              old_state: Optional[Iterable[EventBase]] = None):
        """Build an EventContext structure for the event.

        This works out what the current state should be for the event, and
        generates a new state group if necessary.

        Args:
            event:
            old_state: The state at the event if it can't be
                calculated from existing events. This is normally only specified
                when receiving an event from federation where we don't have the
                prev events for, e.g. when backfilling.
        Returns:
            synapse.events.snapshot.EventContext:
        """

        if event.internal_metadata.is_outlier():
            # If this is an outlier, then we know it shouldn't have any current
            # state. Certainly store.get_current_state won't return any, and
            # persisting the event won't store the state group.

            # FIXME: why do we populate current_state_ids? I thought the point was
            # that we weren't supposed to have any state for outliers?
            if old_state:
                prev_state_ids = {(s.type, s.state_key): s.event_id
                                  for s in old_state}
                if event.is_state():
                    current_state_ids = dict(prev_state_ids)
                    key = (event.type, event.state_key)
                    current_state_ids[key] = event.event_id
                else:
                    current_state_ids = prev_state_ids
            else:
                current_state_ids = {}
                prev_state_ids = {}

            # We don't store state for outliers, so we don't generate a state
            # group for it.
            context = EventContext.with_state(
                state_group=None,
                state_group_before_event=None,
                current_state_ids=current_state_ids,
                prev_state_ids=prev_state_ids,
            )

            return context

        #
        # first of all, figure out the state before the event
        #

        if old_state:
            # if we're given the state before the event, then we use that
            state_ids_before_event = {(s.type, s.state_key): s.event_id
                                      for s in old_state}
            state_group_before_event = None
            state_group_before_event_prev_group = None
            deltas_to_state_group_before_event = None

        else:
            # otherwise, we'll need to resolve the state across the prev_events.
            logger.debug(
                "calling resolve_state_groups from compute_event_context")

            entry = yield self.resolve_state_groups_for_events(
                event.room_id, event.prev_event_ids())

            state_ids_before_event = entry.state
            state_group_before_event = entry.state_group
            state_group_before_event_prev_group = entry.prev_group
            deltas_to_state_group_before_event = entry.delta_ids

        #
        # make sure that we have a state group at that point. If it's not a state event,
        # that will be the state group for the new event. If it *is* a state event,
        # it might get rejected (in which case we'll need to persist it with the
        # previous state group)
        #

        if not state_group_before_event:
            state_group_before_event = yield self.state_store.store_state_group(
                event.event_id,
                event.room_id,
                prev_group=state_group_before_event_prev_group,
                delta_ids=deltas_to_state_group_before_event,
                current_state_ids=state_ids_before_event,
            )

            # XXX: can we update the state cache entry for the new state group? or
            # could we set a flag on resolve_state_groups_for_events to tell it to
            # always make a state group?

        #
        # now if it's not a state event, we're done
        #

        if not event.is_state():
            return EventContext.with_state(
                state_group_before_event=state_group_before_event,
                state_group=state_group_before_event,
                current_state_ids=state_ids_before_event,
                prev_state_ids=state_ids_before_event,
                prev_group=state_group_before_event_prev_group,
                delta_ids=deltas_to_state_group_before_event,
            )

        #
        # otherwise, we'll need to create a new state group for after the event
        #

        key = (event.type, event.state_key)
        if key in state_ids_before_event:
            replaces = state_ids_before_event[key]
            if replaces != event.event_id:
                event.unsigned["replaces_state"] = replaces

        state_ids_after_event = dict(state_ids_before_event)
        state_ids_after_event[key] = event.event_id
        delta_ids = {key: event.event_id}

        state_group_after_event = yield self.state_store.store_state_group(
            event.event_id,
            event.room_id,
            prev_group=state_group_before_event,
            delta_ids=delta_ids,
            current_state_ids=state_ids_after_event,
        )

        return EventContext.with_state(
            state_group=state_group_after_event,
            state_group_before_event=state_group_before_event,
            current_state_ids=state_ids_after_event,
            prev_state_ids=state_ids_before_event,
            prev_group=state_group_before_event,
            delta_ids=delta_ids,
        )
Exemplo n.º 6
0
    async def compute_event_context(
            self,
            event: EventBase,
            old_state: Optional[Iterable[EventBase]] = None) -> EventContext:
        """Build an EventContext structure for a non-outlier event.

        (for an outlier, call EventContext.for_outlier directly)

        This works out what the current state should be for the event, and
        generates a new state group if necessary.

        Args:
            event:
            old_state: The state at the event if it can't be
                calculated from existing events. This is normally only specified
                when receiving an event from federation where we don't have the
                prev events for, e.g. when backfilling.
        Returns:
            The event context.
        """

        assert not event.internal_metadata.is_outlier()

        #
        # first of all, figure out the state before the event
        #

        if old_state:
            # if we're given the state before the event, then we use that
            state_ids_before_event: StateMap[str] = {(s.type, s.state_key):
                                                     s.event_id
                                                     for s in old_state}
            state_group_before_event = None
            state_group_before_event_prev_group = None
            deltas_to_state_group_before_event = None
            entry = None

        else:
            # otherwise, we'll need to resolve the state across the prev_events.
            logger.debug(
                "calling resolve_state_groups from compute_event_context")

            entry = await self.resolve_state_groups_for_events(
                event.room_id, event.prev_event_ids())

            state_ids_before_event = entry.state
            state_group_before_event = entry.state_group
            state_group_before_event_prev_group = entry.prev_group
            deltas_to_state_group_before_event = entry.delta_ids

        #
        # make sure that we have a state group at that point. If it's not a state event,
        # that will be the state group for the new event. If it *is* a state event,
        # it might get rejected (in which case we'll need to persist it with the
        # previous state group)
        #

        if not state_group_before_event:
            state_group_before_event = await self.state_store.store_state_group(
                event.event_id,
                event.room_id,
                prev_group=state_group_before_event_prev_group,
                delta_ids=deltas_to_state_group_before_event,
                current_state_ids=state_ids_before_event,
            )

            # Assign the new state group to the cached state entry.
            #
            # Note that this can race in that we could generate multiple state
            # groups for the same state entry, but that is just inefficient
            # rather than dangerous.
            if entry and entry.state_group is None:
                entry.state_group = state_group_before_event

        #
        # now if it's not a state event, we're done
        #

        if not event.is_state():
            return EventContext.with_state(
                state_group_before_event=state_group_before_event,
                state_group=state_group_before_event,
                current_state_ids=state_ids_before_event,
                prev_state_ids=state_ids_before_event,
                prev_group=state_group_before_event_prev_group,
                delta_ids=deltas_to_state_group_before_event,
            )

        #
        # otherwise, we'll need to create a new state group for after the event
        #

        key = (event.type, event.state_key)
        if key in state_ids_before_event:
            replaces = state_ids_before_event[key]
            if replaces != event.event_id:
                event.unsigned["replaces_state"] = replaces

        state_ids_after_event = dict(state_ids_before_event)
        state_ids_after_event[key] = event.event_id
        delta_ids = {key: event.event_id}

        state_group_after_event = await self.state_store.store_state_group(
            event.event_id,
            event.room_id,
            prev_group=state_group_before_event,
            delta_ids=delta_ids,
            current_state_ids=state_ids_after_event,
        )

        return EventContext.with_state(
            state_group=state_group_after_event,
            state_group_before_event=state_group_before_event,
            current_state_ids=state_ids_after_event,
            prev_state_ids=state_ids_before_event,
            prev_group=state_group_before_event,
            delta_ids=delta_ids,
        )
Exemplo n.º 7
0
    async def compute_event_context(
        self,
        event: EventBase,
        state_ids_before_event: Optional[StateMap[str]] = None,
        partial_state: bool = False,
    ) -> EventContext:
        """Build an EventContext structure for a non-outlier event.

        (for an outlier, call EventContext.for_outlier directly)

        This works out what the current state should be for the event, and
        generates a new state group if necessary.

        Args:
            event:
            state_ids_before_event: The event ids of the state before the event if
                it can't be calculated from existing events. This is normally
                only specified when receiving an event from federation where we
                don't have the prev events, e.g. when backfilling.
            partial_state: True if `state_ids_before_event` is partial and omits
                non-critical membership events
        Returns:
            The event context.
        """

        assert not event.internal_metadata.is_outlier()

        #
        # first of all, figure out the state before the event, unless we
        # already have it.
        #
        if state_ids_before_event:
            # if we're given the state before the event, then we use that
            state_group_before_event = None
            state_group_before_event_prev_group = None
            deltas_to_state_group_before_event = None
            entry = None

        else:
            # otherwise, we'll need to resolve the state across the prev_events.

            # partial_state should not be set explicitly in this case:
            # we work it out dynamically
            assert not partial_state

            # if any of the prev-events have partial state, so do we.
            # (This is slightly racy - the prev-events might get fixed up before we use
            # their states - but I don't think that really matters; it just means we
            # might redundantly recalculate the state for this event later.)
            prev_event_ids = event.prev_event_ids()
            incomplete_prev_events = await self.store.get_partial_state_events(
                prev_event_ids
            )
            if any(incomplete_prev_events.values()):
                logger.debug(
                    "New/incoming event %s refers to prev_events %s with partial state",
                    event.event_id,
                    [k for (k, v) in incomplete_prev_events.items() if v],
                )
                partial_state = True

            logger.debug("calling resolve_state_groups from compute_event_context")
            entry = await self.resolve_state_groups_for_events(
                event.room_id, event.prev_event_ids()
            )

            state_ids_before_event = entry.state
            state_group_before_event = entry.state_group
            state_group_before_event_prev_group = entry.prev_group
            deltas_to_state_group_before_event = entry.delta_ids

        #
        # make sure that we have a state group at that point. If it's not a state event,
        # that will be the state group for the new event. If it *is* a state event,
        # it might get rejected (in which case we'll need to persist it with the
        # previous state group)
        #

        if not state_group_before_event:
            state_group_before_event = (
                await self._state_storage_controller.store_state_group(
                    event.event_id,
                    event.room_id,
                    prev_group=state_group_before_event_prev_group,
                    delta_ids=deltas_to_state_group_before_event,
                    current_state_ids=state_ids_before_event,
                )
            )

            # Assign the new state group to the cached state entry.
            #
            # Note that this can race in that we could generate multiple state
            # groups for the same state entry, but that is just inefficient
            # rather than dangerous.
            if entry and entry.state_group is None:
                entry.state_group = state_group_before_event

        #
        # now if it's not a state event, we're done
        #

        if not event.is_state():
            return EventContext.with_state(
                storage=self._storage_controllers,
                state_group_before_event=state_group_before_event,
                state_group=state_group_before_event,
                state_delta_due_to_event={},
                prev_group=state_group_before_event_prev_group,
                delta_ids=deltas_to_state_group_before_event,
                partial_state=partial_state,
            )

        #
        # otherwise, we'll need to create a new state group for after the event
        #

        key = (event.type, event.state_key)
        if key in state_ids_before_event:
            replaces = state_ids_before_event[key]
            if replaces != event.event_id:
                event.unsigned["replaces_state"] = replaces

        state_ids_after_event = dict(state_ids_before_event)
        state_ids_after_event[key] = event.event_id
        delta_ids = {key: event.event_id}

        state_group_after_event = (
            await self._state_storage_controller.store_state_group(
                event.event_id,
                event.room_id,
                prev_group=state_group_before_event,
                delta_ids=delta_ids,
                current_state_ids=state_ids_after_event,
            )
        )

        return EventContext.with_state(
            storage=self._storage_controllers,
            state_group=state_group_after_event,
            state_group_before_event=state_group_before_event,
            state_delta_due_to_event=delta_ids,
            prev_group=state_group_before_event,
            delta_ids=delta_ids,
            partial_state=partial_state,
        )
Exemplo n.º 8
0
                async def handle_event(event: EventBase) -> None:
                    # Only send events for this server.
                    send_on_behalf_of = event.internal_metadata.get_send_on_behalf_of()
                    is_mine = self.is_mine_id(event.sender)
                    if not is_mine and send_on_behalf_of is None:
                        logger.debug("Not sending remote-origin event %s", event)
                        return

                    # We also want to not send out-of-band membership events.
                    #
                    # OOB memberships are used in three (and a half) situations:
                    #
                    # (1) invite events which we have received over federation. Those
                    #     will have a `sender` on a different server, so will be
                    #     skipped by the "is_mine" test above anyway.
                    #
                    # (2) rejections of invites to federated rooms - either remotely
                    #     or locally generated. (Such rejections are normally
                    #     created via federation, in which case the remote server is
                    #     responsible for sending out the rejection. If that fails,
                    #     we'll create a leave event locally, but that's only really
                    #     for the benefit of the invited user - we don't have enough
                    #     information to send it out over federation).
                    #
                    # (2a) rescinded knocks. These are identical to rejected invites.
                    #
                    # (3) knock events which we have sent over federation. As with
                    #     invite rejections, the remote server should send them out to
                    #     the federation.
                    #
                    # So, in all the above cases, we want to ignore such events.
                    #
                    # OOB memberships are always(?) outliers anyway, so if we *don't*
                    # ignore them, we'll get an exception further down when we try to
                    # fetch the membership list for the room.
                    #
                    # Arguably, we could equivalently ignore all outliers here, since
                    # in theory the only way for an outlier with a local `sender` to
                    # exist is by being an OOB membership (via one of (2), (2a) or (3)
                    # above).
                    #
                    if event.internal_metadata.is_out_of_band_membership():
                        logger.debug("Not sending OOB membership event %s", event)
                        return

                    # Finally, there are some other events that we should not send out
                    # until someone asks for them. They are explicitly flagged as such
                    # with `proactively_send: False`.
                    if not event.internal_metadata.should_proactively_send():
                        logger.debug(
                            "Not sending event with proactively_send=false: %s", event
                        )
                        return

                    destinations: Optional[Collection[str]] = None
                    if not event.prev_event_ids():
                        # If there are no prev event IDs then the state is empty
                        # and so no remote servers in the room
                        destinations = set()
                    else:
                        # We check the external cache for the destinations, which is
                        # stored per state group.

                        sg = await self._external_cache.get(
                            "event_to_prev_state_group", event.event_id
                        )
                        if sg:
                            destinations = await self._external_cache.get(
                                "get_joined_hosts", str(sg)
                            )

                    if destinations is None:
                        try:
                            # Get the state from before the event.
                            # We need to make sure that this is the state from before
                            # the event and not from after it.
                            # Otherwise if the last member on a server in a room is
                            # banned then it won't receive the event because it won't
                            # be in the room after the ban.
                            destinations = await self.state.get_hosts_in_room_at_events(
                                event.room_id, event_ids=event.prev_event_ids()
                            )
                        except Exception:
                            logger.exception(
                                "Failed to calculate hosts in room for event: %s",
                                event.event_id,
                            )
                            return

                    sharded_destinations = {
                        d
                        for d in destinations
                        if self._federation_shard_config.should_handle(
                            self._instance_name, d
                        )
                    }

                    if send_on_behalf_of is not None:
                        # If we are sending the event on behalf of another server
                        # then it already has the event and there is no reason to
                        # send the event to it.
                        sharded_destinations.discard(send_on_behalf_of)

                    logger.debug("Sending %s to %r", event, sharded_destinations)

                    if sharded_destinations:
                        await self._send_pdu(event, sharded_destinations)

                        now = self.clock.time_msec()
                        ts = await self.store.get_received_ts(event.event_id)
                        assert ts is not None
                        synapse.metrics.event_processing_lag_by_event.labels(
                            "federation_sender"
                        ).observe((now - ts) / 1000)