diff --git a/zerver/lib/users.py b/zerver/lib/users.py index 8f76e996e5..7cf3a25a61 100644 --- a/zerver/lib/users.py +++ b/zerver/lib/users.py @@ -43,6 +43,7 @@ from zerver.models.users import ( active_user_ids, base_bulk_get_user_queryset, base_get_user_queryset, + get_partial_realm_user_dicts, get_realm_user_dicts, get_realm_user_dicts_from_ids, get_user_by_id_in_realm_including_cross_realm, @@ -1058,6 +1059,8 @@ def get_user_dicts_in_realm( ) -> tuple[list[RawUserDict], list[APIUserDict]]: if user_ids is not None: all_user_dicts = get_realm_user_dicts_from_ids(realm.id, user_ids) + elif settings.PARTIAL_USERS: + all_user_dicts = get_partial_realm_user_dicts(realm.id, user_profile) else: all_user_dicts = get_realm_user_dicts(realm.id) if check_user_can_access_all_users(user_profile): diff --git a/zerver/models/users.py b/zerver/models/users.py index a8e82dd0e3..d7c347704c 100644 --- a/zerver/models/users.py +++ b/zerver/models/users.py @@ -1116,6 +1116,27 @@ def get_realm_user_dicts(realm_id: int) -> list[RawUserDict]: ) +def get_partial_realm_user_dicts( + realm_id: int, user_profile: UserProfile | None +) -> list[RawUserDict]: + """Returns a subset of the users in the realm, guaranteed to + include the current user as well as all bots in the realm. + """ + + # Currently, we send the minimum set of users permitted by the API. + user_selection_clause = Q(is_bot=True) + if user_profile is not None: + user_selection_clause |= Q(id=user_profile.id) + + return list( + UserProfile.objects.filter(realm_id=realm_id) + .filter( + user_selection_clause, + ) + .values(*realm_user_dict_fields) + ) + + def get_realm_user_dicts_from_ids(realm_id: int, user_ids: list[int]) -> list[RawUserDict]: return list( UserProfile.objects.filter( diff --git a/zerver/tests/test_users.py b/zerver/tests/test_users.py index 35b0b587b5..5dc7a23719 100644 --- a/zerver/tests/test_users.py +++ b/zerver/tests/test_users.py @@ -3165,6 +3165,17 @@ class GetProfileTest(ZulipTestCase): result = self.client_get(f"/json/users/{user.id}") self.assert_json_error(result, "Insufficient permission") + with self.settings(PARTIAL_USERS=True), self.assert_database_query_count(9): + result = self.client_get("/json/users") + self.assert_json_success(result) + + result_dict = orjson.loads(result.content) + all_fetched_users = result_dict["members"] + self.assertEqual( + len(all_fetched_users), + UserProfile.objects.filter(realm=hamlet.realm, is_bot=True).count() + 1, + ) + def test_get_inaccessible_user_ids(self) -> None: polonius = self.example_user("polonius") bot = self.example_user("default_bot") @@ -3232,6 +3243,16 @@ class GetProfileTest(ZulipTestCase): user_ids_to_fetch, ) + with self.settings(PARTIAL_USERS=True), self.assert_database_query_count(4): + result = self.client_get("/json/users") + self.assert_json_success(result) + result_dict = orjson.loads(result.content) + all_fetched_users = result_dict["members"] + self.assertEqual( + len(all_fetched_users), + UserProfile.objects.filter(realm=hamlet.realm, is_bot=True).count(), + ) + class DeleteUserTest(ZulipTestCase): def test_do_delete_user(self) -> None: diff --git a/zproject/computed_settings.py b/zproject/computed_settings.py index 18be84e12f..2815949255 100644 --- a/zproject/computed_settings.py +++ b/zproject/computed_settings.py @@ -1307,3 +1307,5 @@ SCIM_SERVICE_PROVIDER = { # Which API key to use will be determined based on TOPIC_SUMMARIZATION_MODEL. TOPIC_SUMMARIZATION_API_KEY = get_secret("topic_summarization_api_key", None) + +PARTIAL_USERS = bool(os.environ.get("PARTIAL_USERS"))