Exemplo n.º 1
0
    async def on_POST(
        self, request: SynapseRequest, room_id: str
    ) -> Tuple[int, JsonDict]:
        requester = await self._auth.get_user_by_req(request)

        content = parse_json_object_from_request(request)
        assert_params_in_dict(content, ("new_version",))

        new_version = KNOWN_ROOM_VERSIONS.get(content["new_version"])
        if new_version is None:
            raise SynapseError(
                400,
                "Your homeserver does not support this room version",
                Codes.UNSUPPORTED_ROOM_VERSION,
            )

        try:
            new_room_id = await self._room_creation_handler.upgrade_room(
                requester, room_id, new_version
            )
        except ShadowBanError:
            # Generate a random room ID.
            new_room_id = stringutils.random_string(18)

        ret = {"replacement_room": new_room_id}

        return 200, ret
Exemplo n.º 2
0
def check_redaction(room_version, event, auth_events):
    """Check whether the event sender is allowed to redact the target event.

    Returns:
        True if the the sender is allowed to redact the target event if the
        target event was created by them.
        False if the sender is allowed to redact the target event with no
        further checks.

    Raises:
        AuthError if the event sender is definitely not allowed to redact
        the target event.
    """
    user_level = get_user_power_level(event.user_id, auth_events)

    redact_level = _get_named_level(auth_events, "redact", 50)

    if user_level >= redact_level:
        return False

    v = KNOWN_ROOM_VERSIONS.get(room_version)
    if not v:
        raise RuntimeError("Unrecognized room version %r" % (room_version,))

    if v.event_format == EventFormatVersions.V1:
        redacter_domain = get_domain_from_id(event.event_id)
        redactee_domain = get_domain_from_id(event.redacts)
        if redacter_domain == redactee_domain:
            return True
    else:
        event.internal_metadata.recheck_redaction = True
        return True

    raise AuthError(403, "You don't have permission to redact events")
Exemplo n.º 3
0
        async def send_request(destination: str) -> Tuple[str, EventBase, RoomVersion]:
            ret = await self.transport_layer.make_membership_event(
                destination, room_id, user_id, membership, params
            )

            # Note: If not supplied, the room version may be either v1 or v2,
            # however either way the event format version will be v1.
            room_version_id = ret.get("room_version", RoomVersions.V1.identifier)
            room_version = KNOWN_ROOM_VERSIONS.get(room_version_id)
            if not room_version:
                raise UnsupportedRoomVersionError()

            pdu_dict = ret.get("event", None)
            if not isinstance(pdu_dict, dict):
                raise InvalidResponseError("Bad 'event' field in response")

            logger.debug("Got response to make_%s: %s", membership, pdu_dict)

            pdu_dict["content"].update(content)

            # The protoevent received over the JSON wire may not have all
            # the required fields. Lets just gloss over that because
            # there's some we never care about
            if "prev_state" not in pdu_dict:
                pdu_dict["prev_state"] = []

            ev = builder.create_local_event_from_event_dict(
                self._clock,
                self.hostname,
                self.signing_key,
                room_version=room_version,
                event_dict=pdu_dict,
            )

            return destination, ev, room_version
Exemplo n.º 4
0
def _retrieve_and_check_room_version(room_id: str,
                                     room_version_id: str) -> RoomVersion:
    v = KNOWN_ROOM_VERSIONS.get(room_version_id)
    if not v:
        raise UnsupportedRoomVersionError(
            "Room %s uses a room version %s which is no longer supported" %
            (room_id, room_version_id))
    return v
Exemplo n.º 5
0
def room_version_to_event_format(room_version):
    """Converts a room version string to the event format

    Args:
        room_version (str)

    Returns:
        int
    """
    v = KNOWN_ROOM_VERSIONS.get(room_version)

    if not v:
        # We should have already checked version, so this should not happen
        raise RuntimeError("Unrecognized room version %s" % (room_version,))

    return v.event_format
Exemplo n.º 6
0
    def new(self, room_version: str, key_values: dict) -> EventBuilder:
        """Generate an event builder appropriate for the given room version

        Deprecated: use for_room_version with a RoomVersion object instead

        Args:
            room_version: Version of the room that we're creating an event builder for
            key_values: Fields used as the basis of the new event

        Returns:
            EventBuilder
        """
        v = KNOWN_ROOM_VERSIONS.get(room_version)
        if not v:
            # this can happen if support is withdrawn for a room version
            raise UnsupportedRoomVersionError()
        return self.for_room_version(v, key_values)
Exemplo n.º 7
0
    def new(self, room_version, key_values):
        """Generate an event builder appropriate for the given room version

        Deprecated: use for_room_version with a RoomVersion object instead

        Args:
            room_version (str): Version of the room that we're creating an event builder
                for
            key_values (dict): Fields used as the basis of the new event

        Returns:
            EventBuilder
        """
        v = KNOWN_ROOM_VERSIONS.get(room_version)
        if not v:
            raise Exception("No event format defined for version %r" %
                            (room_version, ))
        return self.for_room_version(v, key_values)
Exemplo n.º 8
0
    async def on_invite_request(self, origin: str, content: JsonDict,
                                room_version_id: str) -> Dict[str, Any]:
        room_version = KNOWN_ROOM_VERSIONS.get(room_version_id)
        if not room_version:
            raise SynapseError(
                400,
                "Homeserver does not support this room version",
                Codes.UNSUPPORTED_ROOM_VERSION,
            )

        pdu = event_from_pdu_json(content, room_version)
        origin_host, _ = parse_server_name(origin)
        await self.check_server_matches_acl(origin_host, pdu.room_id)
        pdu = await self._check_sigs_and_hash(room_version, pdu)
        ret_pdu = await self.handler.on_invite_request(origin, pdu,
                                                       room_version)
        time_now = self._clock.time_msec()
        return {"event": ret_pdu.get_pdu_json(time_now)}
Exemplo n.º 9
0
def room_version_to_event_format(room_version):
    """Converts a room version string to the event format

    Args:
        room_version (str)

    Returns:
        int

    Raises:
        UnsupportedRoomVersionError if the room version is unknown
    """
    v = KNOWN_ROOM_VERSIONS.get(room_version)

    if not v:
        # this can happen if support is withdrawn for a room version
        raise UnsupportedRoomVersionError()

    return v.event_format
Exemplo n.º 10
0
    async def get_room_version(self, room_id: str) -> RoomVersion:
        """Get the room_version of a given room

        Raises:
            NotFoundError: if the room is unknown

            UnsupportedRoomVersionError: if the room uses an unknown room version.
                Typically this happens if support for the room's version has been
                removed from Synapse.
        """
        room_version_id = await self.get_room_version_id(room_id)
        v = KNOWN_ROOM_VERSIONS.get(room_version_id)

        if not v:
            raise UnsupportedRoomVersionError(
                "Room %s uses a room version %s which is no longer supported" %
                (room_id, room_version_id))

        return v
Exemplo n.º 11
0
def room_version_to_event_format(room_version):
    """Converts a room version string to the event format

    Args:
        room_version (str)

    Returns:
        int

    Raises:
        UnsupportedRoomVersionError if the room version is unknown
    """
    v = KNOWN_ROOM_VERSIONS.get(room_version)

    if not v:
        # this can happen if support is withdrawn for a room version
        raise UnsupportedRoomVersionError()

    return v.event_format
Exemplo n.º 12
0
    def get_room_version_txn(self, txn: LoggingTransaction,
                             room_id: str) -> RoomVersion:
        """Get the room_version of a given room
        Args:
            txn: Transaction object
            room_id: The room_id of the room you are trying to get the version for
        Raises:
            NotFoundError: if the room is unknown
            UnsupportedRoomVersionError: if the room uses an unknown room version.
                Typically this happens if support for the room's version has been
                removed from Synapse.
        """
        room_version_id = self.get_room_version_id_txn(txn, room_id)
        v = KNOWN_ROOM_VERSIONS.get(room_version_id)

        if not v:
            raise UnsupportedRoomVersionError(
                "Room %s uses a room version %s which is no longer supported" %
                (room_id, room_version_id))

        return v
    async def on_POST(self, request, room_id):
        requester = await self._auth.get_user_by_req(request)

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

        new_version = KNOWN_ROOM_VERSIONS.get(content["new_version"])
        if new_version is None:
            raise SynapseError(
                400,
                "Your homeserver does not support this room version",
                Codes.UNSUPPORTED_ROOM_VERSION,
            )

        new_room_id = await self._room_creation_handler.upgrade_room(
            requester, room_id, new_version)

        ret = {"replacement_room": new_room_id}

        return 200, ret
Exemplo n.º 14
0
def check_redaction(room_version, event, auth_events):
    """Check whether the event sender is allowed to redact the target event.

    Returns:
        True if the the sender is allowed to redact the target event if the
        target event was created by them.
        False if the sender is allowed to redact the target event with no
        further checks.

    Raises:
        AuthError if the event sender is definitely not allowed to redact
        the target event.
    """
    user_level = get_user_power_level(event.user_id, auth_events)

    redact_level = _get_named_level(auth_events, "redact", 50)

    if user_level >= redact_level:
        return False

    v = KNOWN_ROOM_VERSIONS.get(room_version)
    if not v:
        raise RuntimeError("Unrecognized room version %r" % (room_version,))

    if v.event_format == EventFormatVersions.V1:
        redacter_domain = get_domain_from_id(event.event_id)
        redactee_domain = get_domain_from_id(event.redacts)
        if redacter_domain == redactee_domain:
            return True
    else:
        event.internal_metadata.recheck_redaction = True
        return True

    raise AuthError(
        403,
        "You don't have permission to redact events"
    )
Exemplo n.º 15
0
    async def _get_events_from_db(self, event_ids, allow_rejected=False):
        """Fetch a bunch of events from the database.

        Returned events will be added to the cache for future lookups.

        Unknown events are omitted from the response.

        Args:
            event_ids (Iterable[str]): The event_ids of the events to fetch

            allow_rejected (bool): Whether to include rejected events. If False,
                rejected events are omitted from the response.

        Returns:
            Dict[str, _EventCacheEntry]:
                map from event id to result. May return extra events which
                weren't asked for.
        """
        fetched_events = {}
        events_to_fetch = event_ids

        while events_to_fetch:
            row_map = await self._enqueue_events(events_to_fetch)

            # we need to recursively fetch any redactions of those events
            redaction_ids = set()
            for event_id in events_to_fetch:
                row = row_map.get(event_id)
                fetched_events[event_id] = row
                if row:
                    redaction_ids.update(row["redactions"])

            events_to_fetch = redaction_ids.difference(fetched_events.keys())
            if events_to_fetch:
                logger.debug("Also fetching redaction events %s",
                             events_to_fetch)

        # build a map from event_id to EventBase
        event_map = {}
        for event_id, row in fetched_events.items():
            if not row:
                continue
            assert row["event_id"] == event_id

            rejected_reason = row["rejected_reason"]

            if not allow_rejected and rejected_reason:
                continue

            # If the event or metadata cannot be parsed, log the error and act
            # as if the event is unknown.
            try:
                d = db_to_json(row["json"])
            except ValueError:
                logger.error("Unable to parse json from event: %s", event_id)
                continue
            try:
                internal_metadata = db_to_json(row["internal_metadata"])
            except ValueError:
                logger.error(
                    "Unable to parse internal_metadata from event: %s",
                    event_id)
                continue

            format_version = row["format_version"]
            if format_version is None:
                # This means that we stored the event before we had the concept
                # of a event format version, so it must be a V1 event.
                format_version = EventFormatVersions.V1

            room_version_id = row["room_version_id"]

            if not room_version_id:
                # this should only happen for out-of-band membership events which
                # arrived before #6983 landed. For all other events, we should have
                # an entry in the 'rooms' table.
                #
                # However, the 'out_of_band_membership' flag is unreliable for older
                # invites, so just accept it for all membership events.
                #
                if d["type"] != EventTypes.Member:
                    raise Exception("Room %s for event %s is unknown" %
                                    (d["room_id"], event_id))

                # so, assuming this is an out-of-band-invite that arrived before #6983
                # landed, we know that the room version must be v5 or earlier (because
                # v6 hadn't been invented at that point, so invites from such rooms
                # would have been rejected.)
                #
                # The main reason we need to know the room version here (other than
                # choosing the right python Event class) is in case the event later has
                # to be redacted - and all the room versions up to v5 used the same
                # redaction algorithm.
                #
                # So, the following approximations should be adequate.

                if format_version == EventFormatVersions.V1:
                    # if it's event format v1 then it must be room v1 or v2
                    room_version = RoomVersions.V1
                elif format_version == EventFormatVersions.V2:
                    # if it's event format v2 then it must be room v3
                    room_version = RoomVersions.V3
                else:
                    # if it's event format v3 then it must be room v4 or v5
                    room_version = RoomVersions.V5
            else:
                room_version = KNOWN_ROOM_VERSIONS.get(room_version_id)
                if not room_version:
                    logger.warning(
                        "Event %s in room %s has unknown room version %s",
                        event_id,
                        d["room_id"],
                        room_version_id,
                    )
                    continue

                if room_version.event_format != format_version:
                    logger.error(
                        "Event %s in room %s with version %s has wrong format: "
                        "expected %s, was %s",
                        event_id,
                        d["room_id"],
                        room_version_id,
                        room_version.event_format,
                        format_version,
                    )
                    continue

            original_ev = make_event_from_dict(
                event_dict=d,
                room_version=room_version,
                internal_metadata_dict=internal_metadata,
                rejected_reason=rejected_reason,
            )

            event_map[event_id] = original_ev

        # finally, we can decide whether each one needs redacting, and build
        # the cache entries.
        result_map = {}
        for event_id, original_ev in event_map.items():
            redactions = fetched_events[event_id]["redactions"]
            redacted_event = self._maybe_redact_event_row(
                original_ev, redactions, event_map)

            cache_entry = _EventCacheEntry(event=original_ev,
                                           redacted_event=redacted_event)

            self._get_event_cache.prefill((event_id, ), cache_entry)
            result_map[event_id] = cache_entry

        return result_map
Exemplo n.º 16
0
    def _do_send_invite(self, destination, pdu, room_version):
        """Actually sends the invite, first trying v2 API and falling back to
        v1 API if necessary.

        Args:
            destination (str): Target server
            pdu (FrozenEvent)
            room_version (str)

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

        try:
            content = yield 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,
                    "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.
                v = KNOWN_ROOM_VERSIONS.get(room_version)
                if v.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 = yield 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
Exemplo n.º 17
0
    def create_room(self,
                    requester,
                    config,
                    ratelimit=True,
                    creator_join_profile=None):
        """ Creates a new room.

        Args:
            requester (synapse.types.Requester):
                The user who requested the room creation.
            config (dict) : A dict of configuration options.
            ratelimit (bool): set to False to disable the rate limiter

            creator_join_profile (dict|None):
                Set to override the displayname and avatar for the creating
                user in this room. If unset, displayname and avatar will be
                derived from the user's profile. If set, should contain the
                values to go in the body of the 'join' event (typically
                `avatar_url` and/or `displayname`.

        Returns:
            Deferred[dict]:
                a dict containing the keys `room_id` and, if an alias was
                requested, `room_alias`.
        Raises:
            SynapseError if the room ID couldn't be stored, or something went
            horribly wrong.
            ResourceLimitError if server is blocked to some resource being
            exceeded
        """
        user_id = requester.user.to_string()

        yield self.auth.check_auth_blocking(user_id)

        if (self._server_notices_mxid is not None
                and requester.user.to_string() == self._server_notices_mxid):
            # allow the server notices mxid to create rooms
            is_requester_admin = True
        else:
            is_requester_admin = yield self.auth.is_server_admin(
                requester.user)

        # Check whether the third party rules allows/changes the room create
        # request.
        yield self.third_party_event_rules.on_create_room(
            requester, config, is_requester_admin=is_requester_admin)

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

        if ratelimit:
            yield self.ratelimit(requester)

        room_version_id = config.get(
            "room_version", self.config.default_room_version.identifier)

        if not isinstance(room_version_id, string_types):
            raise SynapseError(400, "room_version must be a string",
                               Codes.BAD_JSON)

        room_version = KNOWN_ROOM_VERSIONS.get(room_version_id)
        if room_version is None:
            raise SynapseError(
                400,
                "Your homeserver does not support this room version",
                Codes.UNSUPPORTED_ROOM_VERSION,
            )

        if "room_alias_name" in config:
            for wchar in string.whitespace:
                if wchar in config["room_alias_name"]:
                    raise SynapseError(400, "Invalid characters in room alias")

            room_alias = RoomAlias(config["room_alias_name"], self.hs.hostname)
            mapping = yield self.store.get_association_from_room_alias(
                room_alias)

            if mapping:
                raise SynapseError(400, "Room alias already taken",
                                   Codes.ROOM_IN_USE)
        else:
            room_alias = None

        invite_list = config.get("invite", [])
        for i in invite_list:
            try:
                uid = UserID.from_string(i)
                parse_and_validate_server_name(uid.domain)
            except Exception:
                raise SynapseError(400, "Invalid user_id: %s" % (i, ))

        yield self.event_creation_handler.assert_accepted_privacy_policy(
            requester)

        power_level_content_override = config.get(
            "power_level_content_override")
        if (power_level_content_override
                and "users" in power_level_content_override
                and user_id not in power_level_content_override["users"]):
            raise SynapseError(
                400,
                "Not a valid power_level_content_override: 'users' did not contain %s"
                % (user_id, ),
            )

        invite_3pid_list = config.get("invite_3pid", [])

        visibility = config.get("visibility", None)
        is_public = visibility == "public"

        room_id = yield self._generate_room_id(
            creator_id=user_id,
            is_public=is_public,
            room_version=room_version,
        )

        directory_handler = self.hs.get_handlers().directory_handler
        if room_alias:
            yield directory_handler.create_association(
                requester=requester,
                room_id=room_id,
                room_alias=room_alias,
                servers=[self.hs.hostname],
                send_event=False,
                check_membership=False,
            )

        preset_config = config.get(
            "preset",
            RoomCreationPreset.PRIVATE_CHAT
            if visibility == "private" else RoomCreationPreset.PUBLIC_CHAT,
        )

        raw_initial_state = config.get("initial_state", [])

        initial_state = OrderedDict()
        for val in raw_initial_state:
            initial_state[(val["type"], val.get("state_key",
                                                ""))] = val["content"]

        creation_content = config.get("creation_content", {})

        # override any attempt to set room versions via the creation_content
        creation_content["room_version"] = room_version.identifier

        yield self._send_events_for_new_room(
            requester,
            room_id,
            preset_config=preset_config,
            invite_list=invite_list,
            initial_state=initial_state,
            creation_content=creation_content,
            room_alias=room_alias,
            power_level_content_override=power_level_content_override,
            creator_join_profile=creator_join_profile,
        )

        if "name" in config:
            name = config["name"]
            yield self.event_creation_handler.create_and_send_nonmember_event(
                requester,
                {
                    "type": EventTypes.Name,
                    "room_id": room_id,
                    "sender": user_id,
                    "state_key": "",
                    "content": {
                        "name": name
                    },
                },
                ratelimit=False,
            )

        if "topic" in config:
            topic = config["topic"]
            yield self.event_creation_handler.create_and_send_nonmember_event(
                requester,
                {
                    "type": EventTypes.Topic,
                    "room_id": room_id,
                    "sender": user_id,
                    "state_key": "",
                    "content": {
                        "topic": topic
                    },
                },
                ratelimit=False,
            )

        for invitee in invite_list:
            content = {}
            is_direct = config.get("is_direct", None)
            if is_direct:
                content["is_direct"] = is_direct

            yield self.room_member_handler.update_membership(
                requester,
                UserID.from_string(invitee),
                room_id,
                "invite",
                ratelimit=False,
                content=content,
            )

        for invite_3pid in invite_3pid_list:
            id_server = invite_3pid["id_server"]
            id_access_token = invite_3pid.get("id_access_token")  # optional
            address = invite_3pid["address"]
            medium = invite_3pid["medium"]
            yield self.hs.get_room_member_handler().do_3pid_invite(
                room_id,
                requester.user,
                medium,
                address,
                id_server,
                requester,
                txn_id=None,
                id_access_token=id_access_token,
            )

        result = {"room_id": room_id}

        if room_alias:
            result["room_alias"] = room_alias.to_string()
            yield directory_handler.send_room_alias_update_event(
                requester, room_id)

        return result
Exemplo n.º 18
0
    async def _rejected_events_metadata(self, progress: dict,
                                        batch_size: int) -> int:
        """Adds rejected events to the `state_events` and `event_auth` metadata
        tables.
        """

        last_event_id = progress.get("last_event_id", "")

        def get_rejected_events(
            txn: Cursor, ) -> List[Tuple[str, str, JsonDict, bool, bool]]:
            # Fetch rejected event json, their room version and whether we have
            # inserted them into the state_events or auth_events tables.
            #
            # Note we can assume that events that don't have a corresponding
            # room version are V1 rooms.
            sql = """
                SELECT DISTINCT
                    event_id,
                    COALESCE(room_version, '1'),
                    json,
                    state_events.event_id IS NOT NULL,
                    event_auth.event_id IS NOT NULL
                FROM rejections
                INNER JOIN event_json USING (event_id)
                LEFT JOIN rooms USING (room_id)
                LEFT JOIN state_events USING (event_id)
                LEFT JOIN event_auth USING (event_id)
                WHERE event_id > ?
                ORDER BY event_id
                LIMIT ?
            """

            txn.execute(
                sql,
                (
                    last_event_id,
                    batch_size,
                ),
            )

            return [(row[0], row[1], db_to_json(row[2]), row[3], row[4])
                    for row in txn]  # type: ignore

        results = await self.db_pool.runInteraction(
            desc="_rejected_events_metadata_get", func=get_rejected_events)

        if not results:
            await self.db_pool.updates._end_background_update(
                "rejected_events_metadata")
            return 0

        state_events = []
        auth_events = []
        for event_id, room_version, event_json, has_state, has_event_auth in results:
            last_event_id = event_id

            if has_state and has_event_auth:
                continue

            room_version_obj = KNOWN_ROOM_VERSIONS.get(room_version)
            if not room_version_obj:
                # We no longer support this room version, so we just ignore the
                # events entirely.
                logger.info(
                    "Ignoring event with unknown room version %r: %r",
                    room_version,
                    event_id,
                )
                continue

            event = make_event_from_dict(event_json, room_version_obj)

            if not event.is_state():
                continue

            if not has_state:
                state_events.append({
                    "event_id": event.event_id,
                    "room_id": event.room_id,
                    "type": event.type,
                    "state_key": event.state_key,
                })

            if not has_event_auth:
                # Old, dodgy, events may have duplicate auth events, which we
                # need to deduplicate as we have a unique constraint.
                for auth_id in set(event.auth_event_ids()):
                    auth_events.append({
                        "room_id": event.room_id,
                        "event_id": event.event_id,
                        "auth_id": auth_id,
                    })

        if state_events:
            await self.db_pool.simple_insert_many(
                table="state_events",
                values=state_events,
                desc="_rejected_events_metadata_state_events",
            )

        if auth_events:
            await self.db_pool.simple_insert_many(
                table="event_auth",
                values=auth_events,
                desc="_rejected_events_metadata_event_auth",
            )

        await self.db_pool.updates._background_update_progress(
            "rejected_events_metadata", {"last_event_id": last_event_id})

        if len(results) < batch_size:
            await self.db_pool.updates._end_background_update(
                "rejected_events_metadata")

        return len(results)
Exemplo n.º 19
0
def _check_sigs_on_pdus(keyring, room_version, pdus):
    """Check that the given events are correctly signed

    Args:
        keyring (synapse.crypto.Keyring): keyring object to do the checks
        room_version (str): the room version of the PDUs
        pdus (Collection[EventBase]): the events to be checked

    Returns:
        List[Deferred]: a Deferred for each event in pdus, which will either succeed if
           the signatures are valid, or fail (with a SynapseError) if not.
    """

    # we want to check that the event is signed by:
    #
    # (a) the sender's server
    #
    #     - except in the case of invites created from a 3pid invite, which are exempt
    #     from this check, because the sender has to match that of the original 3pid
    #     invite, but the event may come from a different HS, for reasons that I don't
    #     entirely grok (why do the senders have to match? and if they do, why doesn't the
    #     joining server ask the inviting server to do the switcheroo with
    #     exchange_third_party_invite?).
    #
    #     That's pretty awful, since redacting such an invite will render it invalid
    #     (because it will then look like a regular invite without a valid signature),
    #     and signatures are *supposed* to be valid whether or not an event has been
    #     redacted. But this isn't the worst of the ways that 3pid invites are broken.
    #
    # (b) for V1 and V2 rooms, the server which created the event_id
    #
    # let's start by getting the domain for each pdu, and flattening the event back
    # to JSON.

    pdus_to_check = [
        PduToCheckSig(
            pdu=p,
            redacted_pdu_json=prune_event(p).get_pdu_json(),
            sender_domain=get_domain_from_id(p.sender),
            deferreds=[],
        )
        for p in pdus
    ]

    v = KNOWN_ROOM_VERSIONS.get(room_version)
    if not v:
        raise RuntimeError("Unrecognized room version %s" % (room_version,))

    # First we check that the sender event is signed by the sender's domain
    # (except if its a 3pid invite, in which case it may be sent by any server)
    pdus_to_check_sender = [p for p in pdus_to_check if not _is_invite_via_3pid(p.pdu)]

    more_deferreds = keyring.verify_json_objects_for_server(
        [
            (
                p.sender_domain,
                p.redacted_pdu_json,
                p.pdu.origin_server_ts if v.enforce_key_validity else 0,
                p.pdu.event_id,
            )
            for p in pdus_to_check_sender
        ]
    )

    def sender_err(e, pdu_to_check):
        errmsg = "event id %s: unable to verify signature for sender %s: %s" % (
            pdu_to_check.pdu.event_id,
            pdu_to_check.sender_domain,
            e.getErrorMessage(),
        )
        raise SynapseError(403, errmsg, Codes.FORBIDDEN)

    for p, d in zip(pdus_to_check_sender, more_deferreds):
        d.addErrback(sender_err, p)
        p.deferreds.append(d)

    # now let's look for events where the sender's domain is different to the
    # event id's domain (normally only the case for joins/leaves), and add additional
    # checks. Only do this if the room version has a concept of event ID domain
    # (ie, the room version uses old-style non-hash event IDs).
    if v.event_format == EventFormatVersions.V1:
        pdus_to_check_event_id = [
            p
            for p in pdus_to_check
            if p.sender_domain != get_domain_from_id(p.pdu.event_id)
        ]

        more_deferreds = keyring.verify_json_objects_for_server(
            [
                (
                    get_domain_from_id(p.pdu.event_id),
                    p.redacted_pdu_json,
                    p.pdu.origin_server_ts if v.enforce_key_validity else 0,
                    p.pdu.event_id,
                )
                for p in pdus_to_check_event_id
            ]
        )

        def event_err(e, pdu_to_check):
            errmsg = (
                "event id %s: unable to verify signature for event id domain: %s"
                % (pdu_to_check.pdu.event_id, e.getErrorMessage())
            )
            raise SynapseError(403, errmsg, Codes.FORBIDDEN)

        for p, d in zip(pdus_to_check_event_id, more_deferreds):
            d.addErrback(event_err, p)
            p.deferreds.append(d)

    # replace lists of deferreds with single Deferreds
    return [_flatten_deferred_list(p.deferreds) for p in pdus_to_check]
Exemplo n.º 20
0
    def _get_events_from_db(self, event_ids, allow_rejected=False):
        """Fetch a bunch of events from the database.

        Returned events will be added to the cache for future lookups.

        Unknown events are omitted from the response.

        Args:
            event_ids (Iterable[str]): The event_ids of the events to fetch

            allow_rejected (bool): Whether to include rejected events. If False,
                rejected events are omitted from the response.

        Returns:
            Deferred[Dict[str, _EventCacheEntry]]:
                map from event id to result. May return extra events which
                weren't asked for.
        """
        fetched_events = {}
        events_to_fetch = event_ids

        while events_to_fetch:
            row_map = yield self._enqueue_events(events_to_fetch)

            # we need to recursively fetch any redactions of those events
            redaction_ids = set()
            for event_id in events_to_fetch:
                row = row_map.get(event_id)
                fetched_events[event_id] = row
                if row:
                    redaction_ids.update(row["redactions"])

            events_to_fetch = redaction_ids.difference(fetched_events.keys())
            if events_to_fetch:
                logger.debug("Also fetching redaction events %s",
                             events_to_fetch)

        # build a map from event_id to EventBase
        event_map = {}
        for event_id, row in fetched_events.items():
            if not row:
                continue
            assert row["event_id"] == event_id

            rejected_reason = row["rejected_reason"]

            if not allow_rejected and rejected_reason:
                continue

            d = json.loads(row["json"])
            internal_metadata = json.loads(row["internal_metadata"])

            format_version = row["format_version"]
            if format_version is None:
                # This means that we stored the event before we had the concept
                # of a event format version, so it must be a V1 event.
                format_version = EventFormatVersions.V1

            room_version_id = row["room_version_id"]

            if not room_version_id:
                # this should only happen for out-of-band membership events
                if not internal_metadata.get("out_of_band_membership"):
                    logger.warning("Room %s for event %s is unknown",
                                   d["room_id"], event_id)
                    continue

                # take a wild stab at the room version based on the event format
                if format_version == EventFormatVersions.V1:
                    room_version = RoomVersions.V1
                elif format_version == EventFormatVersions.V2:
                    room_version = RoomVersions.V3
                else:
                    room_version = RoomVersions.V5
            else:
                room_version = KNOWN_ROOM_VERSIONS.get(room_version_id)
                if not room_version:
                    logger.error(
                        "Event %s in room %s has unknown room version %s",
                        event_id,
                        d["room_id"],
                        room_version_id,
                    )
                    continue

                if room_version.event_format != format_version:
                    logger.error(
                        "Event %s in room %s with version %s has wrong format: "
                        "expected %s, was %s",
                        event_id,
                        d["room_id"],
                        room_version_id,
                        room_version.event_format,
                        format_version,
                    )
                    continue

            original_ev = make_event_from_dict(
                event_dict=d,
                room_version=room_version,
                internal_metadata_dict=internal_metadata,
                rejected_reason=rejected_reason,
            )

            event_map[event_id] = original_ev

        # finally, we can decide whether each one needs redacting, and build
        # the cache entries.
        result_map = {}
        for event_id, original_ev in event_map.items():
            redactions = fetched_events[event_id]["redactions"]
            redacted_event = self._maybe_redact_event_row(
                original_ev, redactions, event_map)

            cache_entry = _EventCacheEntry(event=original_ev,
                                           redacted_event=redacted_event)

            self._get_event_cache.prefill((event_id, ), cache_entry)
            result_map[event_id] = cache_entry

        return result_map
Exemplo n.º 21
0
    def _do_send_invite(self, destination, pdu, room_version):
        """Actually sends the invite, first trying v2 API and falling back to
        v1 API if necessary.

        Args:
            destination (str): Target server
            pdu (FrozenEvent)
            room_version (str)

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

        try:
            content = yield 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,
                    "invite_room_state": pdu.unsigned.get("invite_room_state", []),
                },
            )
            defer.returnValue(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.
                v = KNOWN_ROOM_VERSIONS.get(room_version)
                if v.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 = yield 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),
        )
        defer.returnValue(content)
Exemplo n.º 22
0
def main() -> None:
    parser = argparse.ArgumentParser(
        description="""Adds a signature to a JSON object.

Example usage:

    $ scripts-dev/sign_json.py -N test -k localhost.signing.key "{}"
    {"signatures":{"test":{"ed25519:a_ZnZh":"LmPnml6iM0iR..."}}}
""",
        formatter_class=argparse.RawDescriptionHelpFormatter,
    )

    parser.add_argument(
        "-N",
        "--server-name",
        help="Name to give as the local homeserver. If unspecified, will be "
        "read from the config file.",
    )

    parser.add_argument(
        "-k",
        "--signing-key-path",
        help="Path to the file containing the private ed25519 key to sign the "
        "request with.",
    )

    parser.add_argument(
        "-K",
        "--signing-key",
        help="The private ed25519 key to sign the request with.",
    )

    parser.add_argument(
        "-c",
        "--config",
        default="homeserver.yaml",
        help=
        ("Path to synapse config file, from which the server name and/or signing "
         "key path will be read. Ignored if --server-name and --signing-key(-path) "
         "are both given."),
    )

    parser.add_argument(
        "--sign-event-room-version",
        type=str,
        help=
        ("Sign the JSON as an event for the given room version, rather than raw JSON. "
         "This means that we will add a 'hashes' object, and redact the event before "
         "signing."),
    )

    input_args = parser.add_mutually_exclusive_group()

    input_args.add_argument("input_data",
                            nargs="?",
                            help="Raw JSON to be signed.")

    input_args.add_argument(
        "-i",
        "--input",
        type=argparse.FileType("r"),
        default=sys.stdin,
        help=
        ("A file from which to read the JSON to be signed. If neither --input nor "
         "input_data are given, JSON will be read from stdin."),
    )

    parser.add_argument(
        "-o",
        "--output",
        type=argparse.FileType("w"),
        default=sys.stdout,
        help="Where to write the signed JSON. Defaults to stdout.",
    )

    args = parser.parse_args()

    if not args.server_name or not (args.signing_key_path or args.signing_key):
        read_args_from_config(args)

    if args.signing_key:
        keys = read_signing_keys([args.signing_key])
    else:
        with open(args.signing_key_path) as f:
            keys = read_signing_keys(f)

    json_to_sign = args.input_data
    if json_to_sign is None:
        json_to_sign = args.input.read()

    try:
        obj = json.loads(json_to_sign)
    except JSONDecodeError as e:
        print("Unable to parse input as JSON: %s" % e, file=sys.stderr)
        sys.exit(1)

    if not isinstance(obj, dict):
        print("Input json was not an object", file=sys.stderr)
        sys.exit(1)

    if args.sign_event_room_version:
        room_version = KNOWN_ROOM_VERSIONS.get(args.sign_event_room_version)
        if not room_version:
            print(f"Unknown room version {args.sign_event_room_version}",
                  file=sys.stderr)
            sys.exit(1)
        add_hashes_and_signatures(room_version, obj, args.server_name, keys[0])
    else:
        sign_json(obj, args.server_name, keys[0])

    for c in json_encoder.iterencode(obj):
        args.output.write(c)
    args.output.write("\n")