def test_invalid_nested(self) -> None: """List and dictionaries are recursively searched.""" with self.assertRaises(SynapseError): event_from_pdu_json( { "type": EventTypes.Message, "content": {"foo": [{"bar": 2**56}]}, "room_id": "!room:test", "sender": "@user:test", "depth": 1, "prev_events": [], "auth_events": [], "origin_server_ts": 1234, }, RoomVersions.V6, )
def on_invite_request(self, origin, content): pdu = event_from_pdu_json(content) origin_host, _ = parse_server_name(origin) yield self.check_server_matches_acl(origin_host, pdu.room_id) ret_pdu = yield self.handler.on_invite_request(origin, pdu) time_now = self._clock.time_msec() defer.returnValue((200, {"event": ret_pdu.get_pdu_json(time_now)}))
def send_invite(self, destination, room_id, event_id, pdu): time_now = self._clock.time_msec() try: code, content = yield self.transport_layer.send_invite( destination=destination, room_id=room_id, event_id=event_id, content=pdu.get_pdu_json(time_now), ) except HttpResponseException as e: if e.code == 403: raise e.to_synapse_error() raise pdu_dict = content["event"] logger.debug("Got response to send_invite: %s", pdu_dict) pdu = event_from_pdu_json(pdu_dict) # Check signatures are correct. pdu = yield self._check_sigs_and_hash(pdu) # FIXME: We should handle signature failures more gracefully. defer.returnValue(pdu)
def get_event_auth(self, destination, room_id, event_id): res = yield self.transport_layer.get_event_auth( destination, room_id, event_id, ) room_version = yield self.store.get_room_version(room_id) format_ver = room_version_to_event_format(room_version) auth_chain = [ event_from_pdu_json(p, format_ver, outlier=True) for p in res["auth_chain"] ] signed_auth = yield self._check_sigs_and_hash_and_fetch( destination, auth_chain, outlier=True, room_version=room_version, ) signed_auth.sort(key=lambda e: e.depth) defer.returnValue(signed_auth)
def test_prune_gap(self): """Test that we drop extremities after a gap when we see an event from the same domain. """ # Fudge a second event which points to an event we don't have. This is a # state event so that the state changes (otherwise we won't prune the # extremity as they'll have the same state group). remote_event_2 = event_from_pdu_json( { "type": EventTypes.Member, "state_key": "@user:other", "content": {"membership": Membership.JOIN}, "room_id": self.room_id, "sender": "@user:other", "depth": 50, "prev_events": ["$some_unknown_message"], "auth_events": [], "origin_server_ts": self.clock.time_msec(), }, RoomVersions.V6, ) state_before_gap = self.get_success(self.state.get_current_state(self.room_id)) self.persist_event(remote_event_2, state=state_before_gap.values()) # Check the new extremity is just the new remote event. self.assert_extremities([remote_event_2.event_id])
async def backfill( self, dest: str, room_id: str, limit: int, extremities: Iterable[str]) -> Optional[List[EventBase]]: """Requests some more historic PDUs for the given room from the given destination server. Args: dest (str): The remote homeserver to ask. room_id (str): The room_id to backfill. limit (int): The maximum number of events to return. extremities (list): our current backwards extremities, to backfill from """ logger.debug("backfill extrem=%s", extremities) # If there are no extremities then we've (probably) reached the start. if not extremities: return None transaction_data = await self.transport_layer.backfill( dest, room_id, extremities, limit) logger.debug("backfill transaction_data=%r", transaction_data) room_version = await self.store.get_room_version(room_id) pdus = [ event_from_pdu_json(p, room_version, outlier=False) for p in transaction_data["pdus"] ] # Check signatures and hash of pdus, removing any from the list that fail checks pdus[:] = await self._check_sigs_and_hash_and_fetch( dest, pdus, outlier=True, room_version=room_version) return pdus
def test_prune_gap_if_old(self): """Test that we drop extremities after a gap when the previous extremity is "old" """ # Advance the clock for many days to make the old extremity "old". We # also set the depth to "lots". self.reactor.advance(7 * 24 * 60 * 60) # Fudge a second event which points to an event we don't have. This is a # state event so that the state changes (otherwise we won't prune the # extremity as they'll have the same state group). remote_event_2 = event_from_pdu_json( { "type": EventTypes.Member, "state_key": "@user:other2", "content": {"membership": Membership.JOIN}, "room_id": self.room_id, "sender": "@user:other2", "depth": 10000, "prev_events": ["$some_unknown_message"], "auth_events": [], "origin_server_ts": self.clock.time_msec(), }, RoomVersions.V6, ) state_before_gap = self.get_success(self.state.get_current_state(self.room_id)) self.persist_event(remote_event_2, state=state_before_gap.values()) # Check the new extremity is just the new remote event. self.assert_extremities([remote_event_2.event_id])
def test_do_not_prune_gap_if_not_dummy(self): """Test that we do not drop extremities after a gap when the previous extremity is not a dummy event. """ body = self.helper.send(self.room_id, body="test", tok=self.token) local_message_event_id = body["event_id"] self.assert_extremities([local_message_event_id]) # Fudge a second event which points to an event we don't have. This is a # state event so that the state changes (otherwise we won't prune the # extremity as they'll have the same state group). remote_event_2 = event_from_pdu_json( { "type": EventTypes.Member, "state_key": "@user:other2", "content": {"membership": Membership.JOIN}, "room_id": self.room_id, "sender": "@user:other2", "depth": 10000, "prev_events": ["$some_unknown_message"], "auth_events": [], "origin_server_ts": self.clock.time_msec(), }, RoomVersions.V6, ) state_before_gap = self.get_success(self.state.get_current_state(self.room_id)) self.persist_event(remote_event_2, state=state_before_gap.values()) # Check the new extremity is just the new remote event. self.assert_extremities([local_message_event_id, remote_event_2.event_id])
def send_invite(self, destination, room_id, event_id, pdu): time_now = self._clock.time_msec() try: code, content = yield self.transport_layer.send_invite( destination=destination, room_id=room_id, event_id=event_id, content=pdu.get_pdu_json(time_now), ) except HttpResponseException as e: if e.code == 403: raise e.to_synapse_error() raise pdu_dict = content["event"] logger.debug("Got response to send_invite: %s", pdu_dict) pdu = event_from_pdu_json(pdu_dict) # Check signatures are correct. pdu = yield self._check_sigs_and_hash(pdu) # FIXME: We should handle signature failures more gracefully. defer.returnValue(pdu)
def prepare(self, reactor, clock, homeserver): self.state = self.hs.get_state_handler() self.persistence = self.hs.get_storage().persistence self.store = self.hs.get_datastore() self.register_user("user", "pass") self.token = self.login("user", "pass") self.room_id = self.helper.create_room_as( "user", room_version=RoomVersions.V6.identifier, tok=self.token) body = self.helper.send(self.room_id, body="Test", tok=self.token) local_message_event_id = body["event_id"] # Fudge a remote event and persist it. This will be the extremity before # the gap. self.remote_event_1 = event_from_pdu_json( { "type": EventTypes.Message, "state_key": "@user:other", "content": {}, "room_id": self.room_id, "sender": "@user:other", "depth": 5, "prev_events": [local_message_event_id], "auth_events": [], "origin_server_ts": self.clock.time_msec(), }, RoomVersions.V6, ) self.persist_event(self.remote_event_1) # Check that the current extremities is the remote event. self.assert_extremities([self.remote_event_1.event_id])
async def on_send_join_request(self, origin, content, room_id): logger.debug("on_send_join_request: content: %s", content) room_version = await self.store.get_room_version(room_id) format_ver = room_version_to_event_format(room_version) pdu = event_from_pdu_json(content, format_ver) 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 ( 200, { "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"] ], }, )
def query_auth(self, destination, room_id, event_id, local_auth): """ Params: destination (str) event_it (str) local_auth (list) """ time_now = self._clock.time_msec() send_content = { "auth_chain": [e.get_pdu_json(time_now) for e in local_auth], } code, content = yield self.transport_layer.send_query_auth( destination=destination, room_id=room_id, event_id=event_id, content=send_content, ) auth_chain = [event_from_pdu_json(e) for e in content["auth_chain"]] signed_auth = yield self._check_sigs_and_hash_and_fetch(destination, auth_chain, outlier=True) signed_auth.sort(key=lambda e: e.depth) ret = { "auth_chain": signed_auth, "rejects": content.get("rejects", []), "missing": content.get("missing", []), } defer.returnValue(ret)
def on_invite_request(self, origin, content): pdu = event_from_pdu_json(content) origin_host, _ = parse_server_name(origin) yield self.check_server_matches_acl(origin_host, pdu.room_id) ret_pdu = yield self.handler.on_invite_request(origin, pdu) time_now = self._clock.time_msec() defer.returnValue((200, {"event": ret_pdu.get_pdu_json(time_now)}))
async def get_event_auth(destination: str, room_id: str, event_id: str) -> List[EventBase]: return [ event_from_pdu_json(ae.get_pdu_json(), room_version=room_version, outlier=True) for ae in auth_events ]
def on_query_auth_request(self, origin, content, room_id, event_id): """ Content is a dict with keys:: auth_chain (list): A list of events that give the auth chain. missing (list): A list of event_ids indicating what the other side (`origin`) think we're missing. rejects (dict): A mapping from event_id to a 2-tuple of reason string and a proof (or None) of why the event was rejected. The keys of this dict give the list of events the `origin` has rejected. Args: origin (str) content (dict) event_id (str) Returns: Deferred: Results in `dict` with the same format as `content` """ with (yield self._server_linearizer.queue((origin, room_id))): origin_host, _ = parse_server_name(origin) yield self.check_server_matches_acl(origin_host, room_id) room_version = yield self.store.get_room_version(room_id) format_ver = room_version_to_event_format(room_version) auth_chain = [ event_from_pdu_json(e, format_ver) for e in content["auth_chain"] ] signed_auth = yield self._check_sigs_and_hash_and_fetch( origin, auth_chain, outlier=True, room_version=room_version, ) ret = yield self.handler.on_query_auth( origin, event_id, room_id, signed_auth, content.get("rejects", []), content.get("missing", []), ) time_now = self._clock.time_msec() send_content = { "auth_chain": [ e.get_pdu_json(time_now) for e in ret["auth_chain"] ], "rejects": ret.get("rejects", []), "missing": ret.get("missing", []), } defer.returnValue( (200, send_content) )
def on_query_auth_request(self, origin, content, room_id, event_id): """ Content is a dict with keys:: auth_chain (list): A list of events that give the auth chain. missing (list): A list of event_ids indicating what the other side (`origin`) think we're missing. rejects (dict): A mapping from event_id to a 2-tuple of reason string and a proof (or None) of why the event was rejected. The keys of this dict give the list of events the `origin` has rejected. Args: origin (str) content (dict) event_id (str) Returns: Deferred: Results in `dict` with the same format as `content` """ with (yield self._server_linearizer.queue((origin, room_id))): origin_host, _ = parse_server_name(origin) yield self.check_server_matches_acl(origin_host, room_id) room_version = yield self.store.get_room_version(room_id) format_ver = room_version_to_event_format(room_version) auth_chain = [ event_from_pdu_json(e, format_ver) for e in content["auth_chain"] ] signed_auth = yield self._check_sigs_and_hash_and_fetch( origin, auth_chain, outlier=True, room_version=room_version, ) ret = yield self.handler.on_query_auth( origin, event_id, room_id, signed_auth, content.get("rejects", []), content.get("missing", []), ) time_now = self._clock.time_msec() send_content = { "auth_chain": [ e.get_pdu_json(time_now) for e in ret["auth_chain"] ], "rejects": ret.get("rejects", []), "missing": ret.get("missing", []), } defer.returnValue( (200, send_content) )
def on_invite_request(self, origin, content, room_version): format_ver = room_version_to_event_format(room_version) pdu = event_from_pdu_json(content, format_ver) origin_host, _ = parse_server_name(origin) yield self.check_server_matches_acl(origin_host, pdu.room_id) ret_pdu = yield self.handler.on_invite_request(origin, pdu) time_now = self._clock.time_msec() defer.returnValue({"event": ret_pdu.get_pdu_json(time_now)})
async def get_missing_events( self, destination: str, room_id: str, earliest_events_ids: Sequence[str], latest_events: Iterable[EventBase], limit: int, min_depth: int, timeout: int, ) -> List[EventBase]: """Tries to fetch events we are missing. This is called when we receive an event without having received all of its ancestors. Args: destination room_id earliest_events_ids: List of event ids. Effectively the events we expected to receive, but haven't. `get_missing_events` should only return events that didn't happen before these. latest_events: List of events we have received that we don't have all previous events for. limit: Maximum number of events to return. min_depth: Minimum depth of events to return. timeout: Max time to wait in ms """ try: content = await self.transport_layer.get_missing_events( destination=destination, room_id=room_id, earliest_events=earliest_events_ids, latest_events=[e.event_id for e in latest_events], limit=limit, min_depth=min_depth, timeout=timeout, ) room_version = await self.store.get_room_version(room_id) events = [ event_from_pdu_json(e, room_version) for e in content.get("events", []) ] signed_events = await self._check_sigs_and_hash_and_fetch( destination, events, outlier=False, room_version=room_version.identifier) except HttpResponseException as e: if not e.code == 400: raise # We are probably hitting an old server that doesn't support # get_missing_events signed_events = [] return signed_events
def on_send_leave_request(self, origin, content): logger.debug("on_send_leave_request: content: %s", content) pdu = event_from_pdu_json(content) origin_host, _ = parse_server_name(origin) yield self.check_server_matches_acl(origin_host, pdu.room_id) logger.debug("on_send_leave_request: pdu sigs: %s", pdu.signatures) yield self.handler.on_send_leave_request(origin, pdu) defer.returnValue((200, {}))
def on_send_leave_request(self, origin, content): logger.debug("on_send_leave_request: content: %s", content) pdu = event_from_pdu_json(content) origin_host, _ = parse_server_name(origin) yield self.check_server_matches_acl(origin_host, pdu.room_id) logger.debug("on_send_leave_request: pdu sigs: %s", pdu.signatures) yield self.handler.on_send_leave_request(origin, pdu) defer.returnValue((200, {}))
def get_missing_events( self, destination, room_id, earliest_events_ids, latest_events, limit, min_depth, timeout, ): """Tries to fetch events we are missing. This is called when we receive an event without having received all of its ancestors. Args: destination (str) room_id (str) earliest_events_ids (list): List of event ids. Effectively the events we expected to receive, but haven't. `get_missing_events` should only return events that didn't happen before these. latest_events (list): List of events we have received that we don't have all previous events for. limit (int): Maximum number of events to return. min_depth (int): Minimum depth of events tor return. timeout (int): Max time to wait in ms """ try: content = yield self.transport_layer.get_missing_events( destination=destination, room_id=room_id, earliest_events=earliest_events_ids, latest_events=[e.event_id for e in latest_events], limit=limit, min_depth=min_depth, timeout=timeout, ) room_version = yield self.store.get_room_version(room_id) format_ver = room_version_to_event_format(room_version) events = [ event_from_pdu_json(e, format_ver) for e in content.get("events", []) ] signed_events = yield self._check_sigs_and_hash_and_fetch( destination, events, outlier=False, room_version=room_version) except HttpResponseException as e: if not e.code == 400: raise # We are probably hitting an old server that doesn't support # get_missing_events signed_events = [] defer.returnValue(signed_events)
def test_rejected_state_event_state(self): """ Check that we store the state group correctly for rejected state events. Regression test for #6289. """ OTHER_SERVER = "otherserver" OTHER_USER = "******" + OTHER_SERVER # create the room user_id = self.register_user("kermit", "test") tok = self.login("kermit", "test") room_id = self.helper.create_room_as(room_creator=user_id, tok=tok) room_version = self.get_success(self.store.get_room_version(room_id)) # pretend that another server has joined join_event = self._build_and_send_join_event(OTHER_SERVER, OTHER_USER, room_id) # check the state group sg = self.successResultOf( self.store._get_state_group_for_event(join_event.event_id)) # build and send an event which will be rejected ev = event_from_pdu_json( { "type": "org.matrix.test", "state_key": "test_key", "content": {}, "room_id": room_id, "sender": "@yetanotheruser:"******"depth": join_event["depth"] + 1, "prev_events": [join_event.event_id], "auth_events": [], "origin_server_ts": self.clock.time_msec(), }, room_version, ) with LoggingContext("send_rejected"): d = run_in_background( self.hs.get_federation_event_handler().on_receive_pdu, OTHER_SERVER, ev) self.get_success(d) # that should have been rejected e = self.get_success( self.store.get_event(ev.event_id, allow_rejected=True)) self.assertIsNotNone(e.rejected_reason) # ... and the state group should be the same as before sg2 = self.successResultOf( self.store._get_state_group_for_event(ev.event_id)) self.assertEqual(sg, sg2)
def on_send_join_request(self, origin, content): logger.debug("on_send_join_request: content: %s", content) pdu = event_from_pdu_json(content) logger.debug("on_send_join_request: pdu sigs: %s", pdu.signatures) res_pdus = yield self.handler.on_send_join_request(origin, pdu) time_now = self._clock.time_msec() defer.returnValue((200, { "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"]], }))
def on_send_join_request(self, origin, content): logger.debug("on_send_join_request: content: %s", content) pdu = event_from_pdu_json(content) logger.debug("on_send_join_request: pdu sigs: %s", pdu.signatures) res_pdus = yield self.handler.on_send_join_request(origin, pdu) time_now = self._clock.time_msec() defer.returnValue((200, { "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"] ], }))
def on_send_leave_request(self, origin, content, room_id): logger.debug("on_send_leave_request: content: %s", content) room_version = yield self.store.get_room_version(room_id) format_ver = room_version_to_event_format(room_version) pdu = event_from_pdu_json(content, format_ver) origin_host, _ = parse_server_name(origin) yield self.check_server_matches_acl(origin_host, pdu.room_id) logger.debug("on_send_leave_request: pdu sigs: %s", pdu.signatures) yield self.handler.on_send_leave_request(origin, pdu) defer.returnValue((200, {}))
def on_send_leave_request(self, origin, content, room_id): logger.debug("on_send_leave_request: content: %s", content) room_version = yield self.store.get_room_version(room_id) format_ver = room_version_to_event_format(room_version) pdu = event_from_pdu_json(content, format_ver) origin_host, _ = parse_server_name(origin) yield self.check_server_matches_acl(origin_host, pdu.room_id) logger.debug("on_send_leave_request: pdu sigs: %s", pdu.signatures) yield self.handler.on_send_leave_request(origin, pdu) defer.returnValue((200, {}))
def test_room_remote_user_cache_invalidated(self): """Test that if the server leaves a room the `get_users_in_room` cache is invalidated for remote users. """ # Set up a room with a local and remote user in it. user_id = self.register_user("user", "pass") token = self.login("user", "pass") room_id = self.helper.create_room_as( "user", room_version=RoomVersions.V6.identifier, tok=token) body = self.helper.send(room_id, body="Test", tok=token) local_message_event_id = body["event_id"] # Fudge a join event for a remote user. remote_user = "******" remote_event_1 = event_from_pdu_json( { "type": EventTypes.Member, "state_key": remote_user, "content": { "membership": Membership.JOIN }, "room_id": room_id, "sender": remote_user, "depth": 5, "prev_events": [local_message_event_id], "auth_events": [], "origin_server_ts": self.clock.time_msec(), }, RoomVersions.V6, ) context = self.get_success( self.state.compute_event_context(remote_event_1)) self.get_success( self._persistence.persist_event(remote_event_1, context)) # Call `get_users_in_room` to add the remote user to the cache users = self.get_success(self.store.get_users_in_room(room_id)) self.assertEqual(set(users), {user_id, remote_user}) # Now we have the local server leave the room, and check that calling # `get_user_in_room` for the remote user no longer includes the room. self.helper.leave(room_id, user_id, tok=token) users = self.get_success(self.store.get_users_in_room(room_id)) self.assertEqual(users, [])
def test_invalid_numbers(self) -> None: """Invalid values for an integer should be rejected, all floats should be rejected.""" for value in [ -(2**53), 2**53, 1.0, float("inf"), float("-inf"), float("nan"), ]: with self.assertRaises(SynapseError): event_from_pdu_json( { "type": EventTypes.Message, "content": {"foo": value}, "room_id": "!room:test", "sender": "@user:test", "depth": 1, "prev_events": [], "auth_events": [], "origin_server_ts": 1234, }, RoomVersions.V6, )
async def on_send_leave_request(self, origin, content, room_id): logger.debug("on_send_leave_request: content: %s", content) room_version = await self.store.get_room_version(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.identifier, pdu) await self.handler.on_send_leave_request(origin, pdu) return {}
def get_missing_events(self, destination, room_id, earliest_events_ids, latest_events, limit, min_depth, timeout): """Tries to fetch events we are missing. This is called when we receive an event without having received all of its ancestors. Args: destination (str) room_id (str) earliest_events_ids (list): List of event ids. Effectively the events we expected to receive, but haven't. `get_missing_events` should only return events that didn't happen before these. latest_events (list): List of events we have received that we don't have all previous events for. limit (int): Maximum number of events to return. min_depth (int): Minimum depth of events tor return. timeout (int): Max time to wait in ms """ try: content = yield self.transport_layer.get_missing_events( destination=destination, room_id=room_id, earliest_events=earliest_events_ids, latest_events=[e.event_id for e in latest_events], limit=limit, min_depth=min_depth, timeout=timeout, ) room_version = yield self.store.get_room_version(room_id) format_ver = room_version_to_event_format(room_version) events = [ event_from_pdu_json(e, format_ver) for e in content.get("events", []) ] signed_events = yield self._check_sigs_and_hash_and_fetch( destination, events, outlier=False, room_version=room_version, ) except HttpResponseException as e: if not e.code == 400: raise # We are probably hitting an old server that doesn't support # get_missing_events signed_events = [] defer.returnValue(signed_events)
def test_strip_unauthorized_unsigned_values(self): event1 = { "sender": "@baduser:test.serv", "state_key": "@baduser:test.serv", "event_id": "$event1:test.serv", "depth": 1000, "origin_server_ts": 1, "type": "m.room.member", "origin": "test.servx", "content": {"membership": "join"}, "auth_events": [], "unsigned": {"malicious garbage": "hackz", "more warez": "more hackz"}, } filtered_event = event_from_pdu_json(event1, RoomVersions.V1) # Make sure unauthorized fields are stripped from unsigned self.assertNotIn("more warez", filtered_event.unsigned)
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 {}
def on_invite_request(self, origin, content, room_version): if room_version not in KNOWN_ROOM_VERSIONS: raise SynapseError( 400, "Homeserver does not support this room version", Codes.UNSUPPORTED_ROOM_VERSION, ) format_ver = room_version_to_event_format(room_version) pdu = event_from_pdu_json(content, format_ver) origin_host, _ = parse_server_name(origin) yield self.check_server_matches_acl(origin_host, pdu.room_id) ret_pdu = yield self.handler.on_invite_request(origin, pdu) time_now = self._clock.time_msec() defer.returnValue({"event": ret_pdu.get_pdu_json(time_now)})
def on_invite_request(self, origin, content, room_version): if room_version not in KNOWN_ROOM_VERSIONS: raise SynapseError( 400, "Homeserver does not support this room version", Codes.UNSUPPORTED_ROOM_VERSION, ) format_ver = room_version_to_event_format(room_version) pdu = event_from_pdu_json(content, format_ver) origin_host, _ = parse_server_name(origin) yield self.check_server_matches_acl(origin_host, pdu.room_id) ret_pdu = yield self.handler.on_invite_request(origin, pdu) time_now = self._clock.time_msec() defer.returnValue({"event": ret_pdu.get_pdu_json(time_now)})
def get_event_auth(self, destination, room_id, event_id): res = yield self.transport_layer.get_event_auth( destination, room_id, event_id, ) auth_chain = [ event_from_pdu_json(p, outlier=True) for p in res["auth_chain"] ] signed_auth = yield self._check_sigs_and_hash_and_fetch( destination, auth_chain, outlier=True ) signed_auth.sort(key=lambda e: e.depth) defer.returnValue(signed_auth)
def test_valid_json(self) -> None: """Valid JSON should be turned into an event.""" ev = event_from_pdu_json( { "type": EventTypes.Message, "content": {"bool": True, "null": None, "int": 1, "str": "foobar"}, "room_id": "!room:test", "sender": "@user:test", "depth": 1, "prev_events": [], "auth_events": [], "origin_server_ts": 1234, }, RoomVersions.V6, ) self.assertIsInstance(ev, EventBase)
def create_invite(): room_id = self.helper.create_room_as(room_creator=user_id, tok=tok) room_version = self.get_success(self.store.get_room_version(room_id)) return event_from_pdu_json( { "type": EventTypes.Member, "content": {"membership": "invite"}, "room_id": room_id, "sender": other_user, "state_key": "@user:test", "depth": 32, "prev_events": [], "auth_events": [], "origin_server_ts": self.clock.time_msec(), }, room_version, )
async def get_event_auth(self, destination, room_id, event_id): res = await self.transport_layer.get_event_auth( destination, room_id, event_id) room_version = await self.store.get_room_version(room_id) auth_chain = [ event_from_pdu_json(p, room_version, outlier=True) for p in res["auth_chain"] ] signed_auth = await self._check_sigs_and_hash_and_fetch( destination, auth_chain, outlier=True, room_version=room_version) signed_auth.sort(key=lambda e: e.depth) return signed_auth
def test_prune_gap_if_dummy_local(self): """Test that we don't drop extremities after a gap when the previous extremity is a local dummy event and points to local events. """ body = self.helper.send(self.room_id, body="Test", tok=self.token) body = self.helper.send_event(self.room_id, type=EventTypes.Dummy, content={}, tok=self.token) local_message_event_id = body["event_id"] self.assert_extremities([local_message_event_id]) # Advance the clock for many days to make the old extremity "old". We # also set the depth to "lots". self.reactor.advance(7 * 24 * 60 * 60) # Fudge a second event which points to an event we don't have. This is a # state event so that the state changes (otherwise we won't prune the # extremity as they'll have the same state group). remote_event_2 = event_from_pdu_json( { "type": EventTypes.Member, "state_key": "@user:other2", "content": { "membership": Membership.JOIN }, "room_id": self.room_id, "sender": "@user:other2", "depth": 10000, "prev_events": ["$some_unknown_message"], "auth_events": [], "origin_server_ts": self.clock.time_msec(), }, RoomVersions.V6, ) state_before_gap = self.get_success( self._state_storage_controller.get_current_state_ids(self.room_id)) self.persist_event(remote_event_2, state=state_before_gap) # Check the new extremity is just the new remote event. self.assert_extremities( [remote_event_2.event_id, local_message_event_id])
def on_send_join_request(self, origin, content, room_id): logger.debug("on_send_join_request: content: %s", content) room_version = yield self.store.get_room_version(room_id) format_ver = room_version_to_event_format(room_version) pdu = event_from_pdu_json(content, format_ver) origin_host, _ = parse_server_name(origin) yield self.check_server_matches_acl(origin_host, pdu.room_id) logger.debug("on_send_join_request: pdu sigs: %s", pdu.signatures) res_pdus = yield self.handler.on_send_join_request(origin, pdu) time_now = self._clock.time_msec() defer.returnValue((200, { "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"] ], }))
def send_invite(self, destination, room_id, event_id, pdu): room_version = yield self.store.get_room_version(room_id) content = yield self._do_send_invite(destination, pdu, room_version) pdu_dict = content["event"] logger.debug("Got response to send_invite: %s", pdu_dict) room_version = yield self.store.get_room_version(room_id) format_ver = room_version_to_event_format(room_version) pdu = event_from_pdu_json(pdu_dict, format_ver) # Check signatures are correct. pdu = yield self._check_sigs_and_hash(room_version, pdu) # FIXME: We should handle signature failures more gracefully. defer.returnValue(pdu)
def query_auth(self, destination, room_id, event_id, local_auth): """ Params: destination (str) event_it (str) local_auth (list) """ time_now = self._clock.time_msec() send_content = { "auth_chain": [e.get_pdu_json(time_now) for e in local_auth], } code, content = yield self.transport_layer.send_query_auth( destination=destination, room_id=room_id, event_id=event_id, content=send_content, ) room_version = yield self.store.get_room_version(room_id) format_ver = room_version_to_event_format(room_version) auth_chain = [ event_from_pdu_json(e, format_ver) for e in content["auth_chain"] ] signed_auth = yield self._check_sigs_and_hash_and_fetch( destination, auth_chain, outlier=True, room_version=room_version, ) signed_auth.sort(key=lambda e: e.depth) ret = { "auth_chain": signed_auth, "rejects": content.get("rejects", []), "missing": content.get("missing", []), } defer.returnValue(ret)
def backfill(self, dest, room_id, limit, extremities): """Requests some more historic PDUs for the given context from the given destination server. Args: dest (str): The remote home server to ask. room_id (str): The room_id to backfill. limit (int): The maximum number of PDUs to return. extremities (list): List of PDU id and origins of the first pdus we have seen from the context Returns: Deferred: Results in the received PDUs. """ logger.debug("backfill extrem=%s", extremities) # If there are no extremeties then we've (probably) reached the start. if not extremities: return transaction_data = yield self.transport_layer.backfill( dest, room_id, extremities, limit) logger.debug("backfill transaction_data=%s", repr(transaction_data)) room_version = yield self.store.get_room_version(room_id) format_ver = room_version_to_event_format(room_version) pdus = [ event_from_pdu_json(p, format_ver, outlier=False) for p in transaction_data["pdus"] ] # FIXME: We should handle signature failures more gracefully. pdus[:] = yield logcontext.make_deferred_yieldable(defer.gatherResults( self._check_sigs_and_hashes(room_version, pdus), consumeErrors=True, ).addErrback(unwrapFirstError)) defer.returnValue(pdus)
def _handle_incoming_transaction(self, transaction, request_time): """ Process an incoming transaction and return the HTTP response Args: transaction (Transaction): incoming transaction request_time (int): timestamp that the HTTP request arrived at Returns: Deferred[(int, object)]: http response code and body """ response = yield self.transaction_actions.have_responded(transaction) if response: logger.debug( "[%s] We've already responded to this request", transaction.transaction_id ) defer.returnValue(response) return logger.debug("[%s] Transaction is new", transaction.transaction_id) received_pdus_counter.inc_by(len(transaction.pdus)) pdus_by_room = {} for p in transaction.pdus: 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"] event = event_from_pdu_json(p) room_id = event.room_id pdus_by_room.setdefault(room_id, []).append(event) 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. @defer.inlineCallbacks def process_pdus_for_room(room_id): logger.debug("Processing PDUs for %s", room_id) for pdu in pdus_by_room[room_id]: event_id = pdu.event_id try: yield self._handle_received_pdu( transaction.origin, pdu ) pdu_results[event_id] = {} except FederationError as e: logger.warn("Error handling PDU %s: %s", event_id, e) pdu_results[event_id] = {"error": str(e)} except Exception as e: pdu_results[event_id] = {"error": str(e)} logger.exception("Failed to handle PDU %s", event_id) yield async.concurrently_execute( process_pdus_for_room, pdus_by_room.keys(), TRANSACTION_CONCURRENCY_LIMIT, ) if hasattr(transaction, "edus"): for edu in (Edu(**x) for x in transaction.edus): yield self.received_edu( transaction.origin, edu.edu_type, edu.content ) pdu_failures = getattr(transaction, "pdu_failures", []) for failure in pdu_failures: logger.info("Got failure %r", failure) response = { "pdus": pdu_results, } logger.debug("Returning: %s", str(response)) yield self.transaction_actions.set_response( transaction, 200, response ) defer.returnValue((200, response))
def send_request(destination): time_now = self._clock.time_msec() _, content = yield self.transport_layer.send_join( destination=destination, room_id=pdu.room_id, event_id=pdu.event_id, content=pdu.get_pdu_json(time_now), ) logger.debug("Got content: %s", content) state = [ event_from_pdu_json(p, event_format_version, outlier=True) for p in content.get("state", []) ] auth_chain = [ event_from_pdu_json(p, event_format_version, outlier=True) for p in content.get("auth_chain", []) ] pdus = { p.event_id: p for p in itertools.chain(state, auth_chain) } room_version = None for e in state: if (e.type, e.state_key) == (EventTypes.Create, ""): room_version = e.content.get( "room_version", RoomVersions.V1.identifier ) break if room_version is None: # If the state doesn't have a create event then the room is # invalid, and it would fail auth checks anyway. raise SynapseError(400, "No create event in state") valid_pdus = yield self._check_sigs_and_hash_and_fetch( destination, list(pdus.values()), outlier=True, room_version=room_version, ) valid_pdus_map = { p.event_id: p for p in valid_pdus } # NB: We *need* to copy to ensure that we don't have multiple # references being passed on, as that causes... issues. signed_state = [ copy.copy(valid_pdus_map[p.event_id]) for p in state if p.event_id in valid_pdus_map ] signed_auth = [ valid_pdus_map[p.event_id] for p in auth_chain if p.event_id in valid_pdus_map ] # NB: We *need* to copy to ensure that we don't have multiple # references being passed on, as that causes... issues. for s in signed_state: s.internal_metadata = copy.deepcopy(s.internal_metadata) check_authchain_validity(signed_auth) defer.returnValue({ "state": signed_state, "auth_chain": signed_auth, "origin": destination, })
def on_invite_request(self, origin, content): pdu = event_from_pdu_json(content) ret_pdu = yield self.handler.on_invite_request(origin, pdu) time_now = self._clock.time_msec() defer.returnValue((200, {"event": ret_pdu.get_pdu_json(time_now)}))
def get_pdu(self, destinations, event_id, outlier=False, timeout=None): """Requests the PDU with given origin and ID from the remote home servers. Will attempt to get the PDU from each destination in the list until one succeeds. Args: destinations (list): Which home servers to query event_id (str): event to fetch outlier (bool): Indicates whether the PDU is an `outlier`, i.e. if it's from an arbitary point in the context as opposed to part of the current block of PDUs. Defaults to `False` timeout (int): How long to try (in ms) each destination for before moving to the next destination. None indicates no timeout. Returns: Deferred: Results in the requested PDU. """ # TODO: Rate limit the number of times we try and get the same event. ev = self._get_pdu_cache.get(event_id) if ev: defer.returnValue(ev) pdu_attempts = self.pdu_destination_tried.setdefault(event_id, {}) signed_pdu = None for destination in destinations: now = self._clock.time_msec() last_attempt = pdu_attempts.get(destination, 0) if last_attempt + PDU_RETRY_TIME_MS > now: continue try: transaction_data = yield self.transport_layer.get_event( destination, event_id, timeout=timeout, ) logger.debug("transaction_data %r", transaction_data) pdu_list = [ event_from_pdu_json(p, outlier=outlier) for p in transaction_data["pdus"] ] if pdu_list and pdu_list[0]: pdu = pdu_list[0] # Check signatures are correct. signed_pdu = yield self._check_sigs_and_hash(pdu) break pdu_attempts[destination] = now except SynapseError as e: logger.info( "Failed to get PDU %s from %s because %s", event_id, destination, e, ) except NotRetryingDestination as e: logger.info(str(e)) continue except FederationDeniedError as e: logger.info(str(e)) continue except Exception as e: pdu_attempts[destination] = now logger.info( "Failed to get PDU %s from %s because %s", event_id, destination, e, ) continue if signed_pdu: self._get_pdu_cache[event_id] = signed_pdu defer.returnValue(signed_pdu)
def get_state_for_room(self, destination, room_id, event_id): """Requests all of the room state at a given event from a remote home server. Args: destination (str): The remote homeserver to query for the state. room_id (str): The id of the room we're interested in. event_id (str): The id of the event we want the state at. Returns: Deferred[Tuple[List[EventBase], List[EventBase]]]: A list of events in the state, and a list of events in the auth chain for the given event. """ try: # First we try and ask for just the IDs, as thats far quicker if # we have most of the state and auth_chain already. # However, this may 404 if the other side has an old synapse. result = yield self.transport_layer.get_room_state_ids( destination, room_id, event_id=event_id, ) state_event_ids = result["pdu_ids"] auth_event_ids = result.get("auth_chain_ids", []) fetched_events, failed_to_fetch = yield self.get_events( [destination], room_id, set(state_event_ids + auth_event_ids) ) if failed_to_fetch: logger.warn("Failed to get %r", failed_to_fetch) event_map = { ev.event_id: ev for ev in fetched_events } pdus = [event_map[e_id] for e_id in state_event_ids if e_id in event_map] auth_chain = [ event_map[e_id] for e_id in auth_event_ids if e_id in event_map ] auth_chain.sort(key=lambda e: e.depth) defer.returnValue((pdus, auth_chain)) except HttpResponseException as e: if e.code == 400 or e.code == 404: logger.info("Failed to use get_room_state_ids API, falling back") else: raise e result = yield self.transport_layer.get_room_state( destination, room_id, event_id=event_id, ) pdus = [ event_from_pdu_json(p, outlier=True) for p in result["pdus"] ] auth_chain = [ event_from_pdu_json(p, outlier=True) for p in result.get("auth_chain", []) ] seen_events = yield self.store.get_events([ ev.event_id for ev in itertools.chain(pdus, auth_chain) ]) signed_pdus = yield self._check_sigs_and_hash_and_fetch( destination, [p for p in pdus if p.event_id not in seen_events], outlier=True ) signed_pdus.extend( seen_events[p.event_id] for p in pdus if p.event_id in seen_events ) signed_auth = yield self._check_sigs_and_hash_and_fetch( destination, [p for p in auth_chain if p.event_id not in seen_events], outlier=True ) signed_auth.extend( seen_events[p.event_id] for p in auth_chain if p.event_id in seen_events ) signed_auth.sort(key=lambda e: e.depth) defer.returnValue((signed_pdus, signed_auth))
def send_request(destination): time_now = self._clock.time_msec() _, content = yield self.transport_layer.send_join( destination=destination, room_id=pdu.room_id, event_id=pdu.event_id, content=pdu.get_pdu_json(time_now), ) logger.debug("Got content: %s", content) state = [ event_from_pdu_json(p, outlier=True) for p in content.get("state", []) ] auth_chain = [ event_from_pdu_json(p, outlier=True) for p in content.get("auth_chain", []) ] pdus = { p.event_id: p for p in itertools.chain(state, auth_chain) } valid_pdus = yield self._check_sigs_and_hash_and_fetch( destination, list(pdus.values()), outlier=True, ) valid_pdus_map = { p.event_id: p for p in valid_pdus } # NB: We *need* to copy to ensure that we don't have multiple # references being passed on, as that causes... issues. signed_state = [ copy.copy(valid_pdus_map[p.event_id]) for p in state if p.event_id in valid_pdus_map ] signed_auth = [ valid_pdus_map[p.event_id] for p in auth_chain if p.event_id in valid_pdus_map ] # NB: We *need* to copy to ensure that we don't have multiple # references being passed on, as that causes... issues. for s in signed_state: s.internal_metadata = copy.deepcopy(s.internal_metadata) check_authchain_validity(signed_auth) defer.returnValue({ "state": signed_state, "auth_chain": signed_auth, "origin": destination, })
def _handle_incoming_transaction(self, origin, transaction, request_time): """ Process an incoming transaction and return the HTTP response Args: origin (unicode): the server making the request transaction (Transaction): incoming transaction request_time (int): timestamp that the HTTP request arrived at Returns: Deferred[(int, object)]: http response code and body """ response = yield self.transaction_actions.have_responded(origin, transaction) if response: logger.debug( "[%s] We've already responded to this request", transaction.transaction_id ) defer.returnValue(response) return logger.debug("[%s] Transaction is new", transaction.transaction_id) # Reject if PDU count > 50 and EDU count > 100 if (len(transaction.pdus) > 50 or (hasattr(transaction, "edus") and len(transaction.edus) > 100)): logger.info( "Transaction PDU or EDU count too large. Returning 400", ) response = {} yield self.transaction_actions.set_response( origin, transaction, 400, response ) defer.returnValue((400, response)) received_pdus_counter.inc(len(transaction.pdus)) origin_host, _ = parse_server_name(origin) pdus_by_room = {} for p in transaction.pdus: 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 = yield self.store.get_room_version(room_id) except NotFoundError: logger.info("Ignoring PDU for unknown room_id: %s", room_id) continue try: format_ver = room_version_to_event_format(room_version) except UnsupportedRoomVersionError: # 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 for room %s with unknown version %s", room_id, room_version, ) continue event = event_from_pdu_json(p, format_ver) pdus_by_room.setdefault(room_id, []).append(event) 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. @defer.inlineCallbacks def process_pdus_for_room(room_id): logger.debug("Processing PDUs for %s", room_id) try: yield self.check_server_matches_acl(origin_host, room_id) except AuthError as e: logger.warn( "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 nested_logging_context(event_id): try: yield self._handle_received_pdu( origin, pdu ) pdu_results[event_id] = {} except FederationError as e: logger.warn("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()), ) yield concurrently_execute( process_pdus_for_room, pdus_by_room.keys(), TRANSACTION_CONCURRENCY_LIMIT, ) if hasattr(transaction, "edus"): for edu in (Edu(**x) for x in transaction.edus): yield self.received_edu( origin, edu.edu_type, edu.content ) response = { "pdus": pdu_results, } logger.debug("Returning: %s", str(response)) yield self.transaction_actions.set_response( origin, transaction, 200, response ) defer.returnValue((200, response))
def on_send_leave_request(self, origin, content): logger.debug("on_send_leave_request: content: %s", content) pdu = event_from_pdu_json(content) logger.debug("on_send_leave_request: pdu sigs: %s", pdu.signatures) yield self.handler.on_send_leave_request(origin, pdu) defer.returnValue((200, {}))