def event_from_pdu_json( pdu_json: JsonDict, room_version: RoomVersion, outlier: bool = False ) -> EventBase: """Construct an EventBase from an event json received over federation Args: pdu_json: pdu as received over federation room_version: The version of the room this event belongs to outlier: True to mark this event as an outlier Raises: SynapseError: if the pdu is missing required fields or is otherwise not a valid matrix event """ # we could probably enforce a bunch of other fields here (room_id, sender, # origin, etc etc) assert_params_in_dict(pdu_json, ("type", "depth")) depth = pdu_json["depth"] if not isinstance(depth, six.integer_types): raise SynapseError(400, "Depth %r not an intger" % (depth,), Codes.BAD_JSON) if depth < 0: raise SynapseError(400, "Depth too small", Codes.BAD_JSON) elif depth > MAX_DEPTH: raise SynapseError(400, "Depth too large", Codes.BAD_JSON) event = make_event_from_dict(pdu_json, room_version) event.internal_metadata.outlier = outlier return event
async def _handle_request(self, request): with Measure(self.clock, "repl_fed_send_events_parse"): content = parse_json_object_from_request(request) room_id = content["room_id"] backfilled = content["backfilled"] event_payloads = content["events"] event_and_contexts = [] for event_payload in event_payloads: event_dict = event_payload["event"] room_ver = KNOWN_ROOM_VERSIONS[event_payload["room_version"]] internal_metadata = event_payload["internal_metadata"] rejected_reason = event_payload["rejected_reason"] event = make_event_from_dict(event_dict, room_ver, internal_metadata, rejected_reason) context = EventContext.deserialize(self.storage, event_payload["context"]) event_and_contexts.append((event, context)) logger.info("Got %d events from federation", len(event_and_contexts)) max_stream_id = await self.federation_handler.persist_events_and_notify( room_id, event_and_contexts, backfilled) return 200, {"max_stream_id": max_stream_id}
def test_sign_minimal(self): event_dict = { "event_id": "$0:domain", "origin": "domain", "origin_server_ts": 1000000, "signatures": {}, "type": "X", "unsigned": { "age_ts": 1000000 }, } add_hashes_and_signatures(RoomVersions.V1, event_dict, HOSTNAME, self.signing_key) event = make_event_from_dict(event_dict) self.assertTrue(hasattr(event, "hashes")) self.assertIn("sha256", event.hashes) self.assertEquals(event.hashes["sha256"], "6tJjLpXtggfke8UxFhAKg82QVkJzvKOVOOSjUDK4ZSI") self.assertTrue(hasattr(event, "signatures")) self.assertIn(HOSTNAME, event.signatures) self.assertIn(KEY_NAME, event.signatures["domain"]) self.assertEquals( event.signatures[HOSTNAME][KEY_NAME], "2Wptgo4CwmLo/Y8B8qinxApKaCkBG2fjTWB7AbP5Uy+" "aIbygsSdLOFzvdDjww8zUVKCmI02eP9xtyJxc/cLiBA", )
def to_event(self, auth_events, prev_events): """Given the auth_events and prev_events, convert to a Frozen Event Args: auth_events (list[str]): list of event_ids prev_events (list[str]): list of event_ids Returns: FrozenEvent """ global ORIGIN_SERVER_TS ts = ORIGIN_SERVER_TS ORIGIN_SERVER_TS = ORIGIN_SERVER_TS + 1 event_dict = { "auth_events": [(a, {}) for a in auth_events], "prev_events": [(p, {}) for p in prev_events], "event_id": self.node_id, "sender": self.sender, "type": self.type, "content": self.content, "origin_server_ts": ts, "room_id": ROOM_ID, } if self.state_key is not None: event_dict["state_key"] = self.state_key return make_event_from_dict(event_dict)
async def _handle_request(self, request, event_id): with Measure(self.clock, "repl_send_event_parse"): content = parse_json_object_from_request(request) event_dict = content["event"] room_ver = KNOWN_ROOM_VERSIONS[content["room_version"]] internal_metadata = content["internal_metadata"] rejected_reason = content["rejected_reason"] event = make_event_from_dict(event_dict, room_ver, internal_metadata, rejected_reason) requester = Requester.deserialize(self.store, content["requester"]) context = EventContext.deserialize(self.storage, content["context"]) ratelimit = content["ratelimit"] extra_users = [ UserID.from_string(u) for u in content["extra_users"] ] if requester.user: request.authenticated_entity = requester.user.to_string() logger.info("Got event to send with ID: %s into room: %s", event.event_id, event.room_id) stream_id = await self.event_creation_handler.persist_and_notify_client_event( requester, event, context, ratelimit=ratelimit, extra_users=extra_users) return 200, {"stream_id": stream_id}
def build_event( self, sender=USER_ID, room_id=ROOM_ID, type="m.room.message", key=None, internal: Optional[dict] = None, depth=None, prev_events: Optional[list] = None, auth_events: Optional[list] = None, prev_state: Optional[list] = None, redacts=None, push_actions: Iterable = frozenset(), **content, ): prev_events = prev_events or [] auth_events = auth_events or [] prev_state = prev_state or [] if depth is None: depth = self.event_id if not prev_events: latest_event_ids = self.get_success( self.master_store.get_latest_event_ids_in_room(room_id) ) prev_events = [(ev_id, {}) for ev_id in latest_event_ids] event_dict = { "sender": sender, "type": type, "content": content, "event_id": "$%d:blue" % (self.event_id,), "room_id": room_id, "depth": depth, "origin_server_ts": self.event_id, "prev_events": prev_events, "auth_events": auth_events, } if key is not None: event_dict["state_key"] = key event_dict["prev_state"] = prev_state if redacts is not None: event_dict["redacts"] = redacts event = make_event_from_dict(event_dict, internal_metadata_dict=internal or {}) self.event_id += 1 state_handler = self.hs.get_state_handler() context = self.get_success(state_handler.compute_event_context(event)) self.get_success( self.master_store.add_push_actions_to_staging( event.event_id, {user_id: actions for user_id, actions in push_actions}, False, ) ) return event, context
def _poke_fed_invite(self, room_id: str, from_user: str) -> None: """ Creates a invite (as if received over federation) for the room from the given hostname. Args: room_id: The room ID to issue an invite for. fed_hostname: The user to invite from. """ # Poke an invite over federation into the database. fed_handler = self.hs.get_federation_handler() fed_hostname = UserID.from_string(from_user).domain event = make_event_from_dict({ "room_id": room_id, "event_id": "!abcd:" + fed_hostname, "type": EventTypes.Member, "sender": from_user, "state_key": self.user, "content": { "membership": Membership.INVITE }, "prev_events": [], "auth_events": [], "depth": 1, "origin_server_ts": 1234, }) self.get_success( fed_handler.on_invite_request(fed_hostname, event, RoomVersions.V6))
def event_from_pdu_json(pdu_json: JsonDict, room_version: RoomVersion) -> EventBase: """Construct an EventBase from an event json received over federation Args: pdu_json: pdu as received over federation room_version: The version of the room this event belongs to Raises: SynapseError: if the pdu is missing required fields or is otherwise not a valid matrix event """ # we could probably enforce a bunch of other fields here (room_id, sender, # origin, etc etc) assert_params_in_dict(pdu_json, ("type", "depth")) # Strip any unauthorized values from "unsigned" if they exist if "unsigned" in pdu_json: _strip_unsigned_values(pdu_json) depth = pdu_json["depth"] if not isinstance(depth, int): raise SynapseError(400, "Depth %r not an intger" % (depth,), Codes.BAD_JSON) if depth < 0: raise SynapseError(400, "Depth too small", Codes.BAD_JSON) elif depth > MAX_DEPTH: raise SynapseError(400, "Depth too large", Codes.BAD_JSON) # Validate that the JSON conforms to the specification. if room_version.strict_canonicaljson: validate_canonicaljson(pdu_json) event = make_event_from_dict(pdu_json, room_version) return event
def test_sign_message(self): event_dict = { "content": { "body": "Here is the message content" }, "event_id": "$0:domain", "origin": "domain", "origin_server_ts": 1000000, "type": "m.room.message", "room_id": "!r:domain", "sender": "@u:domain", "signatures": {}, "unsigned": { "age_ts": 1000000 }, } add_hashes_and_signatures(RoomVersions.V1, event_dict, HOSTNAME, self.signing_key) event = make_event_from_dict(event_dict) self.assertTrue(hasattr(event, "hashes")) self.assertIn("sha256", event.hashes) self.assertEquals(event.hashes["sha256"], "onLKD1bGljeBWQhWZ1kaP9SorVmRQNdN5aM2JYU2n/g") self.assertTrue(hasattr(event, "signatures")) self.assertIn(HOSTNAME, event.signatures) self.assertIn(KEY_NAME, event.signatures["domain"]) self.assertEquals( event.signatures[HOSTNAME][KEY_NAME], "Wm+VzmOUOz08Ds+0NTWb1d4CZrVsJSikkeRxh6aCcUw" "u6pNC78FunoD7KNWzqFn241eYHYMGCA5McEiVPdhzBA", )
def _random_state_event( room_version: RoomVersion, sender: str, auth_events: Optional[Iterable[EventBase]] = None, ) -> EventBase: if auth_events is None: auth_events = [] return make_event_from_dict( { "room_id": TEST_ROOM_ID, **_maybe_get_event_id_dict_for_room_version(room_version), "type": "test.state", "sender": sender, "state_key": "", "content": { "membership": "join" }, "auth_events": _build_auth_dict_for_room_version(room_version, auth_events), }, room_version=room_version, )
def test_cant_hide_direct_ancestors(self): """ If you send a message, you must be able to provide the direct prev_events that said event references. """ async def post_json(destination, path, data, headers=None, timeout=0): # If it asks us for new missing events, give them NOTHING if path.startswith("/_matrix/federation/v1/get_missing_events/"): return {"events": []} self.http_client.post_json = post_json # Figure out what the most recent event is most_recent = self.get_success( self.store.get_latest_event_ids_in_room(self.room_id))[0] # Now lie about an event lying_event = make_event_from_dict({ "room_id": self.room_id, "sender": "@baduser:test.serv", "event_id": "one:test.serv", "depth": 1000, "origin_server_ts": 1, "type": "m.room.message", "origin": "test.serv", "content": { "body": "hewwo?" }, "auth_events": [], "prev_events": [("two:test.serv", {}), (most_recent, {})], }) federation_event_handler = self.homeserver.get_federation_event_handler( ) with LoggingContext("test-context"): failure = self.get_failure( federation_event_handler.on_receive_pdu( "test.serv", lying_event), FederationError, ) # on_receive_pdu should throw an error self.assertEqual( failure.value.args[0], ("ERROR 403: Your server isn't divulging details about prev_events " "referenced in this event."), ) # Make sure the invalid event isn't there extrem = self.get_success( self.store.get_latest_event_ids_in_room(self.room_id)) self.assertEqual(extrem[0], "$join:test.serv")
def _create_acl_event(content): return make_event_from_dict({ "room_id": "!a:b", "event_id": "$a:b", "type": "m.room.server_acls", "sender": "@a:b", "content": content, })
def finish(self) -> SendJoinResponse: for c in self._coros: c.close() if self._response.event_dict: self._response.event = make_event_from_dict( self._response.event_dict, self._room_version ) return self._response
def _event_list_parser(room_version: RoomVersion, events: List[EventBase]): """Helper function for use with `ijson.items_coro` to parse an array of events and add them to the given list. """ while True: obj = yield event = make_event_from_dict(obj, room_version) events.append(event)
def _power_levels_event(sender, content): return make_event_from_dict({ "room_id": TEST_ROOM_ID, "event_id": _get_event_id(), "type": "m.room.power_levels", "sender": sender, "state_key": "", "content": content, })
def _create_event(user_id): return make_event_from_dict({ "room_id": TEST_ROOM_ID, "event_id": _get_event_id(), "type": "m.room.create", "sender": user_id, "content": { "creator": user_id }, })
def test_create_event_with_prev_events(self): """A create event with prev_events should be rejected https://spec.matrix.org/v1.3/rooms/v9/#authorization-rules 1: If type is m.room.create: 1. If it has any previous events, reject. """ creator = f"@creator:{TEST_DOMAIN}" # we make both a good event and a bad event, to check that we are rejecting # the bad event for the reason we think we are. good_event = make_event_from_dict( { "room_id": TEST_ROOM_ID, "type": "m.room.create", "state_key": "", "sender": creator, "content": { "creator": creator, "room_version": RoomVersions.V9.identifier, }, "auth_events": [], "prev_events": [], }, room_version=RoomVersions.V9, ) bad_event = make_event_from_dict( { **good_event.get_dict(), "prev_events": ["$fakeevent"] }, room_version=RoomVersions.V9, ) event_store = _StubEventSourceStore() get_awaitable_result( event_auth.check_state_independent_auth_rules( event_store, good_event)) with self.assertRaises(AuthError): get_awaitable_result( event_auth.check_state_independent_auth_rules( event_store, bad_event))
def _alias_event(sender, **kwargs): data = { "room_id": TEST_ROOM_ID, "event_id": _get_event_id(), "type": "m.room.aliases", "sender": sender, "state_key": get_domain_from_id(sender), "content": {"aliases": []}, } data.update(**kwargs) return make_event_from_dict(data)
def _join_rules_event(sender, join_rule): return make_event_from_dict({ "room_id": TEST_ROOM_ID, "event_id": _get_event_id(), "type": "m.room.join_rules", "sender": sender, "state_key": "", "content": { "join_rule": join_rule, }, })
def _random_state_event(sender): return make_event_from_dict({ "room_id": TEST_ROOM_ID, "event_id": _get_event_id(), "type": "test.state", "sender": sender, "state_key": "", "content": { "membership": "join" }, })
def _join_event(user_id): return make_event_from_dict({ "room_id": TEST_ROOM_ID, "event_id": _get_event_id(), "type": "m.room.member", "sender": user_id, "state_key": user_id, "content": { "membership": "join" }, })
def _populate_events(self) -> None: """Ensure that there are test events in the database. When testing with the in-memory SQLite database, all the events are lost during the simulated outage. To ensure consistency between `room_id`s and `event_id`s before and after the outage, rows are built and inserted manually. Upserts are used to handle the non-SQLite case where events are not lost. """ self.get_success( self.store.db_pool.simple_upsert( "rooms", {"room_id": self.room_id}, {"room_version": RoomVersions.V4.identifier}, )) self.event_ids: List[str] = [] for idx in range(20): event_json = { "type": f"test {idx}", "room_id": self.room_id, } event = make_event_from_dict(event_json, room_version=RoomVersions.V4) event_id = event.event_id self.get_success( self.store.db_pool.simple_upsert( "events", {"event_id": event_id}, { "event_id": event_id, "room_id": self.room_id, "topological_ordering": idx, "stream_ordering": idx, "type": event.type, "processed": True, "outlier": False, }, )) self.get_success( self.store.db_pool.simple_upsert( "event_json", {"event_id": event_id}, { "room_id": self.room_id, "json": json.dumps(event_json), "internal_metadata": "{}", "format_version": EventFormatVersions.V3, }, )) self.event_ids.append(event_id)
def _member_event(user_id, membership, sender=None): return make_event_from_dict({ "room_id": TEST_ROOM_ID, "event_id": _get_event_id(), "type": "m.room.member", "sender": sender or user_id, "state_key": user_id, "content": { "membership": membership }, "prev_events": [], })
def run_test(self, evdict, matchdict, **kwargs): """ Asserts that a new event constructed with `evdict` will look like `matchdict` when it is redacted. Args: evdict: The dictionary to build the event from. matchdict: The expected resulting dictionary. kwargs: Additional keyword arguments used to create the event. """ self.assertEqual( prune_event(make_event_from_dict(evdict, **kwargs)).get_dict(), matchdict)
def prepare(self, reactor, clock, hs): self.store: EventsWorkerStore = hs.get_datastores().main # insert some test data for rid in ("room1", "room2"): self.get_success( self.store.db_pool.simple_insert( "rooms", { "room_id": rid, "room_version": 4 }, )) self.event_ids: List[str] = [] for idx, rid in enumerate(( "room1", "room1", "room1", "room2", )): event_json = {"type": f"test {idx}", "room_id": rid} event = make_event_from_dict(event_json, room_version=RoomVersions.V4) event_id = event.event_id self.get_success( self.store.db_pool.simple_insert( "events", { "event_id": event_id, "room_id": rid, "topological_ordering": idx, "stream_ordering": idx, "type": event.type, "processed": True, "outlier": False, }, )) self.get_success( self.store.db_pool.simple_insert( "event_json", { "event_id": event_id, "room_id": rid, "json": json.dumps(event_json), "internal_metadata": "{}", "format_version": 3, }, )) self.event_ids.append(event_id)
def _alias_event(room_version: RoomVersion, sender: str, **kwargs) -> EventBase: data = { "room_id": TEST_ROOM_ID, **_maybe_get_event_id_dict_for_room_version(room_version), "type": "m.room.aliases", "sender": sender, "state_key": get_domain_from_id(sender), "content": { "aliases": [] }, } data.update(**kwargs) return make_event_from_dict(data, room_version=room_version)
def _join_rules_event(room_version: RoomVersion, sender: str, join_rule: str) -> EventBase: return make_event_from_dict( { "room_id": TEST_ROOM_ID, **_maybe_get_event_id_dict_for_room_version(room_version), "type": "m.room.join_rules", "sender": sender, "state_key": "", "content": { "join_rule": join_rule, }, }, room_version=room_version, )
def _power_levels_event( room_version: RoomVersion, sender: str, content: JsonDict, ) -> EventBase: return make_event_from_dict( { "room_id": TEST_ROOM_ID, **_maybe_get_event_id_dict_for_room_version(room_version), "type": "m.room.power_levels", "sender": sender, "state_key": "", "content": content, }, room_version=room_version, )
def _member_event( user_id: str, membership: str, sender: Optional[str] = None, additional_content: Optional[dict] = None, ) -> EventBase: return make_event_from_dict( { "room_id": TEST_ROOM_ID, "event_id": _get_event_id(), "type": "m.room.member", "sender": sender or user_id, "state_key": user_id, "content": {"membership": membership, **(additional_content or {})}, "prev_events": [], } )
def _create_event( room_version: RoomVersion, user_id: str, ) -> EventBase: return make_event_from_dict( { "room_id": TEST_ROOM_ID, **_maybe_get_event_id_dict_for_room_version(room_version), "type": "m.room.create", "state_key": "", "sender": user_id, "content": { "creator": user_id }, "auth_events": [], }, room_version=room_version, )