def _part_user(self, user_id): """Causes the given user_id to leave all the rooms they're joined to Returns: None """ user = UserID.from_string(user_id) rooms_for_user = yield self.store.get_rooms_for_user(user_id) for room_id in rooms_for_user: logger.info("User parter parting %r from %r", user_id, room_id) try: yield self._room_member_handler.update_membership( create_requester(user), user, room_id, "leave", ratelimit=False, require_consent=False, ) except Exception: logger.exception( "Failed to part user %r from room %r: ignoring and continuing", user_id, room_id, )
def test_user_is_created_and_logged_in_if_doesnt_exist(self): local_part = "someone" display_name = "someone" user_id = "@someone:test" requester = create_requester("@as:test") result_user_id, result_token = yield self.handler.get_or_create_user( requester, local_part, display_name) self.assertEquals(result_user_id, user_id) self.assertEquals(result_token, 'secret')
def test_user_is_created_and_logged_in_if_doesnt_exist(self): frank = UserID.from_string("@frank:test") user_id = frank.to_string() requester = create_requester(user_id) result_user_id, result_token = yield self.handler.get_or_create_user( requester, frank.localpart, "Frankie" ) self.assertEquals(result_user_id, user_id) self.assertEquals(result_token, 'secret')
def _auto_join_rooms(self, user_id): """Automatically joins users to auto join rooms - creating the room in the first place if the user is the first to be created. Args: user_id(str): The user to join """ # auto-join the user to any rooms we're supposed to dump them into fake_requester = create_requester(user_id) # try to create the room if we're the first real user on the server. Note # that an auto-generated support user is not a real user and will never be # the user to create the room should_auto_create_rooms = False is_support = yield self.store.is_support_user(user_id) # There is an edge case where the first user is the support user, then # the room is never created, though this seems unlikely and # recoverable from given the support user being involved in the first # place. if self.hs.config.autocreate_auto_join_rooms and not is_support: count = yield self.store.count_all_users() should_auto_create_rooms = count == 1 for r in self.hs.config.auto_join_rooms: try: if should_auto_create_rooms: room_alias = RoomAlias.from_string(r) if self.hs.hostname != room_alias.domain: logger.warning( 'Cannot create room alias %s, ' 'it does not match server domain', r, ) else: # create room expects the localpart of the room alias room_alias_localpart = room_alias.localpart # getting the RoomCreationHandler during init gives a dependency # loop yield self.hs.get_room_creation_handler().create_room( fake_requester, config={ "preset": "public_chat", "room_alias_name": room_alias_localpart }, ratelimit=False, ) else: yield self._join_user_to_room(fake_requester, r) except ConsentNotGivenError as e: # Technically not necessary to pull out this error though # moving away from bare excepts is a good thing to do. logger.error("Failed to join new user to %r: %r", r, e) except Exception as e: logger.error("Failed to join new user to %r: %r", r, e)
def prepare(self, reactor, clock, hs): self.mock_distributor = Mock() self.mock_distributor.declare("registered_user") self.mock_captcha_client = Mock() self.macaroon_generator = Mock( generate_access_token=Mock(return_value='secret') ) self.hs.get_macaroon_generator = Mock(return_value=self.macaroon_generator) self.handler = self.hs.get_registration_handler() self.store = self.hs.get_datastore() self.lots_of_users = 100 self.small_number_of_users = 1 self.requester = create_requester("@requester:test")
def test_if_user_exists(self): store = self.hs.get_datastore() frank = UserID.from_string("@frank:test") yield store.register( user_id=frank.to_string(), token="jkv;g498752-43gj['eamb!-5", password_hash=None) local_part = "frank" display_name = "Frank" user_id = "@frank:test" requester = create_requester("@as:test") result_user_id, result_token = yield self.handler.get_or_create_user( requester, local_part, display_name) self.assertEquals(result_user_id, user_id) self.assertEquals(result_token, 'secret')
def test_if_user_exists(self): store = self.hs.get_datastore() frank = UserID.from_string("@frank:test") yield store.register( user_id=frank.to_string(), token="jkv;g498752-43gj['eamb!-5", password_hash=None, ) local_part = frank.localpart user_id = frank.to_string() requester = create_requester(user_id) result_user_id, result_token = yield self.handler.get_or_create_user( requester, local_part, None ) self.assertEquals(result_user_id, user_id) self.assertTrue(result_token is not None)
def on_POST(self, request): user_json = parse_json_object_from_request(request) access_token = get_access_token_from_request(request) app_service = self.store.get_app_service_by_token( access_token ) if not app_service: raise SynapseError(403, "Invalid application service token.") requester = create_requester(app_service.sender) logger.debug("creating user: %s", user_json) response = yield self._do_create(requester, user_json) defer.returnValue((200, response))
def setUp(self): self.mock_distributor = Mock() self.mock_distributor.declare("registered_user") self.mock_captcha_client = Mock() self.hs = yield setup_test_homeserver( self.addCleanup, expire_access_token=True, ) self.macaroon_generator = Mock( generate_access_token=Mock(return_value='secret') ) self.hs.get_macaroon_generator = Mock(return_value=self.macaroon_generator) self.handler = self.hs.get_handlers().registration_handler self.store = self.hs.get_datastore() self.hs.config.max_mau_value = 50 self.lots_of_users = 100 self.small_number_of_users = 1 self.requester = create_requester("@requester:test")
def send_notice( self, user_id, event_content, type=EventTypes.Message, state_key=None ): """Send a notice to the given user Creates the server notices room, if none exists. Args: user_id (str): mxid of user to send event to. event_content (dict): content of event to send type(EventTypes): type of event is_state_event(bool): Is the event a state event Returns: Deferred[FrozenEvent] """ room_id = yield self.get_notice_room_for_user(user_id) system_mxid = self._config.server_notices_mxid requester = create_requester(system_mxid) logger.info("Sending server notice to %s", user_id) event_dict = { "type": type, "room_id": room_id, "sender": system_mxid, "content": event_content, } if state_key is not None: event_dict['state_key'] = state_key res = yield self._event_creation_handler.create_and_send_nonmember_event( requester, event_dict, ratelimit=False, ) defer.returnValue(res)
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 = [ x.decode("ascii") for x in request.args[b"server_name"] ] # type: Optional[List[str]] 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}
async def get_user_by_req( self, request: SynapseRequest, allow_guest: bool = False, rights: str = "access", allow_expired: bool = False, ) -> Requester: """Get a registered user's ID. Args: request: An HTTP request with an access_token query parameter. allow_guest: If False, will raise an AuthError if the user making the request is a guest. rights: The operation being performed; the access token must allow this allow_expired: If True, allow the request through even if the account is expired, or session token lifetime has ended. Note that /login will deliver access tokens regardless of expiration. Returns: Resolves to the requester Raises: InvalidClientCredentialsError if no user by that token exists or the token is invalid. AuthError if access is denied for the user in the access token """ try: ip_addr = request.getClientIP() user_agent = get_request_user_agent(request) access_token = self.get_access_token_from_request(request) user_id, app_service = await self._get_appservice_user_id(request) if user_id and app_service: if ip_addr and self._track_appservice_user_ips: await self.store.insert_client_ip( user_id=user_id, access_token=access_token, ip=ip_addr, user_agent=user_agent, device_id="dummy-device", # stubbed ) requester = create_requester(user_id, app_service=app_service) request.requester = user_id if user_id in self._force_tracing_for_users: opentracing.force_tracing() opentracing.set_tag("authenticated_entity", user_id) opentracing.set_tag("user_id", user_id) opentracing.set_tag("appservice_id", app_service.id) return requester user_info = await self.get_user_by_access_token( access_token, rights, allow_expired=allow_expired ) token_id = user_info.token_id is_guest = user_info.is_guest shadow_banned = user_info.shadow_banned # Deny the request if the user account has expired. if not allow_expired: if await self._account_validity_handler.is_user_expired( user_info.user_id ): # Raise the error if either an account validity module has determined # the account has expired, or the legacy account validity # implementation is enabled and determined the account has expired raise AuthError( 403, "User account has expired", errcode=Codes.EXPIRED_ACCOUNT, ) device_id = user_info.device_id if access_token and ip_addr: await self.store.insert_client_ip( user_id=user_info.token_owner, access_token=access_token, ip=ip_addr, user_agent=user_agent, device_id=device_id, ) if is_guest and not allow_guest: raise AuthError( 403, "Guest access not allowed", errcode=Codes.GUEST_ACCESS_FORBIDDEN, ) # Mark the token as used. This is used to invalidate old refresh # tokens after some time. if not user_info.token_used and token_id is not None: await self.store.mark_access_token_as_used(token_id) requester = create_requester( user_info.user_id, token_id, is_guest, shadow_banned, device_id, app_service=app_service, authenticated_entity=user_info.token_owner, ) request.requester = requester if user_info.token_owner in self._force_tracing_for_users: opentracing.force_tracing() opentracing.set_tag("authenticated_entity", user_info.token_owner) opentracing.set_tag("user_id", user_info.user_id) if device_id: opentracing.set_tag("device_id", device_id) return requester except KeyError: raise MissingClientTokenError()
def _send_dummy_events_to_fill_extremities(self): """Background task to send dummy events into rooms that have a large number of extremities """ self._expire_rooms_to_exclude_from_dummy_event_insertion() room_ids = yield self.store.get_rooms_with_many_extremities( min_count=10, limit=5, room_id_filter=self._rooms_to_exclude_from_dummy_event_insertion. keys(), ) for room_id in room_ids: # For each room we need to find a joined member we can use to send # the dummy event with. prev_events_and_hashes = yield self.store.get_prev_events_for_room( room_id) latest_event_ids = (event_id for (event_id, _, _) in prev_events_and_hashes) members = yield self.state.get_current_users_in_room( room_id, latest_event_ids=latest_event_ids) dummy_event_sent = False for user_id in members: if not self.hs.is_mine_id(user_id): continue requester = create_requester(user_id) try: event, context = yield self.create_event( requester, { "type": "org.matrix.dummy_event", "content": {}, "room_id": room_id, "sender": user_id, }, prev_events_and_hashes=prev_events_and_hashes, ) event.internal_metadata.proactively_send = False yield self.send_nonmember_event(requester, event, context, ratelimit=False) dummy_event_sent = True break except ConsentNotGivenError: logger.info( "Failed to send dummy event into room %s for user %s due to " "lack of consent. Will try another user" % (room_id, user_id)) except AuthError: logger.info( "Failed to send dummy event into room %s for user %s due to " "lack of power. Will try another user" % (room_id, user_id)) if not dummy_event_sent: # Did not find a valid user in the room, so remove from future attempts # Exclusion is time limited, so the room will be rechecked in the future # dependent on _DUMMY_EVENT_ROOM_EXCLUSION_EXPIRY logger.info( "Failed to send dummy event into room %s. Will exclude it from " "future attempts until cache expires" % (room_id, )) now = self.clock.time_msec() self._rooms_to_exclude_from_dummy_event_insertion[ room_id] = now
def register( self, localpart=None, password=None, generate_token=True, guest_access_token=None, make_guest=False, admin=False, threepid=None, ): """Registers a new client on the server. Args: localpart : The local part of the user ID to register. If None, one will be generated. password (unicode) : The password to assign to this user so they can login again. This can be None which means they cannot login again via a password (e.g. the user is an application service user). generate_token (bool): Whether a new access token should be generated. Having this be True should be considered deprecated, since it offers no means of associating a device_id with the access_token. Instead you should call auth_handler.issue_access_token after registration. Returns: A tuple of (user_id, access_token). Raises: RegistrationError if there was a problem registering. """ yield self.auth.check_auth_blocking(threepid=threepid) password_hash = None if password: password_hash = yield self.auth_handler().hash(password) if localpart: yield self.check_username(localpart, guest_access_token=guest_access_token) was_guest = guest_access_token is not None if not was_guest: try: int(localpart) raise RegistrationError( 400, "Numeric user IDs are reserved for guest users.") except ValueError: pass user = UserID(localpart, self.hs.hostname) user_id = user.to_string() token = None if generate_token: token = self.macaroon_gen.generate_access_token(user_id) yield self.store.register( user_id=user_id, token=token, password_hash=password_hash, was_guest=was_guest, make_guest=make_guest, create_profile_with_localpart=( # If the user was a guest then they already have a profile None if was_guest else user.localpart), admin=admin, ) if self.hs.config.user_directory_search_all_users: profile = yield self.store.get_profileinfo(localpart) yield self.user_directory_handler.handle_local_profile_change( user_id, profile) else: # autogen a sequential user ID attempts = 0 token = None user = None while not user: localpart = yield self._generate_user_id(attempts > 0) user = UserID(localpart, self.hs.hostname) user_id = user.to_string() yield self.check_user_id_not_appservice_exclusive(user_id) if generate_token: token = self.macaroon_gen.generate_access_token(user_id) try: yield self.store.register( user_id=user_id, token=token, password_hash=password_hash, make_guest=make_guest, create_profile_with_localpart=user.localpart, ) except SynapseError: # if user id is taken, just generate another user = None user_id = None token = None attempts += 1 # auto-join the user to any rooms we're supposed to dump them into fake_requester = create_requester(user_id) # try to create the room if we're the first user on the server should_auto_create_rooms = False if self.hs.config.autocreate_auto_join_rooms: count = yield self.store.count_all_users() should_auto_create_rooms = count == 1 for r in self.hs.config.auto_join_rooms: try: if should_auto_create_rooms: room_alias = RoomAlias.from_string(r) if self.hs.hostname != room_alias.domain: logger.warning( 'Cannot create room alias %s, ' 'it does not match server domain', r, ) else: # create room expects the localpart of the room alias room_alias_localpart = room_alias.localpart yield self.room_creation_handler.create_room( fake_requester, config={ "preset": "public_chat", "room_alias_name": room_alias_localpart }, ratelimit=False, ) else: yield self._join_user_to_room(fake_requester, r) except Exception as e: logger.error("Failed to join new user to %r: %r", r, e) # We used to generate default identicons here, but nowadays # we want clients to generate their own as part of their branding # rather than there being consistent matrix-wide ones, so we don't. defer.returnValue((user_id, token))
def prepare(self, reactor, clock, hs): self.store = hs.get_datastore() self.user_id = self.register_user("foo", "pass") self.token = self.login("foo", "pass") self.requester = create_requester(self.user_id)
def on_POST(self, request, room_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") content = parse_json_object_from_request(request) assert_params_in_dict(content, ["new_room_user_id"]) new_room_user_id = content["new_room_user_id"] room_creator_requester = create_requester(new_room_user_id) message = content.get("message", self.DEFAULT_MESSAGE) room_name = content.get("room_name", "Content Violation Notification") info = yield self._room_creation_handler.create_room( room_creator_requester, config={ "preset": "public_chat", "name": room_name, "power_level_content_override": { "users_default": -10, }, }, ratelimit=False, ) new_room_id = info["room_id"] yield self.event_creation_handler.create_and_send_nonmember_event( room_creator_requester, { "type": "m.room.message", "content": {"body": message, "msgtype": "m.text"}, "room_id": new_room_id, "sender": new_room_user_id, }, ratelimit=False, ) requester_user_id = requester.user.to_string() logger.info("Shutting down room %r", room_id) yield self.store.block_room(room_id, requester_user_id) users = yield self.state.get_current_user_in_room(room_id) kicked_users = [] for user_id in users: if not self.hs.is_mine_id(user_id): continue logger.info("Kicking %r from %r...", user_id, room_id) target_requester = create_requester(user_id) yield self.room_member_handler.update_membership( requester=target_requester, target=target_requester.user, room_id=room_id, action=Membership.LEAVE, content={}, ratelimit=False ) yield self.room_member_handler.forget(target_requester.user, room_id) yield self.room_member_handler.update_membership( requester=target_requester, target=target_requester.user, room_id=new_room_id, action=Membership.JOIN, content={}, ratelimit=False ) kicked_users.append(user_id) aliases_for_room = yield self.store.get_aliases_for_room(room_id) yield self.store.update_aliases_for_room( room_id, new_room_id, requester_user_id ) defer.returnValue((200, { "kicked_users": kicked_users, "local_aliases": aliases_for_room, "new_room_id": new_room_id, }))
def test_send_typing_sharded(self): """Test that using two federation sender workers correctly sends new typing EDUs. """ mock_client1 = Mock(spec=["put_json"]) mock_client1.put_json.return_value = make_awaitable({}) self.make_worker_hs( "synapse.app.federation_sender", { "send_federation": True, "worker_name": "sender1", "federation_sender_instances": ["sender1", "sender2"], }, federation_http_client=mock_client1, ) mock_client2 = Mock(spec=["put_json"]) mock_client2.put_json.return_value = make_awaitable({}) self.make_worker_hs( "synapse.app.federation_sender", { "send_federation": True, "worker_name": "sender2", "federation_sender_instances": ["sender1", "sender2"], }, federation_http_client=mock_client2, ) user = self.register_user("user3", "pass") token = self.login("user3", "pass") typing_handler = self.hs.get_typing_handler() sent_on_1 = False sent_on_2 = False for i in range(20): server_name = "other_server_%d" % (i, ) room = self.create_room_with_remote_server(user, token, server_name) mock_client1.reset_mock() # type: ignore[attr-defined] mock_client2.reset_mock() # type: ignore[attr-defined] self.get_success( typing_handler.started_typing( target_user=UserID.from_string(user), requester=create_requester(user), room_id=room, timeout=20000, )) self.replicate() if mock_client1.put_json.called: sent_on_1 = True mock_client2.put_json.assert_not_called() self.assertEqual(mock_client1.put_json.call_args[0][0], server_name) self.assertTrue( mock_client1.put_json.call_args[1]["data"].get("edus")) elif mock_client2.put_json.called: sent_on_2 = True mock_client1.put_json.assert_not_called() self.assertEqual(mock_client2.put_json.call_args[0][0], server_name) self.assertTrue( mock_client2.put_json.call_args[1]["data"].get("edus")) else: raise AssertionError( "Expected send transaction from one or the other sender") if sent_on_1 and sent_on_2: break self.assertTrue(sent_on_1) self.assertTrue(sent_on_2)
async def update_room_membership( self, sender: str, target: str, room_id: str, new_membership: str, content: Optional[JsonDict] = None, ) -> EventBase: """Updates the membership of a user to the given value. Added in Synapse v1.46.0. Args: sender: The user performing the membership change. Must be a user local to this homeserver. target: The user whose membership is changing. This is often the same value as `sender`, but it might differ in some cases (e.g. when kicking a user, the `sender` is the user performing the kick and the `target` is the user being kicked). room_id: The room in which to change the membership. new_membership: The new membership state of `target` after this operation. See https://spec.matrix.org/unstable/client-server-api/#mroommember for the list of allowed values. content: Additional values to include in the resulting event's content. Returns: The newly created membership event. Raises: RuntimeError if the `sender` isn't a local user. ShadowBanError if a shadow-banned requester attempts to send an invite. SynapseError if the module attempts to send a membership event that isn't allowed, either by the server's configuration (e.g. trying to set a per-room display name that's too long) or by the validation rules around membership updates (e.g. the `membership` value is invalid). """ if not self.is_mine(sender): raise RuntimeError( "Tried to send an event as a user that isn't local to this homeserver", ) requester = create_requester(sender) target_user_id = UserID.from_string(target) if content is None: content = {} # Set the profile if not already done by the module. if "avatar_url" not in content or "displayname" not in content: try: # Try to fetch the user's profile. profile = await self._hs.get_profile_handler().get_profile( target_user_id.to_string(), ) except SynapseError as e: # If the profile couldn't be found, use default values. profile = { "displayname": target_user_id.localpart, "avatar_url": None, } if e.code != 404: # If the error isn't 404, it means we tried to fetch the profile over # federation but the remote server responded with a non-standard # status code. logger.error( "Got non-404 error status when fetching profile for %s", target_user_id.to_string(), ) # Set the profile where it needs to be set. if "avatar_url" not in content: content["avatar_url"] = profile["avatar_url"] if "displayname" not in content: content["displayname"] = profile["displayname"] event_id, _ = await self._hs.get_room_member_handler( ).update_membership( requester=requester, target=target_user_id, room_id=room_id, action=new_membership, content=content, ) # Try to retrieve the resulting event. event = await self._hs.get_datastore().get_event(event_id) # update_membership is supposed to always return after the event has been # successfully persisted. assert event is not None return event
async def set_displayname( self, target_user: UserID, requester: Requester, new_displayname: str, by_admin: bool = False, deactivation: bool = False, ) -> None: """Set the displayname of a user Args: target_user: the user whose displayname is to be changed. requester: The user attempting to make this change. new_displayname: The displayname to give this user. by_admin: Whether this change was made by an administrator. deactivation: Whether this change was made while deactivating the user. """ if not self.hs.is_mine(target_user): raise SynapseError(400, "User is not hosted on this homeserver") if not by_admin and target_user != requester.user: raise AuthError(400, "Cannot set another user's displayname") if not by_admin and not self.hs.config.registration.enable_set_displayname: profile = await self.store.get_profileinfo(target_user.localpart) if profile.display_name: raise SynapseError( 400, "Changing display name is disabled on this server", Codes.FORBIDDEN, ) if not isinstance(new_displayname, str): raise SynapseError(400, "'displayname' must be a string", errcode=Codes.INVALID_PARAM) if len(new_displayname) > MAX_DISPLAYNAME_LEN: raise SynapseError( 400, "Displayname is too long (max %i)" % (MAX_DISPLAYNAME_LEN, )) displayname_to_set: Optional[str] = new_displayname if new_displayname == "": displayname_to_set = None # If the admin changes the display name of a user, the requesting user cannot send # the join event to update the displayname in the rooms. # This must be done by the target user himself. if by_admin: requester = create_requester( target_user, authenticated_entity=requester.authenticated_entity, ) await self.store.set_profile_displayname(target_user.localpart, displayname_to_set) profile = await self.store.get_profileinfo(target_user.localpart) await self.user_directory_handler.handle_local_profile_change( target_user.to_string(), profile) await self._third_party_rules.on_profile_update( target_user.to_string(), profile, by_admin, deactivation) await self._update_join_states(requester, target_user)
async def on_POST(self, request, room_id): 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, ["new_room_user_id"]) new_room_user_id = content["new_room_user_id"] room_creator_requester = create_requester(new_room_user_id) message = content.get("message", self.DEFAULT_MESSAGE) room_name = content.get("room_name", "Content Violation Notification") info = await self._room_creation_handler.create_room( room_creator_requester, config={ "preset": "public_chat", "name": room_name, "power_level_content_override": { "users_default": -10 }, }, ratelimit=False, ) new_room_id = info["room_id"] requester_user_id = requester.user.to_string() logger.info("Shutting down room %r, joining to new room: %r", room_id, new_room_id) # This will work even if the room is already blocked, but that is # desirable in case the first attempt at blocking the room failed below. await self.store.block_room(room_id, requester_user_id) users = await self.state.get_current_users_in_room(room_id) kicked_users = [] failed_to_kick_users = [] for user_id in users: if not self.hs.is_mine_id(user_id): continue logger.info("Kicking %r from %r...", user_id, room_id) try: target_requester = create_requester(user_id) await self.room_member_handler.update_membership( requester=target_requester, target=target_requester.user, room_id=room_id, action=Membership.LEAVE, content={}, ratelimit=False, require_consent=False, ) await self.room_member_handler.forget(target_requester.user, room_id) await self.room_member_handler.update_membership( requester=target_requester, target=target_requester.user, room_id=new_room_id, action=Membership.JOIN, content={}, ratelimit=False, require_consent=False, ) kicked_users.append(user_id) except Exception: logger.exception( "Failed to leave old room and join new room for %r", user_id) failed_to_kick_users.append(user_id) await self.event_creation_handler.create_and_send_nonmember_event( room_creator_requester, { "type": "m.room.message", "content": { "body": message, "msgtype": "m.text" }, "room_id": new_room_id, "sender": new_room_user_id, }, ratelimit=False, ) aliases_for_room = await maybe_awaitable( self.store.get_aliases_for_room(room_id)) await self.store.update_aliases_for_room(room_id, new_room_id, requester_user_id) return ( 200, { "kicked_users": kicked_users, "failed_to_kick_users": failed_to_kick_users, "local_aliases": aliases_for_room, "new_room_id": new_room_id, }, )
async def _remote_join( self, requester: Requester, remote_room_hosts: List[str], room_id: str, user: UserID, content: dict, ) -> Tuple[str, int]: """Implements RoomMemberHandler._remote_join """ # filter ourselves out of remote_room_hosts: do_invite_join ignores it # and if it is the only entry we'd like to return a 404 rather than a # 500. remote_room_hosts = [ host for host in remote_room_hosts if host != self.hs.hostname ] if len(remote_room_hosts) == 0: raise SynapseError(404, "No known servers") check_complexity = self.hs.config.limit_remote_rooms.enabled if check_complexity and self.hs.config.limit_remote_rooms.admins_can_join: check_complexity = not await self.auth.is_server_admin(user) if check_complexity: # Fetch the room complexity too_complex = await self._is_remote_room_too_complex( room_id, remote_room_hosts) if too_complex is True: raise SynapseError( code=400, msg=self.hs.config.limit_remote_rooms.complexity_error, errcode=Codes.RESOURCE_LIMIT_EXCEEDED, ) # 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. event_id, stream_id = await self.federation_handler.do_invite_join( remote_room_hosts, room_id, user.to_string(), content) await self._user_joined_room(user, room_id) # Check the room we just joined wasn't too large, if we didn't fetch the # complexity of it before. if check_complexity: if too_complex is False: # We checked, and we're under the limit. return event_id, stream_id # Check again, but with the local state events too_complex = await self._is_local_room_too_complex(room_id) if too_complex is False: # We're under the limit. return event_id, stream_id # The room is too large. Leave. requester = types.create_requester(user, None, False, None) await self.update_membership(requester=requester, target=user, room_id=room_id, action="leave") raise SynapseError( code=400, msg=self.hs.config.limit_remote_rooms.complexity_error, errcode=Codes.RESOURCE_LIMIT_EXCEEDED, ) return event_id, stream_id
async def _join_rooms(self, user_id: str): """ Join or invite the user to the auto-join rooms. Args: user_id: The user to join """ room_member_handler = self.hs.get_room_member_handler() for r in self.hs.config.registration.auto_join_rooms: logger.info("Auto-joining %s to %s", user_id, r) try: room_alias = RoomAlias.from_string(r) if RoomAlias.is_valid(r): ( room_id, remote_room_hosts, ) = await room_member_handler.lookup_room_alias(room_alias) room_id = room_id.to_string() else: raise SynapseError( 400, "%s was not legal room ID or room alias" % (r, )) # Calculate whether the room requires an invite or can be # joined directly. Note that unless a join rule of public exists, # it is treated as requiring an invite. requires_invite = True state = await self.store.get_filtered_current_state_ids( room_id, StateFilter.from_types([(EventTypes.JoinRules, "")])) event_id = state.get((EventTypes.JoinRules, "")) if event_id: join_rules_event = await self.store.get_event( event_id, allow_none=True) if join_rules_event: join_rule = join_rules_event.content.get( "join_rule", None) requires_invite = join_rule and join_rule != JoinRules.PUBLIC # Send the invite, if necessary. if requires_invite: await room_member_handler.update_membership( requester=create_requester( self.hs.config.registration.auto_join_user_id), target=UserID.from_string(user_id), room_id=room_id, remote_room_hosts=remote_room_hosts, action="invite", ratelimit=False, ) # Send the join. await room_member_handler.update_membership( requester=create_requester(user_id), target=UserID.from_string(user_id), room_id=room_id, remote_room_hosts=remote_room_hosts, action="join", ratelimit=False, ) except ConsentNotGivenError as e: # Technically not necessary to pull out this error though # moving away from bare excepts is a good thing to do. logger.error("Failed to join new user to %r: %r", r, e) except Exception as e: logger.error("Failed to join new user to %r: %r", r, e)
async def _create_and_join_rooms(self, user_id: str): """ Create the auto-join rooms and join or invite the user to them. This should only be called when the first "real" user registers. Args: user_id: The user to join """ # Getting the handlers during init gives a dependency loop. room_creation_handler = self.hs.get_room_creation_handler() room_member_handler = self.hs.get_room_member_handler() # Generate a stub for how the rooms will be configured. stub_config = { "preset": self.hs.config.registration.autocreate_auto_join_room_preset, } # If the configuration providers a user ID to create rooms with, use # that instead of the first user registered. requires_join = False if self.hs.config.registration.auto_join_user_id: fake_requester = create_requester( self.hs.config.registration.auto_join_user_id) # If the room requires an invite, add the user to the list of invites. if self.hs.config.registration.auto_join_room_requires_invite: stub_config["invite"] = [user_id] # If the room is being created by a different user, the first user # registered needs to join it. Note that in the case of an invitation # being necessary this will occur after the invite was sent. requires_join = True else: fake_requester = create_requester(user_id) # Choose whether to federate the new room. if not self.hs.config.registration.autocreate_auto_join_rooms_federated: stub_config["creation_content"] = {"m.federate": False} for r in self.hs.config.registration.auto_join_rooms: logger.info("Auto-joining %s to %s", user_id, r) try: room_alias = RoomAlias.from_string(r) if self.hs.hostname != room_alias.domain: logger.warning( "Cannot create room alias %s, " "it does not match server domain", r, ) else: # A shallow copy is OK here since the only key that is # modified is room_alias_name. config = stub_config.copy() # create room expects the localpart of the room alias config["room_alias_name"] = room_alias.localpart info, _ = await room_creation_handler.create_room( fake_requester, config=config, ratelimit=False, ) # If the room does not require an invite, but another user # created it, then ensure the first user joins it. if requires_join: await room_member_handler.update_membership( requester=create_requester(user_id), target=UserID.from_string(user_id), room_id=info["room_id"], # Since it was just created, there are no remote hosts. remote_room_hosts=[], action="join", ratelimit=False, ) except ConsentNotGivenError as e: # Technically not necessary to pull out this error though # moving away from bare excepts is a good thing to do. logger.error("Failed to join new user to %r: %r", r, e) except Exception as e: logger.error("Failed to join new user to %r: %r", r, e)
def register( self, localpart=None, password=None, generate_token=True, guest_access_token=None, make_guest=False, admin=False, ): """Registers a new client on the server. Args: localpart : The local part of the user ID to register. If None, one will be generated. password (str) : The password to assign to this user so they can login again. This can be None which means they cannot login again via a password (e.g. the user is an application service user). generate_token (bool): Whether a new access token should be generated. Having this be True should be considered deprecated, since it offers no means of associating a device_id with the access_token. Instead you should call auth_handler.issue_access_token after registration. Returns: A tuple of (user_id, access_token). Raises: RegistrationError if there was a problem registering. """ yield run_on_reactor() password_hash = None if password: password_hash = yield self.auth_handler().hash(password) if localpart: yield self.check_username(localpart, guest_access_token=guest_access_token) was_guest = guest_access_token is not None if not was_guest: try: int(localpart) raise RegistrationError( 400, "Numeric user IDs are reserved for guest users." ) except ValueError: pass user = UserID(localpart, self.hs.hostname) user_id = user.to_string() token = None if generate_token: token = self.macaroon_gen.generate_access_token(user_id) yield self.store.register( user_id=user_id, token=token, password_hash=password_hash, was_guest=was_guest, make_guest=make_guest, create_profile_with_localpart=( # If the user was a guest then they already have a profile None if was_guest else user.localpart ), admin=admin, ) if self.hs.config.user_directory_search_all_users: profile = yield self.store.get_profileinfo(localpart) yield self.user_directory_handler.handle_local_profile_change( user_id, profile ) else: # autogen a sequential user ID attempts = 0 token = None user = None while not user: localpart = yield self._generate_user_id(attempts > 0) user = UserID(localpart, self.hs.hostname) user_id = user.to_string() yield self.check_user_id_not_appservice_exclusive(user_id) if generate_token: token = self.macaroon_gen.generate_access_token(user_id) try: yield self.store.register( user_id=user_id, token=token, password_hash=password_hash, make_guest=make_guest, create_profile_with_localpart=user.localpart, ) except SynapseError: # if user id is taken, just generate another user = None user_id = None token = None attempts += 1 # auto-join the user to any rooms we're supposed to dump them into fake_requester = create_requester(user_id) for r in self.hs.config.auto_join_rooms: try: yield self._join_user_to_room(fake_requester, r) except Exception as e: logger.error("Failed to join new user to %r: %r", r, e) # We used to generate default identicons here, but nowadays # we want clients to generate their own as part of their branding # rather than there being consistent matrix-wide ones, so we don't. defer.returnValue((user_id, token))
def register( self, localpart=None, password=None, generate_token=True, guest_access_token=None, make_guest=False, admin=False, threepid=None, ): """Registers a new client on the server. Args: localpart : The local part of the user ID to register. If None, one will be generated. password (unicode) : The password to assign to this user so they can login again. This can be None which means they cannot login again via a password (e.g. the user is an application service user). generate_token (bool): Whether a new access token should be generated. Having this be True should be considered deprecated, since it offers no means of associating a device_id with the access_token. Instead you should call auth_handler.issue_access_token after registration. Returns: A tuple of (user_id, access_token). Raises: RegistrationError if there was a problem registering. """ yield self.auth.check_auth_blocking(threepid=threepid) password_hash = None if password: password_hash = yield self.auth_handler().hash(password) if localpart: yield self.check_username(localpart, guest_access_token=guest_access_token) was_guest = guest_access_token is not None if not was_guest: try: int(localpart) raise RegistrationError( 400, "Numeric user IDs are reserved for guest users." ) except ValueError: pass user = UserID(localpart, self.hs.hostname) user_id = user.to_string() token = None if generate_token: token = self.macaroon_gen.generate_access_token(user_id) yield self.store.register( user_id=user_id, token=token, password_hash=password_hash, was_guest=was_guest, make_guest=make_guest, create_profile_with_localpart=( # If the user was a guest then they already have a profile None if was_guest else user.localpart ), admin=admin, ) if self.hs.config.user_directory_search_all_users: profile = yield self.store.get_profileinfo(localpart) yield self.user_directory_handler.handle_local_profile_change( user_id, profile ) else: # autogen a sequential user ID attempts = 0 token = None user = None while not user: localpart = yield self._generate_user_id(attempts > 0) user = UserID(localpart, self.hs.hostname) user_id = user.to_string() yield self.check_user_id_not_appservice_exclusive(user_id) if generate_token: token = self.macaroon_gen.generate_access_token(user_id) try: yield self.store.register( user_id=user_id, token=token, password_hash=password_hash, make_guest=make_guest, create_profile_with_localpart=user.localpart, ) except SynapseError: # if user id is taken, just generate another user = None user_id = None token = None attempts += 1 # auto-join the user to any rooms we're supposed to dump them into fake_requester = create_requester(user_id) # try to create the room if we're the first user on the server should_auto_create_rooms = False if self.hs.config.autocreate_auto_join_rooms: count = yield self.store.count_all_users() should_auto_create_rooms = count == 1 for r in self.hs.config.auto_join_rooms: try: if should_auto_create_rooms: room_alias = RoomAlias.from_string(r) if self.hs.hostname != room_alias.domain: logger.warning( 'Cannot create room alias %s, ' 'it does not match server domain', r, ) else: # create room expects the localpart of the room alias room_alias_localpart = room_alias.localpart # getting the RoomCreationHandler during init gives a dependency # loop yield self.hs.get_room_creation_handler().create_room( fake_requester, config={ "preset": "public_chat", "room_alias_name": room_alias_localpart }, ratelimit=False, ) else: yield self._join_user_to_room(fake_requester, r) except Exception as e: logger.error("Failed to join new user to %r: %r", r, e) defer.returnValue((user_id, token))
async def get_or_create_notice_room_for_user(self, user_id: str) -> str: """Get the room for notices for a given user If we have not yet created a notice room for this user, create it, but don't invite the user to it. Args: user_id: complete user id for the user we want a room for Returns: room id of notice room. """ if not self.is_enabled(): raise Exception("Server notices not enabled") assert self._is_mine_id( user_id), "Cannot send server notices to remote users" rooms = await self._store.get_rooms_for_local_user_where_membership_is( user_id, [Membership.INVITE, Membership.JOIN]) for room in rooms: # it's worth noting that there is an asymmetry here in that we # expect the user to be invited or joined, but the system user must # be joined. This is kinda deliberate, in that if somebody somehow # manages to invite the system user to a room, that doesn't make it # the server notices room. user_ids = await self._store.get_users_in_room(room.room_id) if len(user_ids) <= 2 and self.server_notices_mxid in user_ids: # we found a room which our user shares with the system notice # user logger.info( "Using existing server notices room %s for user %s", room.room_id, user_id, ) return room.room_id # apparently no existing notice room: create a new one logger.info("Creating server notices room for %s", user_id) # see if we want to override the profile info for the server user. # note that if we want to override either the display name or the # avatar, we have to use both. join_profile = None if (self._config.server_notices_mxid_display_name is not None or self._config.server_notices_mxid_avatar_url is not None): join_profile = { "displayname": self._config.server_notices_mxid_display_name, "avatar_url": self._config.server_notices_mxid_avatar_url, } requester = create_requester(self.server_notices_mxid, authenticated_entity=self._server_name) info, _ = await self._room_creation_handler.create_room( requester, config={ "preset": RoomCreationPreset.PRIVATE_CHAT, "name": self._config.server_notices_room_name, "power_level_content_override": { "users_default": -10 }, }, ratelimit=False, creator_join_profile=join_profile, ) room_id = info["room_id"] max_id = await self._account_data_handler.add_tag_to_room( user_id, room_id, SERVER_NOTICE_ROOM_TAG, {}) self._notifier.on_new_event("account_data_key", max_id, users=[user_id]) logger.info("Created server notices room %s for %s", room_id, user_id) return room_id
def test_typing_timeout(self): self.room_members = [U_APPLE, U_BANANA] self.assertEquals(self.event_source.get_current_key(), 0) self.get_success( self.handler.started_typing( target_user=U_APPLE, requester=create_requester(U_APPLE), room_id=ROOM_ID, timeout=10000, ) ) self.on_new_event.assert_has_calls([call("typing_key", 1, rooms=[ROOM_ID])]) self.on_new_event.reset_mock() self.assertEquals(self.event_source.get_current_key(), 1) events = self.get_success( self.event_source.get_new_events(room_ids=[ROOM_ID], from_key=0) ) self.assertEquals( events[0], [ { "type": "m.typing", "room_id": ROOM_ID, "content": {"user_ids": [U_APPLE.to_string()]}, } ], ) self.reactor.pump([16]) self.on_new_event.assert_has_calls([call("typing_key", 2, rooms=[ROOM_ID])]) self.assertEquals(self.event_source.get_current_key(), 2) events = self.get_success( self.event_source.get_new_events(room_ids=[ROOM_ID], from_key=1) ) self.assertEquals( events[0], [{"type": "m.typing", "room_id": ROOM_ID, "content": {"user_ids": []}}], ) # SYN-230 - see if we can still set after timeout self.get_success( self.handler.started_typing( target_user=U_APPLE, requester=create_requester(U_APPLE), room_id=ROOM_ID, timeout=10000, ) ) self.on_new_event.assert_has_calls([call("typing_key", 3, rooms=[ROOM_ID])]) self.on_new_event.reset_mock() self.assertEquals(self.event_source.get_current_key(), 3) events = self.get_success( self.event_source.get_new_events(room_ids=[ROOM_ID], from_key=0) ) self.assertEquals( events[0], [ { "type": "m.typing", "room_id": ROOM_ID, "content": {"user_ids": [U_APPLE.to_string()]}, } ], )
def test_auto_create_auto_join_room_preset_invalid_permissions(self): """ Auto-created rooms that are private require an invite, check that registration doesn't completely break if the inviter doesn't have proper permissions. """ inviter = "@support:test" # Register an initial user to create the room and such (essentially this # is a subset of test_auto_create_auto_join_room_preset). room_alias_str = "#room:test" user_id = self.get_success( self.handler.register_user(localpart="jeff")) # Ensure the room was created. directory_handler = self.hs.get_handlers().directory_handler room_alias = RoomAlias.from_string(room_alias_str) room_id = self.get_success( directory_handler.get_association(room_alias)) # Ensure the room exists. self.get_success(self.store.get_room_with_stats(room_id["room_id"])) # Both users should be in the room. rooms = self.get_success(self.store.get_rooms_for_user(inviter)) self.assertIn(room_id["room_id"], rooms) rooms = self.get_success(self.store.get_rooms_for_user(user_id)) self.assertIn(room_id["room_id"], rooms) # Lower the permissions of the inviter. event_creation_handler = self.hs.get_event_creation_handler() requester = create_requester(inviter) event, context = self.get_success( event_creation_handler.create_event( requester, { "type": "m.room.power_levels", "state_key": "", "room_id": room_id["room_id"], "content": { "invite": 100, "users": { inviter: 0 } }, "sender": inviter, }, )) self.get_success( event_creation_handler.send_nonmember_event( requester, event, context)) # Register a second user, which won't be be in the room (or even have an invite) # since the inviter no longer has the proper permissions. user_id = self.get_success(self.handler.register_user(localpart="bob")) # This user should not be in any rooms. rooms = self.get_success(self.store.get_rooms_for_user(user_id)) invited_rooms = self.get_success( self.store.get_invited_rooms_for_local_user(user_id)) self.assertEqual(rooms, set()) self.assertEqual(invited_rooms, [])
async def set_avatar_url( self, target_user: UserID, requester: Requester, new_avatar_url: str, by_admin: bool = False, deactivation: bool = False, ) -> None: """Set a new avatar URL for a user. Args: target_user: the user whose avatar URL is to be changed. requester: The user attempting to make this change. new_avatar_url: The avatar URL to give this user. by_admin: Whether this change was made by an administrator. deactivation: Whether this change was made while deactivating the user. """ if not self.hs.is_mine(target_user): raise SynapseError(400, "User is not hosted on this homeserver") if not by_admin and target_user != requester.user: raise AuthError(400, "Cannot set another user's avatar_url") if not by_admin and not self.hs.config.registration.enable_set_avatar_url: profile = await self.store.get_profileinfo(target_user.localpart) if profile.avatar_url: raise SynapseError( 400, "Changing avatar is disabled on this server", Codes.FORBIDDEN) if not isinstance(new_avatar_url, str): raise SynapseError(400, "'avatar_url' must be a string", errcode=Codes.INVALID_PARAM) if len(new_avatar_url) > MAX_AVATAR_URL_LEN: raise SynapseError( 400, "Avatar URL is too long (max %i)" % (MAX_AVATAR_URL_LEN, )) if not await self.check_avatar_size_and_mime_type(new_avatar_url): raise SynapseError(403, "This avatar is not allowed", Codes.FORBIDDEN) avatar_url_to_set: Optional[str] = new_avatar_url if new_avatar_url == "": avatar_url_to_set = None # Same like set_displayname if by_admin: requester = create_requester( target_user, authenticated_entity=requester.authenticated_entity) await self.store.set_profile_avatar_url(target_user.localpart, avatar_url_to_set) profile = await self.store.get_profileinfo(target_user.localpart) await self.user_directory_handler.handle_local_profile_change( target_user.to_string(), profile) await self._third_party_rules.on_profile_update( target_user.to_string(), profile, by_admin, deactivation) await self._update_join_states(requester, target_user)
async def send_membership_event( self, requester: Optional[Requester], event: EventBase, context: EventContext, ratelimit: bool = True, ): """ Change the membership status of a user in a room. Args: 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: The membership event. context: The context of the event. ratelimit: Whether to rate limit this request. Raises: SynapseError if there was a problem changing the membership. """ 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 = types.create_requester(target_user) prev_state_ids = await context.get_prev_state_ids() if event.membership == Membership.JOIN: if requester.is_guest: guest_can_join = await self._can_guest_join(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 = await self.store.is_room_blocked(room_id) if is_blocked: raise SynapseError( 403, "This room has been blocked on this server") event = await self.event_creation_handler.handle_new_client_event( requester, event, context, extra_users=[target_user], ratelimit=ratelimit) prev_member_event_id = prev_state_ids.get( (EventTypes.Member, event.state_key), None) if event.membership == Membership.LEAVE: if prev_member_event_id: prev_member_event = await self.store.get_event( prev_member_event_id) if prev_member_event.membership == Membership.JOIN: await self._user_left_room(target_user, room_id)
def get_user_by_req(request, allow_guest=False, rights="access"): return create_requester( UserID.from_string(self.helper.auth_user_id), 1, False, None )
def test_sending_events_into_room(self): """Tests that a module can send events into a room""" # Mock out create_and_send_nonmember_event to check whether events are being sent self.event_creation_handler.create_and_send_nonmember_event = Mock( spec=[], side_effect=self.event_creation_handler. create_and_send_nonmember_event, ) # Create a user and room to play with user_id = self.register_user("summer", "monkey") tok = self.login("summer", "monkey") room_id = self.helper.create_room_as(user_id, tok=tok) # Create and send a non-state event content = {"body": "I am a puppet", "msgtype": "m.text"} event_dict = { "room_id": room_id, "type": "m.room.message", "content": content, "sender": user_id, } event: EventBase = self.get_success( self.module_api.create_and_send_event_into_room(event_dict)) self.assertEqual(event.sender, user_id) self.assertEqual(event.type, "m.room.message") self.assertEqual(event.room_id, room_id) self.assertFalse(hasattr(event, "state_key")) self.assertDictEqual(event.content, content) expected_requester = create_requester( user_id, authenticated_entity=self.hs.hostname) # Check that the event was sent self.event_creation_handler.create_and_send_nonmember_event.assert_called_with( expected_requester, event_dict, ratelimit=False, ignore_shadow_ban=True, ) # Create and send a state event content = { "events_default": 0, "users": { user_id: 100 }, "state_default": 50, "users_default": 0, "events": { "test.event.type": 25 }, } event_dict = { "room_id": room_id, "type": "m.room.power_levels", "content": content, "sender": user_id, "state_key": "", } event: EventBase = self.get_success( self.module_api.create_and_send_event_into_room(event_dict)) self.assertEqual(event.sender, user_id) self.assertEqual(event.type, "m.room.power_levels") self.assertEqual(event.room_id, room_id) self.assertEqual(event.state_key, "") self.assertDictEqual(event.content, content) # Check that the event was sent self.event_creation_handler.create_and_send_nonmember_event.assert_called_with( expected_requester, { "type": "m.room.power_levels", "content": content, "room_id": room_id, "sender": user_id, "state_key": "", }, ratelimit=False, ignore_shadow_ban=True, ) # Check that we can't send membership events content = { "membership": "leave", } event_dict = { "room_id": room_id, "type": "m.room.member", "content": content, "sender": user_id, "state_key": user_id, } self.get_failure( self.module_api.create_and_send_event_into_room(event_dict), Exception)
def on_POST(self, request, room_id): requester = yield self.auth.get_user_by_req(request) yield assert_user_is_admin(self.auth, requester.user) content = parse_json_object_from_request(request) assert_params_in_dict(content, ["new_room_user_id"]) new_room_user_id = content["new_room_user_id"] room_creator_requester = create_requester(new_room_user_id) message = content.get("message", self.DEFAULT_MESSAGE) room_name = content.get("room_name", "Content Violation Notification") info = yield self._room_creation_handler.create_room( room_creator_requester, config={ "preset": "public_chat", "name": room_name, "power_level_content_override": { "users_default": -10, }, }, ratelimit=False, ) new_room_id = info["room_id"] requester_user_id = requester.user.to_string() logger.info( "Shutting down room %r, joining to new room: %r", room_id, new_room_id, ) # This will work even if the room is already blocked, but that is # desirable in case the first attempt at blocking the room failed below. yield self.store.block_room(room_id, requester_user_id) users = yield self.state.get_current_users_in_room(room_id) kicked_users = [] failed_to_kick_users = [] for user_id in users: if not self.hs.is_mine_id(user_id): continue logger.info("Kicking %r from %r...", user_id, room_id) try: target_requester = create_requester(user_id) yield self.room_member_handler.update_membership( requester=target_requester, target=target_requester.user, room_id=room_id, action=Membership.LEAVE, content={}, ratelimit=False, require_consent=False, ) yield self.room_member_handler.forget(target_requester.user, room_id) yield self.room_member_handler.update_membership( requester=target_requester, target=target_requester.user, room_id=new_room_id, action=Membership.JOIN, content={}, ratelimit=False, require_consent=False, ) kicked_users.append(user_id) except Exception: logger.exception( "Failed to leave old room and join new room for %r", user_id, ) failed_to_kick_users.append(user_id) yield self.event_creation_handler.create_and_send_nonmember_event( room_creator_requester, { "type": "m.room.message", "content": {"body": message, "msgtype": "m.text"}, "room_id": new_room_id, "sender": new_room_user_id, }, ratelimit=False, ) aliases_for_room = yield self.store.get_aliases_for_room(room_id) yield self.store.update_aliases_for_room( room_id, new_room_id, requester_user_id ) defer.returnValue((200, { "kicked_users": kicked_users, "failed_to_kick_users": failed_to_kick_users, "local_aliases": aliases_for_room, "new_room_id": new_room_id, }))
def send_membership_event(self, requester, event, context, 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. ratelimit (bool): Whether to rate limit this request. Raises: SynapseError if there was a problem changing the membership. """ 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 = types.create_requester(target_user) prev_event = yield self.event_creation_handler.deduplicate_state_event( event, context) if prev_event is not None: return prev_state_ids = yield context.get_prev_state_ids(self.store) if event.membership == Membership.JOIN: if requester.is_guest: guest_can_join = yield self._can_guest_join(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 self.event_creation_handler.handle_new_client_event( requester, event, context, extra_users=[target_user], ratelimit=ratelimit) prev_member_event_id = prev_state_ids.get( (EventTypes.Member, event.state_key), None) if event.membership == Membership.JOIN: # Only fire user_joined_room if the user has actually 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 self._user_joined_room(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: yield self._user_left_room(target_user, room_id)
async def on_POST(self, request, room_identifier): 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") if RoomID.is_valid(room_identifier): room_id = room_identifier try: remote_room_hosts = [ x.decode("ascii") for x in request.args[b"server_name"] ] # type: Optional[List[str]] except Exception: remote_room_hosts = None elif RoomAlias.is_valid(room_identifier): handler = self.room_member_handler room_alias = RoomAlias.from_string(room_identifier) room_id, remote_room_hosts = await handler.lookup_room_alias( room_alias) room_id = room_id.to_string() else: raise SynapseError( 400, "%s was not legal room ID or room alias" % (room_identifier, )) fake_requester = create_requester(target_user) # 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): 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}
async def complete_sso_login_request( self, auth_provider_id: str, remote_user_id: str, request: SynapseRequest, client_redirect_url: str, sso_to_matrix_id_mapper: Callable[[int], Awaitable[UserAttributes]], grandfather_existing_users: Callable[[], Awaitable[Optional[str]]], extra_login_attributes: Optional[JsonDict] = None, auth_provider_session_id: Optional[str] = None, ) -> None: """ Given an SSO ID, retrieve the user ID for it and possibly register the user. This first checks if the SSO ID has previously been linked to a matrix ID, if it has that matrix ID is returned regardless of the current mapping logic. If a callable is provided for grandfathering users, it is called and can potentially return a matrix ID to use. If it does, the SSO ID is linked to this matrix ID for subsequent calls. The mapping function is called (potentially multiple times) to generate a localpart for the user. If an unused localpart is generated, the user is registered from the given user-agent and IP address and the SSO ID is linked to this matrix ID for subsequent calls. Finally, we generate a redirect to the supplied redirect uri, with a login token Args: auth_provider_id: A unique identifier for this SSO provider, e.g. "oidc" or "saml". remote_user_id: The unique identifier from the SSO provider. request: The request to respond to client_redirect_url: The redirect URL passed in by the client. sso_to_matrix_id_mapper: A callable to generate the user attributes. The only parameter is an integer which represents the amount of times the returned mxid localpart mapping has failed. It is expected that the mapper can raise two exceptions, which will get passed through to the caller: MappingException if there was a problem mapping the response to the user. RedirectException to redirect to an additional page (e.g. to prompt the user for more information). grandfather_existing_users: A callable which can return an previously existing matrix ID. The SSO ID is then linked to the returned matrix ID. extra_login_attributes: An optional dictionary of extra attributes to be provided to the client in the login response. auth_provider_session_id: An optional session ID from the IdP. Raises: MappingException if there was a problem mapping the response to a user. RedirectException: if the mapping provider needs to redirect the user to an additional page. (e.g. to prompt for more information) """ new_user = False # grab a lock while we try to find a mapping for this user. This seems... # optimistic, especially for implementations that end up redirecting to # interstitial pages. async with self._mapping_lock.queue(auth_provider_id): # first of all, check if we already have a mapping for this user user_id = await self.get_sso_user_by_remote_user_id( auth_provider_id, remote_user_id, ) # Check for grandfathering of users. if not user_id: user_id = await grandfather_existing_users() if user_id: # Future logins should also match this user ID. await self._store.record_user_external_id( auth_provider_id, remote_user_id, user_id) # Otherwise, generate a new user. if not user_id: attributes = await self._call_attribute_mapper( sso_to_matrix_id_mapper) next_step_url = self._get_url_for_next_new_user_step( attributes=attributes) if next_step_url: await self._redirect_to_next_new_user_step( auth_provider_id, remote_user_id, attributes, client_redirect_url, next_step_url, extra_login_attributes, ) user_id = await self._register_mapped_user( attributes, auth_provider_id, remote_user_id, get_request_user_agent(request), request.getClientAddress().host, ) new_user = True elif self._sso_update_profile_information: attributes = await self._call_attribute_mapper( sso_to_matrix_id_mapper) if attributes.display_name: user_id_obj = UserID.from_string(user_id) profile_display_name = await self._profile_handler.get_displayname( user_id_obj) if profile_display_name != attributes.display_name: requester = create_requester( user_id, authenticated_entity=user_id, ) await self._profile_handler.set_displayname( user_id_obj, requester, attributes.display_name, True) await self._auth_handler.complete_sso_login( user_id, auth_provider_id, request, client_redirect_url, extra_login_attributes, new_user=new_user, auth_provider_session_id=auth_provider_session_id, )
def get_notice_room_for_user(self, user_id): """Get the room for notices for a given user If we have not yet created a notice room for this user, create it Args: user_id (str): complete user id for the user we want a room for Returns: str: room id of notice room. """ if not self.is_enabled(): raise Exception("Server notices not enabled") assert self._is_mine_id(user_id), \ "Cannot send server notices to remote users" rooms = yield self._store.get_rooms_for_user_where_membership_is( user_id, [Membership.INVITE, Membership.JOIN], ) system_mxid = self._config.server_notices_mxid for room in rooms: # it's worth noting that there is an asymmetry here in that we # expect the user to be invited or joined, but the system user must # be joined. This is kinda deliberate, in that if somebody somehow # manages to invite the system user to a room, that doesn't make it # the server notices room. user_ids = yield self._store.get_users_in_room(room.room_id) if system_mxid in user_ids: # we found a room which our user shares with the system notice # user logger.info("Using room %s", room.room_id) defer.returnValue(room.room_id) # apparently no existing notice room: create a new one logger.info("Creating server notices room for %s", user_id) # see if we want to override the profile info for the server user. # note that if we want to override either the display name or the # avatar, we have to use both. join_profile = None if ( self._config.server_notices_mxid_display_name is not None or self._config.server_notices_mxid_avatar_url is not None ): join_profile = { "displayname": self._config.server_notices_mxid_display_name, "avatar_url": self._config.server_notices_mxid_avatar_url, } requester = create_requester(system_mxid) info = yield self._room_creation_handler.create_room( requester, config={ "preset": RoomCreationPreset.PRIVATE_CHAT, "name": self._config.server_notices_room_name, "power_level_content_override": { "users_default": -10, }, "invite": (user_id,) }, ratelimit=False, creator_join_profile=join_profile, ) room_id = info['room_id'] max_id = yield self._store.add_tag_to_room( user_id, room_id, SERVER_NOTICE_ROOM_TAG, {}, ) self._notifier.on_new_event( "account_data_key", max_id, users=[user_id] ) logger.info("Created server notices room %s for %s", room_id, user_id) defer.returnValue(room_id)
async def on_POST(self, request, room_identifier): 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) # Resolve to a room ID, if necessary. if RoomID.is_valid(room_identifier): room_id = room_identifier elif RoomAlias.is_valid(room_identifier): room_alias = RoomAlias.from_string(room_identifier) room_id, _ = await self.room_member_handler.lookup_room_alias( room_alias) room_id = room_id.to_string() else: raise SynapseError( 400, "%s was not legal room ID or room alias" % (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): if room_state.get((EventTypes.Member, admin_user)): 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, {}