def create_and_send_event(self, event_dict, ratelimit=True, token_id=None, txn_id=None, is_guest=False): """ Given a dict from a client, create and handle a new event. Creates an FrozenEvent object, filling out auth_events, prev_events, etc. Adds display names to Join membership events. Persists and notifies local clients and federation. Args: event_dict (dict): An entire event """ builder = self.event_builder_factory.new(event_dict) self.validator.validate_new(builder) if ratelimit: self.ratelimit(builder.user_id) # TODO(paul): Why does 'event' not have a 'user' object? user = UserID.from_string(builder.user_id) assert self.hs.is_mine(user), "User must be our own: %s" % (user,) if builder.type == EventTypes.Member: membership = builder.content.get("membership", None) if membership == Membership.JOIN: joinee = UserID.from_string(builder.state_key) # If event doesn't include a display name, add one. yield self.distributor.fire( "collect_presencelike_data", joinee, builder.content ) if token_id is not None: builder.internal_metadata.token_id = token_id if txn_id is not None: builder.internal_metadata.txn_id = txn_id event, context = yield self._create_new_client_event( builder=builder, ) if event.type == EventTypes.Member: member_handler = self.hs.get_handlers().room_member_handler yield member_handler.change_membership(event, context, is_guest=is_guest) else: yield self.handle_new_client_event( event=event, context=context, ) if event.type == EventTypes.Message: presence = self.hs.get_handlers().presence_handler with PreserveLoggingContext(): presence.bump_presence_active_time(user) defer.returnValue(event)
def on_POST(self, request, target_user_id): body = parse_json_object_from_request(request, allow_empty_body=True) erase = body.get("erase", False) if not isinstance(erase, bool): raise SynapseError( http_client.BAD_REQUEST, "Param 'erase' must be a boolean, if given", Codes.BAD_JSON, ) UserID.from_string(target_user_id) requester = yield self.auth.get_user_by_req(request) is_admin = yield self.auth.is_server_admin(requester.user) if not is_admin: raise AuthError(403, "You are not a server admin") result = yield self._deactivate_account_handler.deactivate_account( target_user_id, erase, ) if result: id_server_unbind_result = "success" else: id_server_unbind_result = "no-support" defer.returnValue((200, { "id_server_unbind_result": id_server_unbind_result, }))
def on_POST(self, request, target_user_id): """Post request to get specific number of users from Synapse.. This needs user to have administrator access in Synapse. Example: http://localhost:8008/_synapse/admin/v1/users_paginate/ @admin:user?access_token=admin_access_token JsonBodyToSend: { "start": "0", "limit": "10 } Returns: 200 OK with json object {list[dict[str, Any]], count} or empty object. """ yield assert_requester_is_admin(self.auth, request) UserID.from_string(target_user_id) order = "name" # order by name in user table params = parse_json_object_from_request(request) assert_params_in_dict(params, ["limit", "start"]) limit = params['limit'] start = params['start'] logger.info("limit: %s, start: %s", limit, start) ret = yield self.handlers.admin_handler.get_users_paginate( order, start, limit ) defer.returnValue((200, ret))
def setUp(self): self.mock_federation = Mock() self.mock_registry = Mock() self.query_handlers = {} def register_query_handler(query_type, handler): self.query_handlers[query_type] = handler self.mock_registry.register_query_handler = register_query_handler hs = yield setup_test_homeserver( self.addCleanup, http_client=None, handlers=None, resource_for_federation=Mock(), federation_client=self.mock_federation, federation_server=Mock(), federation_registry=self.mock_registry, ratelimiter=NonCallableMock(spec_set=["can_do_action"]), ) self.ratelimiter = hs.get_ratelimiter() self.ratelimiter.can_do_action.return_value = (True, 0) self.store = hs.get_datastore() self.frank = UserID.from_string("@1234ABCD:test") self.bob = UserID.from_string("@4567:test") self.alice = UserID.from_string("@alice:remote") yield self.store.create_profile(self.frank.localpart) self.handler = hs.get_profile_handler()
def setUp(self): hs = yield setup_test_homeserver(clock=MockClock()) self.store = PresenceStore(hs) self.u_apple = UserID.from_string("@apple:test") self.u_banana = UserID.from_string("@banana:test")
def test_compare(self): userA = UserID.from_string("@userA:my.domain") userAagain = UserID.from_string("@userA:my.domain") userB = UserID.from_string("@userB:my.domain") self.assertTrue(userA == userAagain) self.assertTrue(userA != userB)
def test_compare(self): userA = UserID.from_string("@userA:my.domain", hs=mock_homeserver) userAagain = UserID.from_string("@userA:my.domain", hs=mock_homeserver) userB = UserID.from_string("@userB:my.domain", hs=mock_homeserver) self.assertTrue(userA == userAagain) self.assertTrue(userA != userB)
def on_POST(self, request, room_id, membership_action, txn_id=None): user, client = yield self.auth.get_user_by_req(request) content = _parse_json(request) # target user is you unless it is an invite state_key = user.to_string() if membership_action in ["invite", "ban", "kick"]: if "user_id" not in content: raise SynapseError(400, "Missing user_id key.") state_key = content["user_id"] # make sure it looks like a user ID; it'll throw if it's invalid. UserID.from_string(state_key) if membership_action == "kick": membership_action = "leave" msg_handler = self.handlers.message_handler yield msg_handler.create_and_send_event( { "type": EventTypes.Member, "content": {"membership": unicode(membership_action)}, "room_id": room_id, "sender": user.to_string(), "state_key": state_key, }, client=client, txn_id=txn_id, ) defer.returnValue((200, {}))
def on_POST(self, request, room_id, membership_action, txn_id=None): user, token_id, is_guest = yield self.auth.get_user_by_req( request, allow_guest=True ) if is_guest and membership_action not in {Membership.JOIN, Membership.LEAVE}: raise AuthError(403, "Guest access not allowed") content = _parse_json(request) # target user is you unless it is an invite state_key = user.to_string() if membership_action == "invite" and self._has_3pid_invite_keys(content): yield self.handlers.room_member_handler.do_3pid_invite( room_id, user, content["medium"], content["address"], content["id_server"], token_id, txn_id ) defer.returnValue((200, {})) return elif membership_action in ["invite", "ban", "kick"]: if "user_id" in content: state_key = content["user_id"] else: raise SynapseError(400, "Missing user_id key.") # make sure it looks like a user ID; it'll throw if it's invalid. UserID.from_string(state_key) if membership_action == "kick": membership_action = "leave" msg_handler = self.handlers.message_handler content = {"membership": unicode(membership_action)} if is_guest: content["kind"] = "guest" yield msg_handler.create_and_send_event( { "type": EventTypes.Member, "content": content, "room_id": room_id, "sender": user.to_string(), "state_key": state_key, }, token_id=token_id, txn_id=txn_id, is_guest=is_guest, ) defer.returnValue((200, {}))
def handle_new_client_event(self, event, context, extra_destinations=[], extra_users=[], suppress_auth=False): yield run_on_reactor() # We now need to go and hit out to wherever we need to hit out to. if not suppress_auth: self.auth.check(event, auth_events=context.current_state) yield self.store.persist_event(event, context=context) federation_handler = self.hs.get_handlers().federation_handler if event.type == EventTypes.Member: if event.content["membership"] == Membership.INVITE: invitee = UserID.from_string(event.state_key) if not self.hs.is_mine(invitee): # TODO: Can we add signature from remote server in a nicer # way? If we have been invited by a remote server, we need # to get them to sign the event. returned_invite = yield federation_handler.send_invite( invitee.domain, event, ) # TODO: Make sure the signatures actually are correct. event.signatures.update( returned_invite.signatures ) destinations = set(extra_destinations) for k, s in context.current_state.items(): try: if k[0] == EventTypes.Member: if s.content["membership"] == Membership.JOIN: destinations.add( UserID.from_string(s.state_key).domain ) except SynapseError: logger.warn( "Failed to get destination from event %s", s.event_id ) # Don't block waiting on waking up all the listeners. d = self.notifier.on_new_room_event(event, extra_users=extra_users) def log_failure(f): logger.warn( "Failed to notify about %s: %s", event.event_id, f.value ) d.addErrback(log_failure) yield federation_handler.handle_new_event( event, destinations=destinations, )
def setUp(self): self.mock_resource = MockHttpResource(prefix=PATH_PREFIX) hs = yield setup_test_homeserver( datastore=Mock(spec=[ "has_presence_state", "get_presence_state", "allow_presence_visible", "is_presence_visible", "add_presence_list_pending", "set_presence_list_accepted", "del_presence_list", "get_presence_list", "insert_client_ip", ]), http_client=None, resource_for_client=self.mock_resource, resource_for_federation=self.mock_resource, ) hs.handlers = JustPresenceHandlers(hs) self.datastore = hs.get_datastore() self.datastore.get_app_service_by_token = Mock(return_value=None) def has_presence_state(user_localpart): return defer.succeed( user_localpart in ("apple", "banana",) ) self.datastore.has_presence_state = has_presence_state def _get_user_by_token(token=None): return { "user": UserID.from_string(myid), "admin": False, "device_id": None, "token_id": 1, } hs.handlers.room_member_handler = Mock( spec=[ "get_joined_rooms_for_user", ] ) hs.get_v1auth().get_user_by_token = _get_user_by_token presence.register_servlets(hs, self.mock_resource) self.u_apple = UserID.from_string("@apple:test") self.u_banana = UserID.from_string("@banana:test")
def _recv_edu(self, origin, content): room_id = content["room_id"] user_id = content["user_id"] # Check that the string is a valid user id UserID.from_string(user_id) domains = yield self.store.get_joined_hosts_for_room(room_id) if self.server_name in domains: self._push_update_local( room_id=room_id, user_id=user_id, typing=content["typing"] )
def setUp(self): hs = yield setup_test_homeserver(resource_for_federation=Mock(), http_client=None) self.store = hs.get_datastore() self.event_builder_factory = hs.get_event_builder_factory() self.handlers = hs.get_handlers() self.message_handler = self.handlers.message_handler self.u_alice = UserID.from_string("@alice:test") self.u_bob = UserID.from_string("@bob:test") self.room1 = RoomID.from_string("!abc123:test") self.room2 = RoomID.from_string("!xyx987:test") self.depth = 1
def on_POST(self, request, target_user_id): UserID.from_string(target_user_id) requester = yield self.auth.get_user_by_req(request) is_admin = yield self.auth.is_server_admin(requester.user) if not is_admin: raise AuthError(403, "You are not a server admin") # FIXME: Theoretically there is a race here wherein user resets password # using threepid. yield self.store.user_delete_access_tokens(target_user_id) yield self.store.user_delete_threepids(target_user_id) yield self.store.user_set_password_hash(target_user_id, None) defer.returnValue((200, {}))
def setUp(self): hs = yield tests.utils.setup_test_homeserver(self.addCleanup) self.store = hs.get_datastore() self.event_builder_factory = hs.get_event_builder_factory() self.event_creation_handler = hs.get_event_creation_handler() self.u_alice = UserID.from_string("@alice:test") self.u_bob = UserID.from_string("@bob:test") self.room = RoomID.from_string("!abc123:test") yield self.store.store_room( self.room.to_string(), room_creator_user_id="@creator:text", is_public=True )
def test_invited_remote_nonexistant(self): # Use a different destination, otherwise retry logic might fail the # request u_rocket = UserID.from_string("@rocket:sun") put_json = self.mock_http_client.put_json put_json.expect_call_and_return( call("sun", path="/_matrix/federation/v1/send/1000000/", data=_expect_edu("sun", "m.presence_deny", content={ "observer_user": "******", "observed_user": "******", } ), json_data_callback=ANY, long_retries=True, ), defer.succeed((200, "OK")) ) yield self.mock_federation_resource.trigger("PUT", "/_matrix/federation/v1/send/1000000/", _make_edu_json("sun", "m.presence_invite", content={ "observer_user": "******", "observed_user": "******", } ) ) yield put_json.await_calls()
def test_get_user_from_macaroon_with_valid_duration(self): # TODO(danielwh): Remove this mock when we remove the # get_user_by_access_token fallback. self.store.get_user_by_access_token = Mock( return_value={"name": "@baldrick:matrix.org"} ) self.store.get_user_by_access_token = Mock( return_value={"name": "@baldrick:matrix.org"} ) user_id = "@baldrick:matrix.org" macaroon = pymacaroons.Macaroon( location=self.hs.config.server_name, identifier="key", key=self.hs.config.macaroon_secret_key) macaroon.add_first_party_caveat("gen = 1") macaroon.add_first_party_caveat("type = access") macaroon.add_first_party_caveat("user_id = %s" % (user_id,)) macaroon.add_first_party_caveat("time < 900000000") # ms self.hs.clock.now = 5000 # seconds self.hs.config.expire_access_token = True user_info = yield self.auth.get_user_from_macaroon(macaroon.serialize()) user = user_info["user"] self.assertEqual(UserID.from_string(user_id), user)
def _get_user_by_token(token=None): return { "user": UserID.from_string(self.auth_user_id), "admin": False, "device_id": None, "token_id": 1, }
def get_inviter(self, event): # TODO(markjh): get prev_state from snapshot prev_state = yield self.store.get_room_member( event.user_id, event.room_id ) if prev_state and prev_state.membership == Membership.INVITE: defer.returnValue(UserID.from_string(prev_state.user_id)) return elif "third_party_invite" in event.content: if "sender" in event.content["third_party_invite"]: inviter = UserID.from_string( event.content["third_party_invite"]["sender"] ) defer.returnValue(inviter) defer.returnValue(None)
def on_PUT(self, request, user_id): auth_user, _, _ = yield self.auth.get_user_by_req(request) user = UserID.from_string(user_id) state = {} try: content = json.loads(request.content.read()) state["presence"] = content.pop("presence") if "status_msg" in content: state["status_msg"] = content.pop("status_msg") if not isinstance(state["status_msg"], basestring): raise SynapseError(400, "status_msg must be a string.") if content: raise KeyError() except SynapseError as e: raise e except: raise SynapseError(400, "Unable to parse state") yield self.handlers.presence_handler.set_state( target_user=user, auth_user=auth_user, state=state) defer.returnValue((200, {}))
def test_get_user_from_macaroon(self): # TODO(danielwh): Remove this mock when we remove the # get_user_by_access_token fallback. self.store.get_user_by_access_token = Mock( return_value={ "name": "@baldrick:matrix.org", "device_id": "device", } ) user_id = "@baldrick:matrix.org" macaroon = pymacaroons.Macaroon( location=self.hs.config.server_name, identifier="key", key=self.hs.config.macaroon_secret_key) macaroon.add_first_party_caveat("gen = 1") macaroon.add_first_party_caveat("type = access") macaroon.add_first_party_caveat("user_id = %s" % (user_id,)) user_info = yield self.auth.get_user_from_macaroon(macaroon.serialize()) user = user_info["user"] self.assertEqual(UserID.from_string(user_id), user) # TODO: device_id should come from the macaroon, but currently comes # from the db. self.assertEqual(user_info["device_id"], "device")
def test_filter_room_state_no_match(self): user_filter_json = { "room": { "state": { "types": ["m.*"] } } } user = UserID.from_string("@" + user_localpart + ":test") filter_id = yield self.datastore.add_user_filter( user_localpart=user_localpart, user_filter=user_filter_json, ) event = MockEvent( sender="@foo:bar", type="org.matrix.custom.event", room_id="!foo:bar" ) events = [event] user_filter = yield self.filtering.get_user_filter( user_localpart=user_localpart, filter_id=filter_id, ) results = user_filter.filter_room_state(events) self.assertEquals([], results)
def get_user_by_token(self, token): """ Get a registered user's ID. Args: token (str): The access token to get the user by. Returns: dict : dict that includes the user, device_id, and whether the user is a server admin. Raises: AuthError if no user by that token exists or the token is invalid. """ ret = yield self.store.get_user_by_token(token) if not ret: raise AuthError( self.TOKEN_NOT_FOUND_HTTP_STATUS, "Unrecognised access token.", errcode=Codes.UNKNOWN_TOKEN ) user_info = { "admin": bool(ret.get("admin", False)), "device_id": ret.get("device_id"), "user": UserID.from_string(ret.get("name")), "token_id": ret.get("token_id", None), } defer.returnValue(user_info)
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"] internal_metadata = content["internal_metadata"] rejected_reason = content["rejected_reason"] event = FrozenEvent(event_dict, internal_metadata, rejected_reason) requester = Requester.deserialize(self.store, content["requester"]) context = yield EventContext.deserialize(self.store, 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, ) yield self.event_creation_handler.persist_and_notify_client_event( requester, event, context, ratelimit=ratelimit, extra_users=extra_users, ) defer.returnValue((200, {}))
def test_filter_public_user_data_no_match(self): user_filter_json = { "public_user_data": { "types": ["m.*"] } } user = UserID.from_string("@" + user_localpart + ":test") filter_id = yield self.datastore.add_user_filter( user_localpart=user_localpart, user_filter=user_filter_json, ) event = MockEvent( sender="@foo:bar", type="custom.avatar.3d.crazy", room_id="!foo:bar" ) events = [event] user_filter = yield self.filtering.get_user_filter( user_localpart=user_localpart, filter_id=filter_id, ) results = user_filter.filter_public_user_data(events=events) self.assertEquals([], results)
def do_3pid_invite( self, room_id, inviter, medium, address, id_server, requester, txn_id ): invitee = yield self._lookup_3pid( id_server, medium, address ) if invitee: yield self.update_membership( requester, UserID.from_string(invitee), room_id, "invite", txn_id=txn_id, ) else: yield self._make_and_store_3pid_invite( requester, id_server, medium, address, room_id, inviter, txn_id=txn_id )
def test_invite_remote(self): # Use a different destination, otherwise retry logic might fail the # request u_rocket = UserID.from_string("@rocket:there") put_json = self.mock_http_client.put_json put_json.expect_call_and_return( call("there", path="/_matrix/federation/v1/send/1000000/", data=_expect_edu("there", "m.presence_invite", content={ "observer_user": "******", "observed_user": "******", } ), json_data_callback=ANY, long_retries=True, ), defer.succeed((200, "OK")) ) yield self.handler.send_presence_invite( observer_user=self.u_apple, observed_user=u_rocket) self.assertEquals( [{"observed_user_id": "@rocket:there", "accepted": 0}], (yield self.datastore.get_presence_list(self.u_apple.localpart)) ) yield put_json.await_calls()
def get_inviter(self, user_id, room_id): invite = yield self.store.get_invite_for_user_in_room( user_id=user_id, room_id=room_id, ) if invite: defer.returnValue(UserID.from_string(invite.sender))
def get_presence_list(self, observer_user, accepted=None): """Get the presence list for a local user. The retured list includes the current presence state for each user listed. Args: observer_user(UserID): The local user whose presence list to fetch. accepted(bool or None): If not none then only include users who have or have not accepted the presence invite request. Returns: A Deferred list of presence state events. """ if not self.hs.is_mine(observer_user): raise SynapseError(400, "User is not hosted on this Home Server") presence_list = yield self.store.get_presence_list( observer_user.localpart, accepted=accepted ) results = [] for row in presence_list: observed_user = UserID.from_string(row["observed_user_id"]) result = { "observed_user": observed_user, "accepted": row["accepted"] } result.update( self._get_or_offline_usercache(observed_user).get_state() ) if "last_active" in result: result["last_active_ago"] = int( self.clock.time_msec() - result.pop("last_active") ) results.append(result) defer.returnValue(results)
def on_PUT(self, request, user_id): requester = yield self.auth.get_user_by_req(request) user = UserID.from_string(user_id) if requester.user != user: raise AuthError(403, "Can only set your own presence state") state = {} content = parse_json_object_from_request(request) try: state["presence"] = content.pop("presence") if "status_msg" in content: state["status_msg"] = content.pop("status_msg") if not isinstance(state["status_msg"], string_types): raise SynapseError(400, "status_msg must be a string.") if content: raise KeyError() except SynapseError as e: raise e except Exception: raise SynapseError(400, "Unable to parse state") if self.hs.config.use_presence: yield self.presence_handler.set_state(user, state) defer.returnValue((200, {}))
async def on_PUT(self, request, user_id): requester = await self.auth.get_user_by_req(request) await assert_user_is_admin(self.auth, requester.user) target_user = UserID.from_string(user_id) body = parse_json_object_from_request(request) if not self.hs.is_mine(target_user): raise SynapseError(400, "This endpoint can only be used with local users") user = await self.admin_handler.get_user(target_user) user_id = target_user.to_string() if user: # modify user if "displayname" in body: await self.profile_handler.set_displayname( target_user, requester, body["displayname"], True ) if "threepids" in body: # check for required parameters for each threepid for threepid in body["threepids"]: assert_params_in_dict(threepid, ["medium", "address"]) # remove old threepids from user threepids = await self.store.user_get_threepids(user_id) for threepid in threepids: try: await self.auth_handler.delete_threepid( user_id, threepid["medium"], threepid["address"], None ) except Exception: logger.exception("Failed to remove threepids") raise SynapseError(500, "Failed to remove threepids") # add new threepids to user current_time = self.hs.get_clock().time_msec() for threepid in body["threepids"]: await self.auth_handler.add_threepid( user_id, threepid["medium"], threepid["address"], current_time ) if "avatar_url" in body and type(body["avatar_url"]) == str: await self.profile_handler.set_avatar_url( target_user, requester, body["avatar_url"], True ) if "admin" in body: set_admin_to = bool(body["admin"]) if set_admin_to != user["admin"]: auth_user = requester.user if target_user == auth_user and not set_admin_to: raise SynapseError(400, "You may not demote yourself.") await self.store.set_server_admin(target_user, set_admin_to) if "password" in body: if ( not isinstance(body["password"], text_type) or len(body["password"]) > 512 ): raise SynapseError(400, "Invalid password") else: new_password = body["password"] logout_devices = True new_password_hash = await self.auth_handler.hash(new_password) await self.set_password_handler.set_password( target_user.to_string(), new_password_hash, logout_devices, requester, ) if "deactivated" in body: deactivate = body["deactivated"] if not isinstance(deactivate, bool): raise SynapseError( 400, "'deactivated' parameter is not of type boolean" ) if deactivate and not user["deactivated"]: await self.deactivate_account_handler.deactivate_account( target_user.to_string(), False ) user = await self.admin_handler.get_user(target_user) return 200, user else: # create user password = body.get("password") password_hash = None if password is not None: if not isinstance(password, text_type) or len(password) > 512: raise SynapseError(400, "Invalid password") password_hash = await self.auth_handler.hash(password) admin = body.get("admin", None) user_type = body.get("user_type", None) displayname = body.get("displayname", None) threepids = body.get("threepids", None) if user_type is not None and user_type not in UserTypes.ALL_USER_TYPES: raise SynapseError(400, "Invalid user type") user_id = await self.registration_handler.register_user( localpart=target_user.localpart, password_hash=password_hash, admin=bool(admin), default_display_name=displayname, user_type=user_type, ) if "threepids" in body: # check for required parameters for each threepid for threepid in body["threepids"]: assert_params_in_dict(threepid, ["medium", "address"]) current_time = self.hs.get_clock().time_msec() for threepid in body["threepids"]: await self.auth_handler.add_threepid( user_id, threepid["medium"], threepid["address"], current_time ) if "avatar_url" in body and type(body["avatar_url"]) == str: await self.profile_handler.set_avatar_url( user_id, requester, body["avatar_url"], True ) ret = await self.admin_handler.get_user(target_user) return 201, ret
def create_room(self, requester, config, ratelimit=True): """ Creates a new room. Args: requester (Requester): The user who requested the room creation. config (dict) : A dict of configuration options. Returns: The new room ID. Raises: SynapseError if the room ID couldn't be stored, or something went horribly wrong. """ user_id = requester.user.to_string() if not self.spam_checker.user_may_create_room(user_id): raise SynapseError(403, "You are not permitted to create rooms") if ratelimit: yield self.ratelimit(requester) if "room_alias_name" in config: for wchar in string.whitespace: if wchar in config["room_alias_name"]: raise SynapseError(400, "Invalid characters in room alias") room_alias = RoomAlias( config["room_alias_name"], self.hs.hostname, ) mapping = yield self.store.get_association_from_room_alias( room_alias) if mapping: raise SynapseError(400, "Room alias already taken") else: room_alias = None invite_list = config.get("invite", []) for i in invite_list: try: UserID.from_string(i) except Exception: raise SynapseError(400, "Invalid user_id: %s" % (i, )) invite_3pid_list = config.get("invite_3pid", []) visibility = config.get("visibility", None) is_public = visibility == "public" # autogen room IDs and try to create it. We may clash, so just # try a few times till one goes through, giving up eventually. attempts = 0 room_id = None while attempts < 5: try: random_string = stringutils.random_string(18) gen_room_id = RoomID( random_string, self.hs.hostname, ) yield self.store.store_room(room_id=gen_room_id.to_string(), room_creator_user_id=user_id, is_public=is_public) room_id = gen_room_id.to_string() break except StoreError: attempts += 1 if not room_id: raise StoreError(500, "Couldn't generate a room ID.") if room_alias: directory_handler = self.hs.get_handlers().directory_handler yield directory_handler.create_association( user_id=user_id, room_id=room_id, room_alias=room_alias, servers=[self.hs.hostname], ) preset_config = config.get( "preset", RoomCreationPreset.PRIVATE_CHAT if visibility == "private" else RoomCreationPreset.PUBLIC_CHAT) raw_initial_state = config.get("initial_state", []) initial_state = OrderedDict() for val in raw_initial_state: initial_state[(val["type"], val.get("state_key", ""))] = val["content"] creation_content = config.get("creation_content", {}) msg_handler = self.hs.get_handlers().message_handler room_member_handler = self.hs.get_handlers().room_member_handler yield self._send_events_for_new_room( requester, room_id, msg_handler, room_member_handler, preset_config=preset_config, invite_list=invite_list, initial_state=initial_state, creation_content=creation_content, room_alias=room_alias, power_level_content_override=config.get( "power_level_content_override", {})) if "name" in config: name = config["name"] yield msg_handler.create_and_send_nonmember_event( requester, { "type": EventTypes.Name, "room_id": room_id, "sender": user_id, "state_key": "", "content": { "name": name }, }, ratelimit=False) if "topic" in config: topic = config["topic"] yield msg_handler.create_and_send_nonmember_event( requester, { "type": EventTypes.Topic, "room_id": room_id, "sender": user_id, "state_key": "", "content": { "topic": topic }, }, ratelimit=False) for invitee in invite_list: content = {} is_direct = config.get("is_direct", None) if is_direct: content["is_direct"] = is_direct yield room_member_handler.update_membership( requester, UserID.from_string(invitee), room_id, "invite", ratelimit=False, content=content, ) for invite_3pid in invite_3pid_list: id_server = invite_3pid["id_server"] address = invite_3pid["address"] medium = invite_3pid["medium"] yield self.hs.get_handlers().room_member_handler.do_3pid_invite( room_id, requester.user, medium, address, id_server, requester, txn_id=None, ) result = {"room_id": room_id} if room_alias: result["room_alias"] = room_alias.to_string() yield directory_handler.send_room_alias_update_event( requester, user_id, room_id) defer.returnValue(result)
async def on_rdata( self, stream_name: str, instance_name: str, token: int, rows: list ): """Called to handle a batch of replication data with a given stream token. By default this just pokes the slave store. Can be overridden in subclasses to handle more. Args: stream_name: name of the replication stream for this batch of rows instance_name: the instance that wrote the rows. token: stream token for this batch of rows rows: a list of Stream.ROW_TYPE objects as returned by Stream.parse_row. """ self.store.process_replication_rows(stream_name, instance_name, token, rows) if self.send_handler: await self.send_handler.process_replication_rows(stream_name, token, rows) if stream_name == TypingStream.NAME: self._typing_handler.process_replication_rows(token, rows) self.notifier.on_new_event( "typing_key", token, rooms=[row.room_id for row in rows] ) elif stream_name == PushRulesStream.NAME: self.notifier.on_new_event( "push_rules_key", token, users=[row.user_id for row in rows] ) elif stream_name in (AccountDataStream.NAME, TagAccountDataStream.NAME): self.notifier.on_new_event( "account_data_key", token, users=[row.user_id for row in rows] ) elif stream_name == ReceiptsStream.NAME: self.notifier.on_new_event( "receipt_key", token, rooms=[row.room_id for row in rows] ) await self._pusher_pool.on_new_receipts( token, token, {row.room_id for row in rows} ) elif stream_name == ToDeviceStream.NAME: entities = [row.entity for row in rows if row.entity.startswith("@")] if entities: self.notifier.on_new_event("to_device_key", token, users=entities) elif stream_name == DeviceListsStream.NAME: all_room_ids = set() # type: Set[str] for row in rows: if row.entity.startswith("@"): room_ids = await self.store.get_rooms_for_user(row.entity) all_room_ids.update(room_ids) self.notifier.on_new_event("device_list_key", token, rooms=all_room_ids) elif stream_name == GroupServerStream.NAME: self.notifier.on_new_event( "groups_key", token, users=[row.user_id for row in rows] ) elif stream_name == PushersStream.NAME: for row in rows: if row.deleted: self.stop_pusher(row.user_id, row.app_id, row.pushkey) else: await self.start_pusher(row.user_id, row.app_id, row.pushkey) elif stream_name == EventsStream.NAME: # We shouldn't get multiple rows per token for events stream, so # we don't need to optimise this for multiple rows. for row in rows: if row.type != EventsStreamEventRow.TypeId: continue assert isinstance(row, EventsStreamRow) assert isinstance(row.data, EventsStreamEventRow) if row.data.rejected: continue extra_users = () # type: Tuple[UserID, ...] if row.data.type == EventTypes.Member and row.data.state_key: extra_users = (UserID.from_string(row.data.state_key),) max_token = self.store.get_room_max_token() event_pos = PersistedEventPosition(instance_name, token) self.notifier.on_new_room_event_args( event_pos=event_pos, max_room_stream_token=max_token, extra_users=extra_users, room_id=row.data.room_id, event_type=row.data.type, state_key=row.data.state_key, membership=row.data.membership, ) await self._presence_handler.process_replication_rows( stream_name, instance_name, token, rows ) # Notify any waiting deferreds. The list is ordered by position so we # just iterate through the list until we reach a position that is # greater than the received row position. waiting_list = self._streams_to_waiters.get(stream_name, []) # Index of first item with a position after the current token, i.e we # have called all deferreds before this index. If not overwritten by # loop below means either a) no items in list so no-op or b) all items # in list were called and so the list should be cleared. Setting it to # `len(list)` works for both cases. index_of_first_deferred_not_called = len(waiting_list) for idx, (position, deferred) in enumerate(waiting_list): if position <= token: try: with PreserveLoggingContext(): deferred.callback(None) except Exception: # The deferred has been cancelled or timed out. pass else: # The list is sorted by position so we don't need to continue # checking any further entries in the list. index_of_first_deferred_not_called = idx break # Drop all entries in the waiting list that were called in the above # loop. (This maintains the order so no need to resort) waiting_list[:] = waiting_list[index_of_first_deferred_not_called:]
def get_messages(self, user_id=None, room_id=None, pagin_config=None, as_client_event=True, is_guest=False): """Get messages in a room. Args: user_id (str): The user requesting messages. room_id (str): The room they want messages from. pagin_config (synapse.api.streams.PaginationConfig): The pagination config rules to apply, if any. as_client_event (bool): True to get events in client-server format. is_guest (bool): Whether the requesting user is a guest (as opposed to a fully registered user). Returns: dict: Pagination API results """ data_source = self.hs.get_event_sources().sources["room"] if pagin_config.from_token: room_token = pagin_config.from_token.room_key else: pagin_config.from_token = ( yield self.hs.get_event_sources().get_current_token( direction='b' ) ) room_token = pagin_config.from_token.room_key room_token = RoomStreamToken.parse(room_token) if room_token.topological is None: raise SynapseError(400, "Invalid token") pagin_config.from_token = pagin_config.from_token.copy_and_replace( "room_key", str(room_token) ) source_config = pagin_config.get_source_config("room") if not is_guest: member_event = yield self.auth.check_user_was_in_room(room_id, user_id) if member_event.membership == Membership.LEAVE: # If they have left the room then clamp the token to be before # they left the room. # If they're a guest, we'll just 403 them if they're asking for # events they can't see. leave_token = yield self.store.get_topological_token_for_event( member_event.event_id ) leave_token = RoomStreamToken.parse(leave_token) if leave_token.topological < room_token.topological: source_config.from_key = str(leave_token) if source_config.direction == "f": if source_config.to_key is None: source_config.to_key = str(leave_token) else: to_token = RoomStreamToken.parse(source_config.to_key) if leave_token.topological < to_token.topological: source_config.to_key = str(leave_token) yield self.hs.get_handlers().federation_handler.maybe_backfill( room_id, room_token.topological ) user = UserID.from_string(user_id) events, next_key = yield data_source.get_pagination_rows( user, source_config, room_id ) next_token = pagin_config.from_token.copy_and_replace( "room_key", next_key ) if not events: defer.returnValue({ "chunk": [], "start": pagin_config.from_token.to_string(), "end": next_token.to_string(), }) events = yield self._filter_events_for_client(user_id, events, is_guest=is_guest) time_now = self.clock.time_msec() chunk = { "chunk": [ serialize_event(e, time_now, as_client_event) for e in events ], "start": pagin_config.from_token.to_string(), "end": next_token.to_string(), } defer.returnValue(chunk)
async def on_POST(self, request: SynapseRequest, room_identifier: str) -> Tuple[int, JsonDict]: # This will always be set by the time Twisted calls us. assert request.args is not None requester = await self.auth.get_user_by_req(request) await assert_user_is_admin(self.auth, requester.user) content = parse_json_object_from_request(request) assert_params_in_dict(content, ["user_id"]) target_user = UserID.from_string(content["user_id"]) if not self.hs.is_mine(target_user): raise SynapseError( 400, "This endpoint can only be used with local users") if not await self.admin_handler.get_user(target_user): raise NotFoundError("User not found") # Get the room ID from the identifier. try: remote_room_hosts: Optional[List[str]] = [ x.decode("ascii") for x in request.args[b"server_name"] ] except Exception: remote_room_hosts = None room_id, remote_room_hosts = await self.resolve_room_id( room_identifier, remote_room_hosts) fake_requester = create_requester( target_user, authenticated_entity=requester.authenticated_entity) # send invite if room has "JoinRules.INVITE" room_state = await self.state_handler.get_current_state(room_id) join_rules_event = room_state.get((EventTypes.JoinRules, "")) if join_rules_event: if not (join_rules_event.content.get("join_rule") == JoinRules.PUBLIC): # update_membership with an action of "invite" can raise a # ShadowBanError. This is not handled since it is assumed that # an admin isn't going to call this API with a shadow-banned user. await self.room_member_handler.update_membership( requester=requester, target=fake_requester.user, room_id=room_id, action="invite", remote_room_hosts=remote_room_hosts, ratelimit=False, ) await self.room_member_handler.update_membership( requester=fake_requester, target=fake_requester.user, room_id=room_id, action="join", remote_room_hosts=remote_room_hosts, ratelimit=False, ) return 200, {"room_id": room_id}
def test_parse(self): user = UserID.from_string("@1234abcd:my.domain") self.assertEquals("1234abcd", user.localpart) self.assertEquals("my.domain", user.domain) self.assertEquals(True, mock_homeserver.is_mine(user))
def _snapshot_all_rooms(self, user_id=None, pagin_config=None, as_client_event=True, include_archived=False): memberships = [Membership.INVITE, Membership.JOIN] if include_archived: memberships.append(Membership.LEAVE) room_list = yield self.store.get_rooms_for_user_where_membership_is( user_id=user_id, membership_list=memberships) user = UserID.from_string(user_id) rooms_ret = [] now_token = yield self.hs.get_event_sources().get_current_token() presence_stream = self.hs.get_event_sources().sources["presence"] pagination_config = PaginationConfig(from_token=now_token) presence, _ = yield presence_stream.get_pagination_rows( user, pagination_config.get_source_config("presence"), None) receipt_stream = self.hs.get_event_sources().sources["receipt"] receipt, _ = yield receipt_stream.get_pagination_rows( user, pagination_config.get_source_config("receipt"), None) tags_by_room = yield self.store.get_tags_for_user(user_id) account_data, account_data_by_room = ( yield self.store.get_account_data_for_user(user_id)) public_room_ids = yield self.store.get_public_room_ids() limit = pagin_config.limit if limit is None: limit = 10 @defer.inlineCallbacks def handle_room(event): d = { "room_id": event.room_id, "membership": event.membership, "visibility": ("public" if event.room_id in public_room_ids else "private"), } if event.membership == Membership.INVITE: time_now = self.clock.time_msec() d["inviter"] = event.sender invite_event = yield self.store.get_event(event.event_id) d["invite"] = serialize_event(invite_event, time_now, as_client_event) rooms_ret.append(d) if event.membership not in (Membership.JOIN, Membership.LEAVE): return try: if event.membership == Membership.JOIN: room_end_token = now_token.room_key deferred_room_state = self.state_handler.get_current_state( event.room_id) elif event.membership == Membership.LEAVE: room_end_token = "s%d" % (event.stream_ordering, ) deferred_room_state = self.store.get_state_for_events( [event.event_id], None) deferred_room_state.addCallback( lambda states: states[event.event_id]) (messages, token), current_state = yield preserve_context_over_deferred( defer.gatherResults([ preserve_fn(self.store.get_recent_events_for_room)( event.room_id, limit=limit, end_token=room_end_token, ), deferred_room_state, ])).addErrback(unwrapFirstError) messages = yield filter_events_for_client( self.store, user_id, messages) start_token = now_token.copy_and_replace("room_key", token[0]) end_token = now_token.copy_and_replace("room_key", token[1]) time_now = self.clock.time_msec() d["messages"] = { "chunk": [ serialize_event(m, time_now, as_client_event) for m in messages ], "start": start_token.to_string(), "end": end_token.to_string(), } d["state"] = [ serialize_event(c, time_now, as_client_event) for c in current_state.values() ] account_data_events = [] tags = tags_by_room.get(event.room_id) if tags: account_data_events.append({ "type": "m.tag", "content": { "tags": tags }, }) account_data = account_data_by_room.get(event.room_id, {}) for account_data_type, content in account_data.items(): account_data_events.append({ "type": account_data_type, "content": content, }) d["account_data"] = account_data_events except: logger.exception("Failed to get snapshot") yield concurrently_execute(handle_room, room_list, 10) account_data_events = [] for account_data_type, content in account_data.items(): account_data_events.append({ "type": account_data_type, "content": content, }) ret = { "rooms": rooms_ret, "presence": presence, "account_data": account_data_events, "receipts": receipt, "end": now_token.to_string(), } defer.returnValue(ret)
def get_user_by_req(request, allow_guest=False, rights="access"): return synapse.types.create_requester( UserID.from_string(self.USER_ID), 1, False, None)
def create_and_send_event(self, event_dict, ratelimit=True, token_id=None, txn_id=None, is_guest=False): """ Given a dict from a client, create and handle a new event. Creates an FrozenEvent object, filling out auth_events, prev_events, etc. Adds display names to Join membership events. Persists and notifies local clients and federation. Args: event_dict (dict): An entire event """ builder = self.event_builder_factory.new(event_dict) self.validator.validate_new(builder) if ratelimit: self.ratelimit(builder.user_id) # TODO(paul): Why does 'event' not have a 'user' object? user = UserID.from_string(builder.user_id) assert self.hs.is_mine(user), "User must be our own: %s" % (user,) if builder.type == EventTypes.Member: membership = builder.content.get("membership", None) if membership == Membership.JOIN: joinee = UserID.from_string(builder.state_key) # If event doesn't include a display name, add one. yield collect_presencelike_data( self.distributor, joinee, builder.content ) if token_id is not None: builder.internal_metadata.token_id = token_id if txn_id is not None: builder.internal_metadata.txn_id = txn_id event, context = yield self._create_new_client_event( builder=builder, ) if event.is_state(): prev_state = context.current_state.get((event.type, event.state_key)) if prev_state and event.user_id == prev_state.user_id: prev_content = encode_canonical_json(prev_state.content) next_content = encode_canonical_json(event.content) if prev_content == next_content: # Duplicate suppression for state updates with same sender # and content. defer.returnValue(prev_state) if event.type == EventTypes.Member: member_handler = self.hs.get_handlers().room_member_handler yield member_handler.change_membership(event, context, is_guest=is_guest) else: yield self.handle_new_client_event( event=event, context=context, ) if event.type == EventTypes.Message: presence = self.hs.get_handlers().presence_handler with PreserveLoggingContext(): presence.bump_presence_active_time(user) defer.returnValue(event)
async def get_user_by_access_token(token=None, allow_guest=False): return { "user": UserID.from_string(self.helper.auth_user_id), "token_id": token_id, "is_guest": False, }
async def on_POST(self, request: SynapseRequest, room_identifier: str) -> Tuple[int, JsonDict]: requester = await self.auth.get_user_by_req(request) await assert_user_is_admin(self.auth, requester.user) content = parse_json_object_from_request(request, allow_empty_body=True) room_id, _ = await self.resolve_room_id(room_identifier) # Which user to grant room admin rights to. user_to_add = content.get("user_id", requester.user.to_string()) # Figure out which local users currently have power in the room, if any. room_state = await self.state_handler.get_current_state(room_id) if not room_state: raise SynapseError(400, "Server not in room") create_event = room_state[(EventTypes.Create, "")] power_levels = room_state.get((EventTypes.PowerLevels, "")) if power_levels is not None: # We pick the local user with the highest power. user_power = power_levels.content.get("users", {}) admin_users = [ user_id for user_id in user_power if self.is_mine_id(user_id) ] admin_users.sort(key=lambda user: user_power[user]) if not admin_users: raise SynapseError(400, "No local admin user in room") admin_user_id = None for admin_user in reversed(admin_users): ( current_membership_type, _, ) = await self.store.get_local_current_membership_for_user_in_room( admin_user, room_id) if current_membership_type == "join": admin_user_id = admin_user break if not admin_user_id: raise SynapseError( 400, "No local admin user in room", ) pl_content = power_levels.content else: # If there is no power level events then the creator has rights. pl_content = {} admin_user_id = create_event.sender if not self.is_mine_id(admin_user_id): raise SynapseError( 400, "No local admin user in room", ) # Grant the user power equal to the room admin by attempting to send an # updated power level event. new_pl_content = dict(pl_content) new_pl_content["users"] = dict(pl_content.get("users", {})) new_pl_content["users"][user_to_add] = new_pl_content["users"][ admin_user_id] fake_requester = create_requester( admin_user_id, authenticated_entity=requester.authenticated_entity, ) try: await self.event_creation_handler.create_and_send_nonmember_event( fake_requester, event_dict={ "content": new_pl_content, "sender": admin_user_id, "type": EventTypes.PowerLevels, "state_key": "", "room_id": room_id, }, ) except AuthError: # The admin user we found turned out not to have enough power. raise SynapseError( 400, "No local admin user in room with power to update power levels." ) # Now we check if the user we're granting admin rights to is already in # the room. If not and it's not a public room we invite them. member_event = room_state.get((EventTypes.Member, user_to_add)) is_joined = False if member_event: is_joined = member_event.content["membership"] in ( Membership.JOIN, Membership.INVITE, ) if is_joined: return 200, {} join_rules = room_state.get((EventTypes.JoinRules, "")) is_public = False if join_rules: is_public = join_rules.content.get("join_rule") == JoinRules.PUBLIC if is_public: return 200, {} await self.room_member_handler.update_membership( fake_requester, target=UserID.from_string(user_to_add), room_id=room_id, action=Membership.INVITE, ) return 200, {}
def create_group(self, group_id, requester_user_id, content): group = yield self.check_group_is_ours(group_id, requester_user_id) logger.info("Attempting to create group with ID: %r", group_id) # parsing the id into a GroupID validates it. group_id_obj = GroupID.from_string(group_id) if group: raise SynapseError(400, "Group already exists") is_admin = yield self.auth.is_server_admin( UserID.from_string(requester_user_id)) if not is_admin: if not self.hs.config.enable_group_creation: raise SynapseError( 403, "Only a server admin can create groups on this server") localpart = group_id_obj.localpart if not localpart.startswith(self.hs.config.group_creation_prefix): raise SynapseError( 400, "Can only create groups with prefix %r on this server" % (self.hs.config.group_creation_prefix, ), ) profile = content.get("profile", {}) name = profile.get("name") avatar_url = profile.get("avatar_url") short_description = profile.get("short_description") long_description = profile.get("long_description") user_profile = content.get("user_profile", {}) yield self.store.create_group( group_id, requester_user_id, name=name, avatar_url=avatar_url, short_description=short_description, long_description=long_description, ) if not self.hs.is_mine_id(requester_user_id): remote_attestation = content["attestation"] yield self.attestations.verify_attestation( remote_attestation, user_id=requester_user_id, group_id=group_id) local_attestation = self.attestations.create_attestation( group_id, requester_user_id) else: local_attestation = None remote_attestation = None yield self.store.add_user_to_group( group_id, requester_user_id, is_admin=True, is_public=True, # TODO local_attestation=local_attestation, remote_attestation=remote_attestation, ) if not self.hs.is_mine_id(requester_user_id): yield self.store.add_remote_profile_cache( requester_user_id, displayname=user_profile.get("displayname"), avatar_url=user_profile.get("avatar_url"), ) return {"group_id": group_id}
def setUp(self): hs = yield setup_test_homeserver(self.addCleanup) self.store = ProfileStore(None, hs) self.u_frank = UserID.from_string("@frank:test")
def delete_group(self, group_id, requester_user_id): """Deletes a group, kicking out all current members. Only group admins or server admins can call this request Args: group_id (str) request_user_id (str) Returns: Deferred """ yield self.check_group_is_ours(group_id, requester_user_id, and_exists=True) # Only server admins or group admins can delete groups. is_admin = yield self.store.is_user_admin_in_group( group_id, requester_user_id) if not is_admin: is_admin = yield self.auth.is_server_admin( UserID.from_string(requester_user_id)) if not is_admin: raise SynapseError(403, "User is not an admin") # Before deleting the group lets kick everyone out of it users = yield self.store.get_users_in_group(group_id, include_private=True) @defer.inlineCallbacks def _kick_user_from_group(user_id): if self.hs.is_mine_id(user_id): groups_local = self.hs.get_groups_local_handler() yield groups_local.user_removed_from_group( group_id, user_id, {}) else: yield self.transport_client.remove_user_from_group_notification( get_domain_from_id(user_id), group_id, user_id, {}) yield self.store.maybe_delete_remote_profile_cache(user_id) # We kick users out in the order of: # 1. Non-admins # 2. Other admins # 3. The requester # # This is so that if the deletion fails for some reason other admins or # the requester still has auth to retry. non_admins = [] admins = [] for u in users: if u["user_id"] == requester_user_id: continue if u["is_admin"]: admins.append(u["user_id"]) else: non_admins.append(u["user_id"]) yield concurrently_execute(_kick_user_from_group, non_admins, 10) yield concurrently_execute(_kick_user_from_group, admins, 10) yield _kick_user_from_group(requester_user_id) yield self.store.delete_group(group_id)
def create_room(self, user_id, room_id, config): """ Creates a new room. Args: user_id (str): The ID of the user creating the new room. room_id (str): The proposed ID for the new room. Can be None, in which case one will be created for you. config (dict) : A dict of configuration options. Returns: The new room ID. Raises: SynapseError if the room ID was taken, couldn't be stored, or something went horribly wrong. """ self.ratelimit(user_id) if "room_alias_name" in config: for wchar in string.whitespace: if wchar in config["room_alias_name"]: raise SynapseError(400, "Invalid characters in room alias") room_alias = RoomAlias.create( config["room_alias_name"], self.hs.hostname, ) mapping = yield self.store.get_association_from_room_alias( room_alias) if mapping: raise SynapseError(400, "Room alias already taken") else: room_alias = None invite_list = config.get("invite", []) for i in invite_list: try: UserID.from_string(i) except: raise SynapseError(400, "Invalid user_id: %s" % (i, )) is_public = config.get("visibility", None) == "public" if room_id: # Ensure room_id is the correct type room_id_obj = RoomID.from_string(room_id) if not self.hs.is_mine(room_id_obj): raise SynapseError(400, "Room id must be local") yield self.store.store_room(room_id=room_id, room_creator_user_id=user_id, is_public=is_public) else: # autogen room IDs and try to create it. We may clash, so just # try a few times till one goes through, giving up eventually. attempts = 0 room_id = None while attempts < 5: try: random_string = stringutils.random_string(18) gen_room_id = RoomID.create( random_string, self.hs.hostname, ) yield self.store.store_room( room_id=gen_room_id.to_string(), room_creator_user_id=user_id, is_public=is_public) room_id = gen_room_id.to_string() break except StoreError: attempts += 1 if not room_id: raise StoreError(500, "Couldn't generate a room ID.") if room_alias: directory_handler = self.hs.get_handlers().directory_handler yield directory_handler.create_association( user_id=user_id, room_id=room_id, room_alias=room_alias, servers=[self.hs.hostname], ) preset_config = config.get( "preset", RoomCreationPreset.PUBLIC_CHAT if is_public else RoomCreationPreset.PRIVATE_CHAT) raw_initial_state = config.get("initial_state", []) initial_state = OrderedDict() for val in raw_initial_state: initial_state[(val["type"], val.get("state_key", ""))] = val["content"] user = UserID.from_string(user_id) creation_events = self._create_events_for_new_room( user, room_id, preset_config=preset_config, invite_list=invite_list, initial_state=initial_state, ) msg_handler = self.hs.get_handlers().message_handler for event in creation_events: yield msg_handler.create_and_send_event(event, ratelimit=False) if "name" in config: name = config["name"] yield msg_handler.create_and_send_event( { "type": EventTypes.Name, "room_id": room_id, "sender": user_id, "state_key": "", "content": { "name": name }, }, ratelimit=False) if "topic" in config: topic = config["topic"] yield msg_handler.create_and_send_event( { "type": EventTypes.Topic, "room_id": room_id, "sender": user_id, "state_key": "", "content": { "topic": topic }, }, ratelimit=False) for invitee in invite_list: yield msg_handler.create_and_send_event( { "type": EventTypes.Member, "state_key": invitee, "room_id": room_id, "sender": user_id, "content": { "membership": Membership.INVITE }, }, ratelimit=False) result = {"room_id": room_id} if room_alias: result["room_alias"] = room_alias.to_string() yield directory_handler.send_room_alias_update_event( user_id, room_id) defer.returnValue(result)
def setUp(self): hs = yield setup_test_homeserver(self.addCleanup) self.store = hs.get_datastore() self.u_frank = UserID.from_string("@frank:test")
def get_room_members(self, room_id): users = yield self.store.get_users_in_room(room_id) defer.returnValue([UserID.from_string(u) for u in users])
def __init__(self, hs): """ Args: hs (synapse.server.HomeServer): """ self.hs = hs self.is_mine = hs.is_mine self.is_mine_id = hs.is_mine_id self.clock = hs.get_clock() self.store = hs.get_datastore() self.wheel_timer = WheelTimer() self.notifier = hs.get_notifier() self.federation = hs.get_federation_sender() self.state = hs.get_state_handler() federation_registry = hs.get_federation_registry() federation_registry.register_edu_handler("m.presence", self.incoming_presence) federation_registry.register_edu_handler( "m.presence_invite", lambda origin, content: self.invite_presence( observed_user=UserID.from_string(content["observed_user"]), observer_user=UserID.from_string(content["observer_user"]), )) federation_registry.register_edu_handler( "m.presence_accept", lambda origin, content: self.accept_presence( observed_user=UserID.from_string(content["observed_user"]), observer_user=UserID.from_string(content["observer_user"]), )) federation_registry.register_edu_handler( "m.presence_deny", lambda origin, content: self.deny_presence( observed_user=UserID.from_string(content["observed_user"]), observer_user=UserID.from_string(content["observer_user"]), )) distributor = hs.get_distributor() distributor.observe("user_joined_room", self.user_joined_room) active_presence = self.store.take_presence_startup_info() # A dictionary of the current state of users. This is prefilled with # non-offline presence from the DB. We should fetch from the DB if # we can't find a users presence in here. self.user_to_current_state = { state.user_id: state for state in active_presence } LaterGauge("synapse_handlers_presence_user_to_current_state_size", "", [], lambda: len(self.user_to_current_state)) now = self.clock.time_msec() for state in active_presence: self.wheel_timer.insert( now=now, obj=state.user_id, then=state.last_active_ts + IDLE_TIMER, ) self.wheel_timer.insert( now=now, obj=state.user_id, then=state.last_user_sync_ts + SYNC_ONLINE_TIMEOUT, ) if self.is_mine_id(state.user_id): self.wheel_timer.insert( now=now, obj=state.user_id, then=state.last_federation_update_ts + FEDERATION_PING_INTERVAL, ) else: self.wheel_timer.insert( now=now, obj=state.user_id, then=state.last_federation_update_ts + FEDERATION_TIMEOUT, ) # Set of users who have presence in the `user_to_current_state` that # have not yet been persisted self.unpersisted_users_changes = set() hs.get_reactor().addSystemEventTrigger("before", "shutdown", self._on_shutdown) self.serial_to_user = {} self._next_serial = 1 # Keeps track of the number of *ongoing* syncs on this process. While # this is non zero a user will never go offline. self.user_to_num_current_syncs = {} # Keeps track of the number of *ongoing* syncs on other processes. # While any sync is ongoing on another process the user will never # go offline. # Each process has a unique identifier and an update frequency. If # no update is received from that process within the update period then # we assume that all the sync requests on that process have stopped. # Stored as a dict from process_id to set of user_id, and a dict of # process_id to millisecond timestamp last updated. self.external_process_to_current_syncs = {} self.external_process_last_updated_ms = {} self.external_sync_linearizer = Linearizer( name="external_sync_linearizer") # Start a LoopingCall in 30s that fires every 5s. # The initial delay is to allow disconnected clients a chance to # reconnect before we treat them as offline. self.clock.call_later( 30, self.clock.looping_call, self._handle_timeouts, 5000, ) self.clock.call_later( 60, self.clock.looping_call, self._persist_unpersisted_changes, 60 * 1000, ) LaterGauge("synapse_handlers_presence_wheel_timer_size", "", [], lambda: len(self.wheel_timer))
def on_send_join_request(self, origin, pdu): """ We have received a join event for a room. Fully process it and respond with the current state and auth chains. """ event = pdu logger.debug( "on_send_join_request: Got event: %s, signatures: %s", event.event_id, event.signatures, ) event.internal_metadata.outlier = False context, event_stream_id, max_stream_id = yield self._handle_new_event( origin, event) logger.debug( "on_send_join_request: After _handle_new_event: %s, sigs: %s", event.event_id, event.signatures, ) extra_users = [] if event.type == EventTypes.Member: target_user_id = event.state_key target_user = UserID.from_string(target_user_id) extra_users.append(target_user) with PreserveLoggingContext(): d = self.notifier.on_new_room_event(event, event_stream_id, max_stream_id, extra_users=extra_users) def log_failure(f): logger.warn("Failed to notify about %s: %s", event.event_id, f.value) d.addErrback(log_failure) if event.type == EventTypes.Member: if event.content["membership"] == Membership.JOIN: user = UserID.from_string(event.state_key) yield self.distributor.fire("user_joined_room", user=user, room_id=event.room_id) new_pdu = event destinations = set() for k, s in context.current_state.items(): try: if k[0] == EventTypes.Member: if s.content["membership"] == Membership.JOIN: destinations.add( UserID.from_string(s.state_key).domain) except: logger.warn("Failed to get destination from event %s", s.event_id) destinations.discard(origin) logger.debug( "on_send_join_request: Sending event: %s, signatures: %s", event.event_id, event.signatures, ) self.replication_layer.send_pdu(new_pdu, destinations) state_ids = [e.event_id for e in context.current_state.values()] auth_chain = yield self.store.get_auth_chain( set([event.event_id] + state_ids)) defer.returnValue({ "state": context.current_state.values(), "auth_chain": auth_chain, })
def _do_join(self, event, context, room_hosts=None, do_auth=True): joinee = UserID.from_string(event.state_key) # room_id = RoomID.from_string(event.room_id, self.hs) room_id = event.room_id # XXX: We don't do an auth check if we are doing an invite # join dance for now, since we're kinda implicitly checking # that we are allowed to join when we decide whether or not we # need to do the invite/join dance. is_host_in_room = yield self.auth.check_host_in_room( event.room_id, self.hs.hostname) if not is_host_in_room: # is *anyone* in the room? room_member_keys = [ v for (k, v) in context.current_state.keys() if (k == "m.room.member") ] if len(room_member_keys) == 0: # has the room been created so we can join it? create_event = context.current_state.get(("m.room.create", "")) if create_event: is_host_in_room = True if is_host_in_room: should_do_dance = False elif room_hosts: # TODO: Shouldn't this be remote_room_host? should_do_dance = True else: # TODO(markjh): get prev_state from snapshot prev_state = yield self.store.get_room_member( joinee.to_string(), room_id) if prev_state and prev_state.membership == Membership.INVITE: inviter = UserID.from_string(prev_state.user_id) should_do_dance = not self.hs.is_mine(inviter) room_hosts = [inviter.domain] else: # return the same error as join_room_alias does raise SynapseError(404, "No known servers") if should_do_dance: handler = self.hs.get_handlers().federation_handler yield handler.do_invite_join( room_hosts, room_id, event.user_id, event.content, # FIXME To get a non-frozen dict context) else: logger.debug("Doing normal join") yield self._do_local_membership_update( event, membership=event.content["membership"], context=context, do_auth=do_auth, ) user = UserID.from_string(event.user_id) yield self.distributor.fire("user_joined_room", user=user, room_id=room_id)
async def send_device_message( self, requester: Requester, message_type: str, messages: Dict[str, Dict[str, JsonDict]], ) -> None: sender_user_id = requester.user.to_string() set_tag("number_of_messages", len(messages)) set_tag("sender", sender_user_id) local_messages = {} remote_messages = {} # type: Dict[str, Dict[str, Dict[str, JsonDict]]] for user_id, by_device in messages.items(): # Ratelimit local cross-user key requests by the sending device. if (message_type == EduTypes.RoomKeyRequest and user_id != sender_user_id and self._ratelimiter.can_do_action( (sender_user_id, requester.device_id))): continue # we use UserID.from_string to catch invalid user ids if self.is_mine(UserID.from_string(user_id)): messages_by_device = { device_id: { "content": message_content, "type": message_type, "sender": sender_user_id, } for device_id, message_content in by_device.items() } if messages_by_device: local_messages[user_id] = messages_by_device else: destination = get_domain_from_id(user_id) remote_messages.setdefault(destination, {})[user_id] = by_device message_id = random_string(16) context = get_active_span_text_map() remote_edu_contents = {} for destination, messages in remote_messages.items(): with start_active_span("to_device_for_user"): set_tag("destination", destination) remote_edu_contents[destination] = { "messages": messages, "sender": sender_user_id, "type": message_type, "message_id": message_id, "org.matrix.opentracing_context": json_encoder.encode(context), } log_kv({"local_messages": local_messages}) stream_id = await self.store.add_messages_to_device_inbox( local_messages, remote_edu_contents) self.notifier.on_new_event("to_device_key", stream_id, users=local_messages.keys()) log_kv({"remote_messages": remote_messages}) if self.federation_sender: for destination in remote_messages.keys(): # Enqueue a new federation transaction to send the new # device messages to each remote destination. self.federation_sender.send_device_messages(destination)
def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None: self.store = hs.get_datastore() self.u_frank = UserID.from_string("@frank:test")
def on_receive_pdu(self, origin, pdu, backfilled, state=None, auth_chain=None): """ Called by the ReplicationLayer when we have a new pdu. We need to do auth checks and put it through the StateHandler. """ event = pdu logger.debug("Got event: %s", event.event_id) # If we are currently in the process of joining this room, then we # queue up events for later processing. if event.room_id in self.room_queues: self.room_queues[event.room_id].append((pdu, origin)) return logger.debug("Processing event: %s", event.event_id) logger.debug("Event: %s", event) # FIXME (erikj): Awful hack to make the case where we are not currently # in the room work current_state = None is_in_room = yield self.auth.check_host_in_room( event.room_id, self.server_name) if not is_in_room and not event.internal_metadata.is_outlier(): logger.debug("Got event for room we're not in.") current_state = state event_ids = set() if state: event_ids |= {e.event_id for e in state} if auth_chain: event_ids |= {e.event_id for e in auth_chain} seen_ids = set((yield self.store.have_events(event_ids)).keys()) if state and auth_chain is not None: # If we have any state or auth_chain given to us by the replication # layer, then we should handle them (if we haven't before.) event_infos = [] for e in itertools.chain(auth_chain, state): if e.event_id in seen_ids: continue e.internal_metadata.outlier = True auth_ids = [e_id for e_id, _ in e.auth_events] auth = {(e.type, e.state_key): e for e in auth_chain if e.event_id in auth_ids} event_infos.append({ "event": e, "auth_events": auth, }) seen_ids.add(e.event_id) yield self._handle_new_events(origin, event_infos, outliers=True) try: _, event_stream_id, max_stream_id = yield self._handle_new_event( origin, event, state=state, backfilled=backfilled, current_state=current_state, ) except AuthError as e: raise FederationError( "ERROR", e.code, e.msg, affected=event.event_id, ) # if we're receiving valid events from an origin, # it's probably a good idea to mark it as not in retry-state # for sending (although this is a bit of a leap) retry_timings = yield self.store.get_destination_retry_timings(origin) if retry_timings and retry_timings["retry_last_ts"]: self.store.set_destination_retry_timings(origin, 0, 0) room = yield self.store.get_room(event.room_id) if not room: try: yield self.store.store_room( room_id=event.room_id, room_creator_user_id="", is_public=False, ) except StoreError: logger.exception("Failed to store room.") if not backfilled: extra_users = [] if event.type == EventTypes.Member: target_user_id = event.state_key target_user = UserID.from_string(target_user_id) extra_users.append(target_user) with PreserveLoggingContext(): d = self.notifier.on_new_room_event(event, event_stream_id, max_stream_id, extra_users=extra_users) def log_failure(f): logger.warn("Failed to notify about %s: %s", event.event_id, f.value) d.addErrback(log_failure) if event.type == EventTypes.Member: if event.membership == Membership.JOIN: user = UserID.from_string(event.state_key) yield self.distributor.fire("user_joined_room", user=user, room_id=event.room_id)
def matrix_user_id_validator(user_id_str: str) -> UserID: return UserID.from_string(user_id_str)
def test_pase_empty(self): with self.assertRaises(SynapseError): UserID.from_string("")
def _register_user( self, txn, user_id, password_hash, was_guest, make_guest, appservice_id, create_profile_with_displayname, admin, user_type, ): user_id_obj = UserID.from_string(user_id) now = int(self.clock.time()) try: if was_guest: # Ensure that the guest user actually exists # ``allow_none=False`` makes this raise an exception # if the row isn't in the database. self._simple_select_one_txn( txn, "users", keyvalues={ "name": user_id, "is_guest": 1 }, retcols=("name", ), allow_none=False, ) self._simple_update_one_txn( txn, "users", keyvalues={ "name": user_id, "is_guest": 1 }, updatevalues={ "password_hash": password_hash, "upgrade_ts": now, "is_guest": 1 if make_guest else 0, "appservice_id": appservice_id, "admin": 1 if admin else 0, "user_type": user_type, }, ) else: self._simple_insert_txn( txn, "users", values={ "name": user_id, "password_hash": password_hash, "creation_ts": now, "is_guest": 1 if make_guest else 0, "appservice_id": appservice_id, "admin": 1 if admin else 0, "user_type": user_type, }, ) except self.database_engine.module.IntegrityError: raise StoreError(400, "User ID already taken.", errcode=Codes.USER_IN_USE) if self._account_validity.enabled: self.set_expiration_date_for_user_txn(txn, user_id) if create_profile_with_displayname: # set a default displayname serverside to avoid ugly race # between auto-joins and clients trying to set displaynames # # *obviously* the 'profiles' table uses localpart for user_id # while everything else uses the full mxid. txn.execute( "INSERT INTO profiles(user_id, displayname) VALUES (?,?)", (user_id_obj.localpart, create_profile_with_displayname), ) self._invalidate_cache_and_stream(txn, self.get_user_by_id, (user_id, )) txn.call_after(self.is_guest.invalidate, (user_id, ))
def send_membership_event( self, requester, event, context, remote_room_hosts=None, ratelimit=True, ): """ Change the membership status of a user in a room. Args: requester (Requester): The local user who requested the membership event. If None, certain checks, like whether this homeserver can act as the sender, will be skipped. event (SynapseEvent): The membership event. context: The context of the event. is_guest (bool): Whether the sender is a guest. room_hosts ([str]): Homeservers which are likely to already be in the room, and could be danced with in order to join this homeserver for the first time. ratelimit (bool): Whether to rate limit this request. Raises: SynapseError if there was a problem changing the membership. """ remote_room_hosts = remote_room_hosts or [] target_user = UserID.from_string(event.state_key) room_id = event.room_id if requester is not None: sender = UserID.from_string(event.sender) assert sender == requester.user, ( "Sender (%s) must be same as requester (%s)" % (sender, requester.user)) assert self.hs.is_mine( sender), "Sender must be our own: %s" % (sender, ) else: requester = synapse.types.create_requester(target_user) message_handler = self.hs.get_handlers().message_handler prev_event = yield message_handler.deduplicate_state_event( event, context) if prev_event is not None: return if event.membership == Membership.JOIN: if requester.is_guest: guest_can_join = yield self._can_guest_join( context.prev_state_ids) if not guest_can_join: # This should be an auth check, but guests are a local concept, # so don't really fit into the general auth process. raise AuthError(403, "Guest access not allowed") if event.membership not in (Membership.LEAVE, Membership.BAN): is_blocked = yield self.store.is_room_blocked(room_id) if is_blocked: raise SynapseError( 403, "This room has been blocked on this server") yield message_handler.handle_new_client_event( requester, event, context, extra_users=[target_user], ratelimit=ratelimit, ) prev_member_event_id = context.prev_state_ids.get( (EventTypes.Member, event.state_key), None) if event.membership == Membership.JOIN: # Only fire user_joined_room if the user has acutally joined the # room. Don't bother if the user is just changing their profile # info. newly_joined = True if prev_member_event_id: prev_member_event = yield self.store.get_event( prev_member_event_id) newly_joined = prev_member_event.membership != Membership.JOIN if newly_joined: yield user_joined_room(self.distributor, target_user, room_id) elif event.membership == Membership.LEAVE: if prev_member_event_id: prev_member_event = yield self.store.get_event( prev_member_event_id) if prev_member_event.membership == Membership.JOIN: user_left_room(self.distributor, target_user, room_id)
def handle_new_client_event(self, requester, event, context, ratelimit=True, extra_users=[]): # We now need to go and hit out to wherever we need to hit out to. if ratelimit: self.ratelimit(requester) try: yield self.auth.check_from_context(event, context) except AuthError as err: logger.warn("Denying new event %r because %s", event, err) raise err yield self.maybe_kick_guest_users(event, context) if event.type == EventTypes.CanonicalAlias: # Check the alias is acually valid (at this time at least) room_alias_str = event.content.get("alias", None) if room_alias_str: room_alias = RoomAlias.from_string(room_alias_str) directory_handler = self.hs.get_handlers().directory_handler mapping = yield directory_handler.get_association(room_alias) if mapping["room_id"] != event.room_id: raise SynapseError( 400, "Room alias %s does not point to the room" % (room_alias_str, )) federation_handler = self.hs.get_handlers().federation_handler if event.type == EventTypes.Member: if event.content["membership"] == Membership.INVITE: def is_inviter_member_event(e): return (e.type == EventTypes.Member and e.sender == event.sender) state_to_include_ids = [ e_id for k, e_id in context.current_state_ids.items() if k[0] in self.hs.config.room_invite_state_types or k[0] == EventTypes.Member and k[1] == event.sender ] state_to_include = yield self.store.get_events( state_to_include_ids) event.unsigned["invite_room_state"] = [{ "type": e.type, "state_key": e.state_key, "content": e.content, "sender": e.sender, } for e in state_to_include.values()] invitee = UserID.from_string(event.state_key) if not self.hs.is_mine(invitee): # TODO: Can we add signature from remote server in a nicer # way? If we have been invited by a remote server, we need # to get them to sign the event. returned_invite = yield federation_handler.send_invite( invitee.domain, event, ) event.unsigned.pop("room_state", None) # TODO: Make sure the signatures actually are correct. event.signatures.update(returned_invite.signatures) if event.type == EventTypes.Redaction: auth_events_ids = yield self.auth.compute_auth_events( event, context.prev_state_ids, for_verification=True, ) auth_events = yield self.store.get_events(auth_events_ids) auth_events = {(e.type, e.state_key): e for e in auth_events.values()} if self.auth.check_redaction(event, auth_events=auth_events): original_event = yield self.store.get_event( event.redacts, check_redacted=False, get_prev_content=False, allow_rejected=False, allow_none=False) if event.user_id != original_event.user_id: raise AuthError( 403, "You don't have permission to redact events") if event.type == EventTypes.Create and context.prev_state_ids: raise AuthError( 403, "Changing the room create event is forbidden", ) action_generator = ActionGenerator(self.hs) yield action_generator.handle_push_actions_for_event(event, context) (event_stream_id, max_stream_id) = yield self.store.persist_event(event, context=context) # this intentionally does not yield: we don't care about the result # and don't need to wait for it. preserve_fn(self.hs.get_pusherpool().on_new_notifications)( event_stream_id, max_stream_id) users_in_room = yield self.store.get_joined_users_from_context( event, context) destinations = [ get_domain_from_id(user_id) for user_id in users_in_room if not self.hs.is_mine_id(user_id) ] @defer.inlineCallbacks def _notify(): yield run_on_reactor() yield self.notifier.on_new_room_event(event, event_stream_id, max_stream_id, extra_users=extra_users) preserve_fn(_notify)() # If invite, remove room_state from unsigned before sending. event.unsigned.pop("invite_room_state", None) preserve_fn(federation_handler.handle_new_event)( event, destinations=destinations, )
def get_user_by_access_token(token=None, allow_guest=False): return { "user": UserID.from_string(self.USER_ID), "token_id": 1, "is_guest": False, }
def _room_initial_sync_joined(self, user_id, room_id, pagin_config, membership, is_guest): current_state = yield self.state.get_current_state( room_id=room_id, ) # TODO(paul): I wish I was called with user objects not user_id # strings... auth_user = UserID.from_string(user_id) # TODO: These concurrently time_now = self.clock.time_msec() state = [ serialize_event(x, time_now) for x in current_state.values() ] now_token = yield self.hs.get_event_sources().get_current_token() limit = pagin_config.limit if pagin_config else None if limit is None: limit = 10 room_members = [ m for m in current_state.values() if m.type == EventTypes.Member and m.content["membership"] == Membership.JOIN ] presence_handler = self.hs.get_handlers().presence_handler @defer.inlineCallbacks def get_presence(): states = yield presence_handler.get_states( target_users=[UserID.from_string(m.user_id) for m in room_members], auth_user=auth_user, as_event=True, check_auth=False, ) defer.returnValue(states.values()) @defer.inlineCallbacks def get_receipts(): receipts_handler = self.hs.get_handlers().receipts_handler receipts = yield receipts_handler.get_receipts_for_room( room_id, now_token.receipt_key ) defer.returnValue(receipts) presence, receipts, (messages, token) = yield defer.gatherResults( [ get_presence(), get_receipts(), self.store.get_recent_events_for_room( room_id, limit=limit, end_token=now_token.room_key, ) ], consumeErrors=True, ).addErrback(unwrapFirstError) messages = yield self._filter_events_for_client( user_id, messages, is_guest=is_guest, require_all_visible_for_guests=False ) start_token = now_token.copy_and_replace("room_key", token[0]) end_token = now_token.copy_and_replace("room_key", token[1]) time_now = self.clock.time_msec() ret = { "room_id": room_id, "messages": { "chunk": [serialize_event(m, time_now) for m in messages], "start": start_token.to_string(), "end": end_token.to_string(), }, "state": state, "presence": presence, "receipts": receipts, } if not is_guest: ret["membership"] = membership defer.returnValue(ret)