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
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]}
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
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}
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)
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
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")
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}
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 {}
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)}
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, }
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
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"]], }
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
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 )