Beispiel #1
0
    async def _do_send_leave(self, destination: str, pdu: EventBase) -> JsonDict:
        time_now = self._clock.time_msec()

        try:
            return await self.transport_layer.send_leave_v2(
                destination=destination,
                room_id=pdu.room_id,
                event_id=pdu.event_id,
                content=pdu.get_pdu_json(time_now),
            )
        except HttpResponseException as e:
            if e.code in [400, 404]:
                err = e.to_synapse_error()

                # If we receive an error response that isn't a generic error, or an
                # unrecognised endpoint error, we  assume that the remote understands
                # the v2 invite API and this is a legitimate error.
                if err.errcode not in [Codes.UNKNOWN, Codes.UNRECOGNIZED]:
                    raise err
            else:
                raise e.to_synapse_error()

        logger.debug("Couldn't send_leave with the v2 API, falling back to the v1 API")

        resp = await self.transport_layer.send_leave_v1(
            destination=destination,
            room_id=pdu.room_id,
            event_id=pdu.event_id,
            content=pdu.get_pdu_json(time_now),
        )

        # We expect the v1 API to respond with [200, content], so we only return the
        # content.
        return resp[1]
Beispiel #2
0
def check_event_content_hash(event: EventBase,
                             hash_algorithm: Hasher = hashlib.sha256) -> bool:
    """Check whether the hash for this PDU matches the contents"""
    name, expected_hash = compute_content_hash(event.get_pdu_json(),
                                               hash_algorithm)
    logger.debug(
        "Verifying content hash on %s (expecting: %s)",
        event.event_id,
        encode_base64(expected_hash),
    )

    # some malformed events lack a 'hashes'. Protect against it being missing
    # or a weird type by basically treating it the same as an unhashed event.
    hashes = event.get("hashes")
    # nb it might be a frozendict or a dict
    if not isinstance(hashes, collections.abc.Mapping):
        raise SynapseError(400, "Malformed 'hashes': %s" % (type(hashes), ),
                           Codes.UNAUTHORIZED)

    if name not in hashes:
        raise SynapseError(
            400,
            "Algorithm %s not in hashes %s" % (name, list(hashes)),
            Codes.UNAUTHORIZED,
        )
    message_hash_base64 = hashes[name]
    try:
        message_hash_bytes = decode_base64(message_hash_base64)
    except Exception:
        raise SynapseError(400, "Invalid base64: %s" % (message_hash_base64, ),
                           Codes.UNAUTHORIZED)
    return message_hash_bytes == expected_hash
Beispiel #3
0
    async def _do_send_leave(self, destination: str,
                             pdu: EventBase) -> JsonDict:
        time_now = self._clock.time_msec()

        try:
            return await self.transport_layer.send_leave_v2(
                destination=destination,
                room_id=pdu.room_id,
                event_id=pdu.event_id,
                content=pdu.get_pdu_json(time_now),
            )
        except HttpResponseException as e:
            # If an error is received that is due to an unrecognised endpoint,
            # fallback to the v1 endpoint. Otherwise consider it a legitmate error
            # and raise.
            if not self._is_unknown_endpoint(e):
                raise

        logger.debug(
            "Couldn't send_leave with the v2 API, falling back to the v1 API")

        resp = await self.transport_layer.send_leave_v1(
            destination=destination,
            room_id=pdu.room_id,
            event_id=pdu.event_id,
            content=pdu.get_pdu_json(time_now),
        )

        # We expect the v1 API to respond with [200, content], so we only return the
        # content.
        return resp[1]
    async def check_event_allowed(self, event: EventBase,
                                  context: EventContext) -> Union[bool, dict]:
        """Check if a provided event should be allowed in the given context.

        The module can return:
            * True: the event is allowed.
            * False: the event is not allowed, and should be rejected with M_FORBIDDEN.
            * a dict: replacement event data.

        Args:
            event: The event to be checked.
            context: The context of the event.

        Returns:
            The result from the ThirdPartyRules module, as above
        """
        if self.third_party_rules is None:
            return True

        prev_state_ids = await context.get_prev_state_ids()

        # Retrieve the state events from the database.
        events = await self.store.get_events(prev_state_ids.values())
        state_events = {(ev.type, ev.state_key): ev for ev in events.values()}

        # Ensure that the event is frozen, to make sure that the module is not tempted
        # to try to modify it. Any attempt to modify it at this point will invalidate
        # the hashes and signatures.
        event.freeze()

        return await self.third_party_rules.check_event_allowed(
            event, state_events)
Beispiel #5
0
def _should_count_as_unread(event: EventBase, context: EventContext) -> bool:
    # Exclude rejected and soft-failed events.
    if context.rejected or event.internal_metadata.is_soft_failed():
        return False

    # Exclude notices.
    if (not event.is_state() and event.type == EventTypes.Message
            and event.content.get("msgtype") == "m.notice"):
        return False

    # Exclude edits.
    relates_to = event.content.get("m.relates_to", {})
    if relates_to.get("rel_type") == RelationTypes.REPLACE:
        return False

    # Mark events that have a non-empty string body as unread.
    body = event.content.get("body")
    if isinstance(body, str) and body:
        return True

    # Mark some state events as unread.
    if event.is_state() and event.type in STATE_EVENT_TYPES_TO_MARK_UNREAD:
        return True

    # Mark encrypted events as unread.
    if not event.is_state() and event.type == EventTypes.Encrypted:
        return True

    return False
Beispiel #6
0
    async def serialize(self, event: EventBase,
                        store: "DataStore") -> JsonDict:
        """Converts self to a type that can be serialized as JSON, and then
        deserialized by `deserialize`

        Args:
            event: The event that this context relates to

        Returns:
            The serialized event.
        """

        # We don't serialize the full state dicts, instead they get pulled out
        # of the DB on the other side. However, the other side can't figure out
        # the prev_state_ids, so if we're a state event we include the event
        # id that we replaced in the state.
        if event.is_state():
            prev_state_ids = await self.get_prev_state_ids()
            prev_state_id = prev_state_ids.get((event.type, event.state_key))
        else:
            prev_state_id = None

        return {
            "prev_state_id": prev_state_id,
            "event_type": event.type,
            "event_state_key": event.get_state_key(),
            "state_group": self._state_group,
            "state_group_before_event": self.state_group_before_event,
            "rejected": self.rejected,
            "prev_group": self.prev_group,
            "delta_ids": _encode_state_dict(self.delta_ids),
            "app_service_id":
            self.app_service.id if self.app_service else None,
        }
Beispiel #7
0
    async def _do_send_join(self, room_version: RoomVersion, destination: str,
                            pdu: EventBase) -> SendJoinResponse:
        time_now = self._clock.time_msec()

        try:
            return await self.transport_layer.send_join_v2(
                room_version=room_version,
                destination=destination,
                room_id=pdu.room_id,
                event_id=pdu.event_id,
                content=pdu.get_pdu_json(time_now),
            )
        except HttpResponseException as e:
            # If an error is received that is due to an unrecognised endpoint,
            # fallback to the v1 endpoint. Otherwise consider it a legitmate error
            # and raise.
            if not self._is_unknown_endpoint(e):
                raise

        logger.debug(
            "Couldn't send_join with the v2 API, falling back to the v1 API")

        return await self.transport_layer.send_join_v1(
            room_version=room_version,
            destination=destination,
            room_id=pdu.room_id,
            event_id=pdu.event_id,
            content=pdu.get_pdu_json(time_now),
        )
Beispiel #8
0
    async def check_event_allowed(
        self, event: EventBase, context: EventContext
    ) -> Tuple[bool, Optional[dict]]:
        """Check if a provided event should be allowed in the given context.

        The module can return:
            * True: the event is allowed.
            * False: the event is not allowed, and should be rejected with M_FORBIDDEN.

        If the event is allowed, the module can also return a dictionary to use as a
        replacement for the event.

        Args:
            event: The event to be checked.
            context: The context of the event.

        Returns:
            The result from the ThirdPartyRules module, as above.
        """
        # Bail out early without hitting the store if we don't have any callbacks to run.
        if len(self._check_event_allowed_callbacks) == 0:
            return True, None

        prev_state_ids = await context.get_prev_state_ids()

        # Retrieve the state events from the database.
        events = await self.store.get_events(prev_state_ids.values())
        state_events = {(ev.type, ev.state_key): ev for ev in events.values()}

        # Ensure that the event is frozen, to make sure that the module is not tempted
        # to try to modify it. Any attempt to modify it at this point will invalidate
        # the hashes and signatures.
        event.freeze()

        for callback in self._check_event_allowed_callbacks:
            try:
                res, replacement_data = await callback(event, state_events)
            except SynapseError as e:
                # FIXME: Being able to throw SynapseErrors is relied upon by
                # some modules. PR #10386 accidentally broke this ability.
                # That said, we aren't keen on exposing this implementation detail
                # to modules and we should one day have a proper way to do what
                # is wanted.
                # This module callback needs a rework so that hacks such as
                # this one are not necessary.
                raise e
            except Exception:
                raise ModuleFailedException(
                    "Failed to run `check_event_allowed` module API callback"
                )

            # Return if the event shouldn't be allowed or if the module came up with a
            # replacement dict for the event.
            if res is False:
                return res, None
            elif isinstance(replacement_data, dict):
                return True, replacement_data

        return True, None
Beispiel #9
0
    async def _do_send_invite(self, destination: str, pdu: EventBase,
                              room_version: RoomVersion) -> JsonDict:
        """Actually sends the invite, first trying v2 API and falling back to
        v1 API if necessary.

        Returns:
            The event as a dict as returned by the remote server
        """
        time_now = self._clock.time_msec()

        try:
            content = await self.transport_layer.send_invite_v2(
                destination=destination,
                room_id=pdu.room_id,
                event_id=pdu.event_id,
                content={
                    "event": pdu.get_pdu_json(time_now),
                    "room_version": room_version.identifier,
                    "invite_room_state":
                    pdu.unsigned.get("invite_room_state", []),
                },
            )
            return content
        except HttpResponseException as e:
            if e.code in [400, 404]:
                err = e.to_synapse_error()

                # If we receive an error response that isn't a generic error, we
                # assume that the remote understands the v2 invite API and this
                # is a legitimate error.
                if err.errcode != Codes.UNKNOWN:
                    raise err

                # Otherwise, we assume that the remote server doesn't understand
                # the v2 invite API. That's ok provided the room uses old-style event
                # IDs.
                if room_version.event_format != EventFormatVersions.V1:
                    raise SynapseError(
                        400,
                        "User's homeserver does not support this room version",
                        Codes.UNSUPPORTED_ROOM_VERSION,
                    )
            elif e.code == 403:
                raise e.to_synapse_error()
            else:
                raise

        # Didn't work, try v1 API.
        # Note the v1 API returns a tuple of `(200, content)`

        _, content = await self.transport_layer.send_invite_v1(
            destination=destination,
            room_id=pdu.room_id,
            event_id=pdu.event_id,
            content=pdu.get_pdu_json(time_now),
        )
        return content
Beispiel #10
0
    async def _check_sigs_and_hash(self, room_version: RoomVersion,
                                   pdu: EventBase) -> EventBase:
        """Checks that event is correctly signed by the sending server.

        Args:
            room_version: The room version of the PDU
            pdu: the event to be checked

        Returns:
              * the original event if the checks pass
              * a redacted version of the event (if the signature
                matched but the hash did not)
              * throws a SynapseError if the signature check failed."""
        try:
            await _check_sigs_on_pdu(self.keyring, room_version, pdu)
        except SynapseError as e:
            logger.warning(
                "Signature check failed for %s: %s",
                pdu.event_id,
                e,
            )
            raise

        if not check_event_content_hash(pdu):
            # let's try to distinguish between failures because the event was
            # redacted (which are somewhat expected) vs actual ball-tampering
            # incidents.
            #
            # This is just a heuristic, so we just assume that if the keys are
            # about the same between the redacted and received events, then the
            # received event was probably a redacted copy (but we then use our
            # *actual* redacted copy to be on the safe side.)
            redacted_event = prune_event(pdu)
            if set(redacted_event.keys()) == set(pdu.keys()) and set(
                    redacted_event.content.keys()) == set(pdu.content.keys()):
                logger.info(
                    "Event %s seems to have been redacted; using our redacted copy",
                    pdu.event_id,
                )
            else:
                logger.warning(
                    "Event %s content has been tampered, redacting",
                    pdu.event_id,
                )
            return redacted_event

        result = await self.spam_checker.check_event_for_spam(pdu)

        if result:
            logger.warning(
                "Event contains spam, redacting %s: %s",
                pdu.event_id,
                pdu.get_pdu_json(),
            )
            return prune_event(pdu)

        return pdu
Beispiel #11
0
    async def _do_send_invite(self, destination: str, pdu: EventBase,
                              room_version: RoomVersion) -> JsonDict:
        """Actually sends the invite, first trying v2 API and falling back to
        v1 API if necessary.

        Returns:
            The event as a dict as returned by the remote server

        Raises:
            SynapseError: if the remote server returns an error or if the server
                only supports the v1 endpoint and a room version other than "1"
                or "2" is requested.
        """
        time_now = self._clock.time_msec()

        try:
            return await self.transport_layer.send_invite_v2(
                destination=destination,
                room_id=pdu.room_id,
                event_id=pdu.event_id,
                content={
                    "event": pdu.get_pdu_json(time_now),
                    "room_version": room_version.identifier,
                    "invite_room_state":
                    pdu.unsigned.get("invite_room_state", []),
                },
            )
        except HttpResponseException as e:
            # If an error is received that is due to an unrecognised endpoint,
            # fallback to the v1 endpoint if the room uses old-style event IDs.
            # Otherwise consider it a legitmate error and raise.
            err = e.to_synapse_error()
            if self._is_unknown_endpoint(e, err):
                if room_version.event_format != EventFormatVersions.V1:
                    raise SynapseError(
                        400,
                        "User's homeserver does not support this room version",
                        Codes.UNSUPPORTED_ROOM_VERSION,
                    )
            else:
                raise err

        # Didn't work, try v1 API.
        # Note the v1 API returns a tuple of `(200, content)`

        _, content = await self.transport_layer.send_invite_v1(
            destination=destination,
            room_id=pdu.room_id,
            event_id=pdu.event_id,
            content=pdu.get_pdu_json(time_now),
        )
        return content
Beispiel #12
0
def _check_size_limits(event: EventBase) -> None:
    if len(event.user_id) > 255:
        raise EventSizeError("'user_id' too large")
    if len(event.room_id) > 255:
        raise EventSizeError("'room_id' too large")
    if event.is_state() and len(event.state_key) > 255:
        raise EventSizeError("'state_key' too large")
    if len(event.type) > 255:
        raise EventSizeError("'type' too large")
    if len(event.event_id) > 255:
        raise EventSizeError("'event_id' too large")
    if len(encode_canonical_json(event.get_pdu_json())) > MAX_PDU_SIZE:
        raise EventSizeError("event too large")
Beispiel #13
0
    async def check_event_allowed(
            self, event: EventBase,
            context: EventContext) -> Tuple[bool, Optional[dict]]:
        """Check if a provided event should be allowed in the given context.

        The module can return:
            * True: the event is allowed.
            * False: the event is not allowed, and should be rejected with M_FORBIDDEN.

        If the event is allowed, the module can also return a dictionary to use as a
        replacement for the event.

        Args:
            event: The event to be checked.
            context: The context of the event.

        Returns:
            The result from the ThirdPartyRules module, as above.
        """
        # Bail out early without hitting the store if we don't have any callbacks to run.
        if len(self._check_event_allowed_callbacks) == 0:
            return True, None

        prev_state_ids = await context.get_prev_state_ids()

        # Retrieve the state events from the database.
        events = await self.store.get_events(prev_state_ids.values())
        state_events = {(ev.type, ev.state_key): ev for ev in events.values()}

        # Ensure that the event is frozen, to make sure that the module is not tempted
        # to try to modify it. Any attempt to modify it at this point will invalidate
        # the hashes and signatures.
        event.freeze()

        for callback in self._check_event_allowed_callbacks:
            try:
                res, replacement_data = await callback(event, state_events)
            except Exception as e:
                logger.warning("Failed to run module API callback %s: %s",
                               callback, e)
                continue

            # Return if the event shouldn't be allowed or if the module came up with a
            # replacement dict for the event.
            if res is False:
                return res, None
            elif isinstance(replacement_data, dict):
                return True, replacement_data

        return True, None
Beispiel #14
0
    async def _serialize_payload(  # type: ignore[override]
        event_id: str,
        store: "DataStore",
        event: EventBase,
        context: EventContext,
        requester: Requester,
        ratelimit: bool,
        extra_users: List[UserID],
    ) -> JsonDict:
        """
        Args:
            event_id
            store
            requester
            event
            context
            ratelimit
            extra_users: Any extra users to notify about event
        """
        serialized_context = await context.serialize(event, store)

        payload = {
            "event": event.get_pdu_json(),
            "room_version": event.room_version.identifier,
            "event_format_version": event.format_version,
            "internal_metadata": event.internal_metadata.get_dict(),
            "outlier": event.internal_metadata.is_outlier(),
            "rejected_reason": event.rejected_reason,
            "context": serialized_context,
            "requester": requester.serialize(),
            "ratelimit": ratelimit,
            "extra_users": [u.to_string() for u in extra_users],
        }

        return payload
 async def check_event_allowed(self, event: EventBase,
                               state: StateMap[EventBase]):
     d = event.get_dict()
     content = unfreeze(event.content)
     content["foo"] = "bar"
     d["content"] = content
     return d
 async def check(ev: EventBase, state):
     d = ev.get_dict()
     d["content"] = {
         "msgtype": "m.text",
         "body": d["content"]["body"].upper(),
     }
     return True, d
Beispiel #17
0
    async def _check_sigs_and_hash(
        self, room_version: RoomVersion, pdu: EventBase
    ) -> EventBase:
        """Checks that event is correctly signed by the sending server.

        Also checks the content hash, and redacts the event if there is a mismatch.

        Also runs the event through the spam checker; if it fails, redacts the event
        and flags it as soft-failed.

        Args:
            room_version: The room version of the PDU
            pdu: the event to be checked

        Returns:
              * the original event if the checks pass
              * a redacted version of the event (if the signature
                matched but the hash did not). In this case a warning will be logged.

        Raises:
          InvalidEventSignatureError if the signature check failed. Nothing
             will be logged in this case.
        """
        await _check_sigs_on_pdu(self.keyring, room_version, pdu)

        if not check_event_content_hash(pdu):
            # let's try to distinguish between failures because the event was
            # redacted (which are somewhat expected) vs actual ball-tampering
            # incidents.
            #
            # This is just a heuristic, so we just assume that if the keys are
            # about the same between the redacted and received events, then the
            # received event was probably a redacted copy (but we then use our
            # *actual* redacted copy to be on the safe side.)
            redacted_event = prune_event(pdu)
            if set(redacted_event.keys()) == set(pdu.keys()) and set(
                redacted_event.content.keys()
            ) == set(pdu.content.keys()):
                logger.debug(
                    "Event %s seems to have been redacted; using our redacted copy",
                    pdu.event_id,
                )
            else:
                logger.warning(
                    "Event %s content has been tampered, redacting",
                    pdu.event_id,
                )
            return redacted_event

        spam_check = await self.spam_checker.check_event_for_spam(pdu)

        if spam_check != self.spam_checker.NOT_SPAM:
            logger.warning("Event contains spam, soft-failing %s", pdu.event_id)
            # we redact (to save disk space) as well as soft-failing (to stop
            # using the event in prev_events).
            redacted_event = prune_event(pdu)
            redacted_event.internal_metadata.soft_failed = True
            return redacted_event

        return pdu
Beispiel #18
0
def _check_size_limits(event: EventBase) -> None:
    def too_big(field):
        raise EventSizeError("%s too large" % (field, ))

    if len(event.user_id) > 255:
        too_big("user_id")
    if len(event.room_id) > 255:
        too_big("room_id")
    if event.is_state() and len(event.state_key) > 255:
        too_big("state_key")
    if len(event.type) > 255:
        too_big("type")
    if len(event.event_id) > 255:
        too_big("event_id")
    if len(encode_canonical_json(event.get_pdu_json())) > 65536:
        too_big("event")
 async def check(
         ev: EventBase,
         state: StateMap[EventBase]) -> Tuple[bool, Optional[JsonDict]]:
     d = ev.get_dict()
     d["content"] = {
         "msgtype": "m.text",
         "body": d["content"]["body"].upper(),
     }
     return True, d
Beispiel #20
0
    async def _locally_reject_invite(
        self,
        invite_event: EventBase,
        txn_id: Optional[str],
        requester: Requester,
        content: JsonDict,
    ) -> Tuple[str, int]:
        """Generate a local invite rejection

        This is called after we fail to reject an invite via a remote server. It
        generates an out-of-band membership event locally.

        Args:
            invite_event: the invite to be rejected
            txn_id: optional transaction ID supplied by the client
            requester:  user making the rejection request, according to the access token
            content: additional content to include in the rejection event.
               Normally an empty dict.
        """

        room_id = invite_event.room_id
        target_user = invite_event.state_key

        content["membership"] = Membership.LEAVE

        event_dict = {
            "type": EventTypes.Member,
            "room_id": room_id,
            "sender": target_user,
            "content": content,
            "state_key": target_user,
        }

        # the auth events for the new event are the same as that of the invite, plus
        # the invite itself.
        #
        # the prev_events are just the invite.
        prev_event_ids = [invite_event.event_id]
        auth_event_ids = invite_event.auth_event_ids() + prev_event_ids

        event, context = await self.event_creation_handler.create_event(
            requester,
            event_dict,
            txn_id=txn_id,
            prev_event_ids=prev_event_ids,
            auth_event_ids=auth_event_ids,
        )
        event.internal_metadata.outlier = True
        event.internal_metadata.out_of_band_membership = True

        result_event = await self.event_creation_handler.handle_new_client_event(
            requester, event, context, extra_users=[UserID.from_string(target_user)],
        )
        # we know it was persisted, so must have a stream ordering
        assert result_event.internal_metadata.stream_ordering

        return result_event.event_id, result_event.internal_metadata.stream_ordering
Beispiel #21
0
 async def check_auth_rules_from_context(
     self,
     room_version_obj: RoomVersion,
     event: EventBase,
     context: EventContext,
 ) -> None:
     """Check an event passes the auth rules at its own auth events"""
     auth_event_ids = event.auth_event_ids()
     auth_events_by_id = await self._store.get_events(auth_event_ids)
     check_auth_rules_for_event(room_version_obj, event, auth_events_by_id.values())
Beispiel #22
0
 async def check_auth_rules_from_context(
     self,
     event: EventBase,
     context: EventContext,
 ) -> None:
     """Check an event passes the auth rules at its own auth events"""
     await check_state_independent_auth_rules(self._store, event)
     auth_event_ids = event.auth_event_ids()
     auth_events_by_id = await self._store.get_events(auth_event_ids)
     check_state_dependent_auth_rules(event, auth_events_by_id.values())
        def callback(_, pdu: EventBase):
            with PreserveLoggingContext(ctx):
                if not check_event_content_hash(pdu):
                    # let's try to distinguish between failures because the event was
                    # redacted (which are somewhat expected) vs actual ball-tampering
                    # incidents.
                    #
                    # This is just a heuristic, so we just assume that if the keys are
                    # about the same between the redacted and received events, then the
                    # received event was probably a redacted copy (but we then use our
                    # *actual* redacted copy to be on the safe side.)
                    redacted_event = prune_event(pdu)
                    if set(redacted_event.keys()) == set(pdu.keys()) and set(
                            redacted_event.content.keys()) == set(
                                pdu.content.keys()):
                        logger.info(
                            "Event %s seems to have been redacted; using our redacted "
                            "copy",
                            pdu.event_id,
                        )
                    else:
                        logger.warning(
                            "Event %s content has been tampered, redacting",
                            pdu.event_id,
                        )
                    return redacted_event

                result = yield defer.ensureDeferred(
                    self.spam_checker.check_event_for_spam(pdu))

                if result:
                    logger.warning(
                        "Event contains spam, redacting %s: %s",
                        pdu.event_id,
                        pdu.get_pdu_json(),
                    )
                    return prune_event(pdu)

                return pdu
Beispiel #24
0
    async def check_event_allowed(self, event: EventBase,
                                  state: StateMap[EventBase]):
        if event.is_state() and event.type == EventTypes.Member:
            await self.api.create_and_send_event_into_room({
                "room_id": event.room_id,
                "sender": event.sender,
                "type": "bzh.abolivier.test3",
                "content": {
                    "now": int(time.time())
                },
                "state_key": "",
            })

        return True, None
Beispiel #25
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)
Beispiel #26
0
 def on_new_room_event(
     self,
     event: EventBase,
     event_pos: PersistedEventPosition,
     max_room_stream_token: RoomStreamToken,
     extra_users: Collection[UserID] = [],
 ):
     """Unwraps event and calls `on_new_room_event_args`."""
     self.on_new_room_event_args(
         event_pos=event_pos,
         room_id=event.room_id,
         event_type=event.type,
         state_key=event.get("state_key"),
         membership=event.content.get("membership"),
         max_room_stream_token=max_room_stream_token,
         extra_users=extra_users,
     )
Beispiel #27
0
 def from_event(
     server_name: str,
     event: EventBase,
     minimum_valid_until_ms: int,
 ) -> "VerifyJsonRequest":
     """Create a VerifyJsonRequest to verify all signatures on an event
     object for the given server.
     """
     key_ids = list(event.signatures.get(server_name, []))
     return VerifyJsonRequest(
         server_name,
         # We defer creating the redacted json object, as it uses a lot more
         # memory than the Event object itself.
         lambda: prune_event_dict(event.room_version, event.get_pdu_json()),
         minimum_valid_until_ms,
         key_ids=key_ids,
     )
Beispiel #28
0
    def maybe_schedule_expiry(self, event: EventBase):
        """Schedule the expiry of an event if there's not already one scheduled,
        or if the one running is for an event that will expire after the provided
        timestamp.

        This function needs to invalidate the event cache, which is only possible on
        the master process, and therefore needs to be run on there.

        Args:
            event: The event to schedule the expiry of.
        """

        expiry_ts = event.content.get(EventContentFields.SELF_DESTRUCT_AFTER)
        if not isinstance(expiry_ts, int) or event.is_state():
            return

        # _schedule_expiry_for_event won't actually schedule anything if there's already
        # a task scheduled for a timestamp that's sooner than the provided one.
        self._schedule_expiry_for_event(event.event_id, expiry_ts)
Beispiel #29
0
def _can_send_event(event: EventBase, auth_events: StateMap[EventBase]) -> bool:
    power_levels_event = get_power_level_event(auth_events)

    send_level = get_send_level(event.type, event.get("state_key"), power_levels_event)
    user_level = get_user_power_level(event.user_id, auth_events)

    if user_level < send_level:
        raise AuthError(
            403,
            "You don't have permission to post that to the room. "
            + "user_level (%d) < send_level (%d)" % (user_level, send_level),
        )

    # Check state_key
    if hasattr(event, "state_key"):
        if event.state_key.startswith("@"):
            if event.state_key != event.user_id:
                raise AuthError(403, "You are not allowed to set others state")

    return True
Beispiel #30
0
    async def send_nonmember_event(
        self,
        requester: Requester,
        event: EventBase,
        context: EventContext,
        ratelimit: bool = True,
    ) -> int:
        """
        Persists and notifies local clients and federation of an event.

        Args:
            requester
            event the event to send.
            context: the context of the event.
            ratelimit: Whether to rate limit this send.

        Return:
            The stream_id of the persisted event.
        """
        if event.type == EventTypes.Member:
            raise SynapseError(
                500, "Tried to send member event through non-member codepath")

        user = UserID.from_string(event.sender)

        assert self.hs.is_mine(user), "User must be our own: %s" % (user, )

        if event.is_state():
            prev_state = await self.deduplicate_state_event(event, context)
            if prev_state is not None:
                logger.info(
                    "Not bothering to persist state event %s duplicated by %s",
                    event.event_id,
                    prev_state.event_id,
                )
                return prev_state

        return await self.handle_new_client_event(requester=requester,
                                                  event=event,
                                                  context=context,
                                                  ratelimit=ratelimit)