예제 #1
0
    async def on_room_state_request(
            self, origin: str, room_id: str,
            event_id: str) -> Tuple[int, Dict[str, Any]]:
        origin_host, _ = parse_server_name(origin)
        await self.check_server_matches_acl(origin_host, room_id)

        in_room = await self.auth.check_host_in_room(room_id, origin)
        if not in_room:
            raise AuthError(403, "Host not in room.")

        # we grab the linearizer to protect ourselves from servers which hammer
        # us. In theory we might already have the response to this query
        # in the cache so we could return it without waiting for the linearizer
        # - but that's non-trivial to get right, and anyway somewhat defeats
        # the point of the linearizer.
        with (await self._server_linearizer.queue((origin, room_id))):
            resp = dict(await self._state_resp_cache.wrap(
                (room_id, event_id),
                self._on_context_state_request_compute,
                room_id,
                event_id,
            ))

        room_version = await self.store.get_room_version_id(room_id)
        resp["room_version"] = room_version

        return 200, resp
예제 #2
0
    async def on_get_missing_events(
        self,
        origin: str,
        room_id: str,
        earliest_events: List[str],
        latest_events: List[str],
        limit: int,
    ) -> Dict[str, list]:
        with (await self._server_linearizer.queue((origin, room_id))):
            origin_host, _ = parse_server_name(origin)
            await self.check_server_matches_acl(origin_host, room_id)

            logger.debug(
                "on_get_missing_events: earliest_events: %r, latest_events: %r,"
                " limit: %d",
                earliest_events,
                latest_events,
                limit,
            )

            missing_events = await self.handler.on_get_missing_events(
                origin, room_id, earliest_events, latest_events, limit)

            if len(missing_events) < 5:
                logger.debug("Returning %d events: %r", len(missing_events),
                             missing_events)
            else:
                logger.debug("Returning %d events", len(missing_events))

            time_now = self._clock.time_msec()

        return {"events": [ev.get_pdu_json(time_now) for ev in missing_events]}
예제 #3
0
    async def on_event_auth(self, origin: str, room_id: str,
                            event_id: str) -> Tuple[int, Dict[str, Any]]:
        with (await self._server_linearizer.queue((origin, room_id))):
            origin_host, _ = parse_server_name(origin)
            await self.check_server_matches_acl(origin_host, room_id)

            time_now = self._clock.time_msec()
            auth_pdus = await self.handler.on_event_auth(event_id)
            res = {"auth_chain": [a.get_pdu_json(time_now) for a in auth_pdus]}
        return 200, res
예제 #4
0
    async def on_make_leave_request(
        self, origin: str, room_id: str, user_id: str
    ) -> Dict[str, Any]:
        origin_host, _ = parse_server_name(origin)
        await self.check_server_matches_acl(origin_host, room_id)
        pdu = await self.handler.on_make_leave_request(origin, room_id, user_id)

        room_version = await self.store.get_room_version_id(room_id)

        return {"event": pdu.get_templated_pdu_json(), "room_version": room_version}
예제 #5
0
    def test_parse_server_name(self):
        test_data = {
            "localhost": ("localhost", None),
            "my-example.com:1234": ("my-example.com", 1234),
            "1.2.3.4": ("1.2.3.4", None),
            "[0abc:1def::1234]": ("[0abc:1def::1234]", None),
            "1.2.3.4:1": ("1.2.3.4", 1),
            "[0abc:1def::1234]:8080": ("[0abc:1def::1234]", 8080),
        }

        for i, o in test_data.items():
            self.assertEqual(parse_server_name(i), o)
예제 #6
0
    async def on_backfill_request(self, origin: str, room_id: str,
                                  versions: List[str],
                                  limit: int) -> Tuple[int, Dict[str, Any]]:
        with (await self._server_linearizer.queue((origin, room_id))):
            origin_host, _ = parse_server_name(origin)
            await self.check_server_matches_acl(origin_host, room_id)

            pdus = await self.handler.on_backfill_request(
                origin, room_id, versions, limit)

            res = self._transaction_from_pdus(pdus).get_dict()

        return 200, res
예제 #7
0
    def __init__(self, hs: "HomeServer"):
        super().__init__()
        self._serve_server_wellknown = hs.config.server.serve_server_wellknown

        host, port = parse_server_name(hs.config.server.server_name)

        # If we've got this far, then https://<server_name>/ must route to us, so
        # we just redirect the traffic to port 443 instead of 8448.
        if port is None:
            port = 443

        self._response = json_encoder.encode({
            "m.server": f"{host}:{port}"
        }).encode("utf-8")
예제 #8
0
    async def on_make_join_request(
        self, origin: str, room_id: str, user_id: str, supported_versions: List[str]
    ) -> Dict[str, Any]:
        origin_host, _ = parse_server_name(origin)
        await self.check_server_matches_acl(origin_host, room_id)

        room_version = await self.store.get_room_version_id(room_id)
        if room_version not in supported_versions:
            logger.warning(
                "Room version %s not in %s", room_version, supported_versions
            )
            raise IncompatibleRoomVersionError(room_version=room_version)

        pdu = await self.handler.on_make_join_request(origin, room_id, user_id)
        return {"event": pdu.get_templated_pdu_json(), "room_version": room_version}
예제 #9
0
    async def on_send_leave_request(self, origin: str, content: JsonDict) -> dict:
        logger.debug("on_send_leave_request: content: %s", content)

        assert_params_in_dict(content, ["room_id"])
        room_version = await self.store.get_room_version(content["room_id"])
        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)

        logger.debug("on_send_leave_request: pdu sigs: %s", pdu.signatures)

        pdu = await self._check_sigs_and_hash(room_version, pdu)

        await self.handler.on_send_leave_request(origin, pdu)
        return {}
예제 #10
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)}
예제 #11
0
    async def on_make_knock_request(
        self, origin: str, room_id: str, user_id: str, supported_versions: List[str]
    ) -> Dict[str, Union[EventBase, str]]:
        """We've received a /make_knock/ request, so we create a partial knock
        event for the room and hand that back, along with the room version, to the knocking
        homeserver. We do *not* persist or process this event until the other server has
        signed it and sent it back.

        Args:
            origin: The (verified) server name of the requesting server.
            room_id: The room to create the knock event in.
            user_id: The user to create the knock for.
            supported_versions: The room versions supported by the requesting server.

        Returns:
            The partial knock event.
        """
        origin_host, _ = parse_server_name(origin)
        await self.check_server_matches_acl(origin_host, room_id)

        room_version = await self.store.get_room_version(room_id)

        # Check that this room version is supported by the remote homeserver
        if room_version.identifier not in supported_versions:
            logger.warning(
                "Room version %s not in %s", room_version.identifier, supported_versions
            )
            raise IncompatibleRoomVersionError(room_version=room_version.identifier)

        # Check that this room supports knocking as defined by its room version
        if not room_version.msc2403_knocking:
            raise SynapseError(
                403,
                "This room version does not support knocking",
                errcode=Codes.FORBIDDEN,
            )

        pdu = await self.handler.on_make_knock_request(origin, room_id, user_id)
        return {
            "event": pdu.get_templated_pdu_json(),
            "room_version": room_version.identifier,
        }
예제 #12
0
    async def on_state_ids_request(
            self, origin: str, room_id: str,
            event_id: str) -> Tuple[int, Dict[str, Any]]:
        if not event_id:
            raise NotImplementedError("Specify an event")

        origin_host, _ = parse_server_name(origin)
        await self.check_server_matches_acl(origin_host, room_id)

        in_room = await self.auth.check_host_in_room(room_id, origin)
        if not in_room:
            raise AuthError(403, "Host not in room.")

        resp = await self._state_ids_resp_cache.wrap(
            (room_id, event_id),
            self._on_state_ids_request_compute,
            room_id,
            event_id,
        )

        return 200, resp
예제 #13
0
    async def on_send_join_request(self, origin: str,
                                   content: JsonDict) -> Dict[str, Any]:
        logger.debug("on_send_join_request: content: %s", content)

        assert_params_in_dict(content, ["room_id"])
        room_version = await self.store.get_room_version(content["room_id"])
        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)

        logger.debug("on_send_join_request: pdu sigs: %s", pdu.signatures)

        pdu = await self._check_sigs_and_hash(room_version, pdu)

        res_pdus = await self.handler.on_send_join_request(origin, pdu)
        time_now = self._clock.time_msec()
        return {
            "state": [p.get_pdu_json(time_now) for p in res_pdus["state"]],
            "auth_chain":
            [p.get_pdu_json(time_now) for p in res_pdus["auth_chain"]],
        }
예제 #14
0
    async def _handle_pdus_in_txn(self, origin: str, transaction: Transaction,
                                  request_time: int) -> Dict[str, dict]:
        """Process the PDUs in a received transaction.

        Args:
            origin: the server making the request
            transaction: incoming transaction
            request_time: timestamp that the HTTP request arrived at

        Returns:
            A map from event ID of a processed PDU to any errors we should
            report back to the sending server.
        """

        received_pdus_counter.inc(len(transaction.pdus))  # type: ignore

        origin_host, _ = parse_server_name(origin)

        pdus_by_room = {}  # type: Dict[str, List[EventBase]]

        newest_pdu_ts = 0

        for p in transaction.pdus:  # type: ignore
            # FIXME (richardv): I don't think this works:
            #  https://github.com/matrix-org/synapse/issues/8429
            if "unsigned" in p:
                unsigned = p["unsigned"]
                if "age" in unsigned:
                    p["age"] = unsigned["age"]
            if "age" in p:
                p["age_ts"] = request_time - int(p["age"])
                del p["age"]

            # We try and pull out an event ID so that if later checks fail we
            # can log something sensible. We don't mandate an event ID here in
            # case future event formats get rid of the key.
            possible_event_id = p.get("event_id", "<Unknown>")

            # Now we get the room ID so that we can check that we know the
            # version of the room.
            room_id = p.get("room_id")
            if not room_id:
                logger.info(
                    "Ignoring PDU as does not have a room_id. Event ID: %s",
                    possible_event_id,
                )
                continue

            try:
                room_version = await self.store.get_room_version(room_id)
            except NotFoundError:
                logger.info("Ignoring PDU for unknown room_id: %s", room_id)
                continue
            except UnsupportedRoomVersionError as e:
                # this can happen if support for a given room version is withdrawn,
                # so that we still get events for said room.
                logger.info("Ignoring PDU: %s", e)
                continue

            event = event_from_pdu_json(p, room_version)
            pdus_by_room.setdefault(room_id, []).append(event)

            if event.origin_server_ts > newest_pdu_ts:
                newest_pdu_ts = event.origin_server_ts

        pdu_results = {}

        # we can process different rooms in parallel (which is useful if they
        # require callouts to other servers to fetch missing events), but
        # impose a limit to avoid going too crazy with ram/cpu.
        async def process_pdus_for_room(room_id: str):
            logger.debug("Processing PDUs for %s", room_id)
            try:
                await self.check_server_matches_acl(origin_host, room_id)
            except AuthError as e:
                logger.warning("Ignoring PDUs for room %s from banned server",
                               room_id)
                for pdu in pdus_by_room[room_id]:
                    event_id = pdu.event_id
                    pdu_results[event_id] = e.error_dict()
                return

            for pdu in pdus_by_room[room_id]:
                event_id = pdu.event_id
                with pdu_process_time.time():
                    with nested_logging_context(event_id):
                        try:
                            await self._handle_received_pdu(origin, pdu)
                            pdu_results[event_id] = {}
                        except FederationError as e:
                            logger.warning("Error handling PDU %s: %s",
                                           event_id, e)
                            pdu_results[event_id] = {"error": str(e)}
                        except Exception as e:
                            f = failure.Failure()
                            pdu_results[event_id] = {"error": str(e)}
                            logger.error(
                                "Failed to handle PDU %s",
                                event_id,
                                exc_info=(f.type, f.value,
                                          f.getTracebackObject()),
                            )

        await concurrently_execute(process_pdus_for_room, pdus_by_room.keys(),
                                   TRANSACTION_CONCURRENCY_LIMIT)

        if newest_pdu_ts and origin in self._federation_metrics_domains:
            newest_pdu_age = self._clock.time_msec() - newest_pdu_ts
            last_pdu_age_metric.labels(server_name=origin).set(newest_pdu_age /
                                                               1000)

        return pdu_results
예제 #15
0
    async def _on_send_membership_event(
        self, origin: str, content: JsonDict, membership_type: str, room_id: str
    ) -> Tuple[EventBase, EventContext]:
        """Handle an on_send_{join,leave,knock} request

        Does some preliminary validation before passing the request on to the
        federation handler.

        Args:
            origin: The (authenticated) requesting server
            content: The body of the send_* request - a complete membership event
            membership_type: The expected membership type (join or leave, depending
                on the endpoint)
            room_id: The room_id from the request, to be validated against the room_id
                in the event

        Returns:
            The event and context of the event after inserting it into the room graph.

        Raises:
            SynapseError if there is a problem with the request, including things like
               the room_id not matching or the event not being authorized.
        """
        assert_params_in_dict(content, ["room_id"])
        if content["room_id"] != room_id:
            raise SynapseError(
                400,
                "Room ID in body does not match that in request path",
                Codes.BAD_JSON,
            )

        room_version = await self.store.get_room_version(room_id)

        if membership_type == Membership.KNOCK and not room_version.msc2403_knocking:
            raise SynapseError(
                403,
                "This room version does not support knocking",
                errcode=Codes.FORBIDDEN,
            )

        event = event_from_pdu_json(content, room_version)

        if event.type != EventTypes.Member or not event.is_state():
            raise SynapseError(400, "Not an m.room.member event", Codes.BAD_JSON)

        if event.content.get("membership") != membership_type:
            raise SynapseError(400, "Not a %s event" % membership_type, Codes.BAD_JSON)

        origin_host, _ = parse_server_name(origin)
        await self.check_server_matches_acl(origin_host, event.room_id)

        logger.debug("_on_send_membership_event: pdu sigs: %s", event.signatures)

        # Sign the event since we're vouching on behalf of the remote server that
        # the event is valid to be sent into the room. Currently this is only done
        # if the user is being joined via restricted join rules.
        if (
            room_version.msc3083_join_rules
            and event.membership == Membership.JOIN
            and "join_authorised_via_users_server" in event.content
        ):
            # We can only authorise our own users.
            authorising_server = get_domain_from_id(
                event.content["join_authorised_via_users_server"]
            )
            if authorising_server != self.server_name:
                raise SynapseError(
                    400,
                    f"Cannot authorise request from resident server: {authorising_server}",
                )

            event.signatures.update(
                compute_event_signature(
                    room_version,
                    event.get_pdu_json(),
                    self.hs.hostname,
                    self.hs.signing_key,
                )
            )

        event = await self._check_sigs_and_hash(room_version, event)

        return await self._federation_event_handler.on_send_membership_event(
            origin, event
        )