diff --git a/templates/zerver/api/changelog.md b/templates/zerver/api/changelog.md index 7b50f01dfa..3cc4c28c51 100644 --- a/templates/zerver/api/changelog.md +++ b/templates/zerver/api/changelog.md @@ -34,6 +34,8 @@ format used by the Zulip server that they are interacting with. * [`GET /user_groups/{user_group_id}/members/{user_id}`](/api/get-is-user-group-member): Added new endpoint for checking whether a given user is member of a given user group. +* [`GET /user_groups/{user_group_id}/members`](/api/get-user-group-members): + Added new endpoint to get members of a user group. **Feature level 126** diff --git a/templates/zerver/help/include/rest-endpoints.md b/templates/zerver/help/include/rest-endpoints.md index 7bf8e32b69..b09318492b 100644 --- a/templates/zerver/help/include/rest-endpoints.md +++ b/templates/zerver/help/include/rest-endpoints.md @@ -63,6 +63,7 @@ * [Update user group members](/api/update-user-group-members) * [Update user group subgroups](/api/update-user-group-subgroups) * [Get user group membership status](/api/get-is-user-group-member) +* [Get user group members](/api/get-user-group-members) * [Mute a user](/api/mute-user) * [Unmute a user](/api/unmute-user) diff --git a/zerver/lib/user_groups.py b/zerver/lib/user_groups.py index 6e971db81c..97b69f7ea0 100644 --- a/zerver/lib/user_groups.py +++ b/zerver/lib/user_groups.py @@ -168,6 +168,17 @@ def is_user_in_group( return get_recursive_group_members(user_group=user_group).filter(id=user.id).exists() +def get_user_group_member_ids( + user_group: UserGroup, *, direct_member_only: bool = False +) -> List[int]: + if direct_member_only: + member_ids = get_user_group_direct_members(user_group) + else: + member_ids = get_recursive_group_members(user_group).values_list("id", flat=True) + + return list(member_ids) + + def create_system_user_groups_for_realm(realm: Realm) -> Dict[int, UserGroup]: """Any changes to this function likely require a migration to adjust existing realms. See e.g. migration 0375_create_role_based_system_groups.py, diff --git a/zerver/openapi/zulip.yaml b/zerver/openapi/zulip.yaml index b9e976fdcb..06d4469e5e 100644 --- a/zerver/openapi/zulip.yaml +++ b/zerver/openapi/zulip.yaml @@ -13756,6 +13756,40 @@ paths: responses: "200": $ref: "#/components/responses/SimpleSuccess" + get: + operationId: get-user-group-members + summary: Get user group members + tags: ["users"] + description: | + Get the members of a [user group](/help/user-groups). + + `GET {{ api_url }}/v1/user_groups/{user_group_id}/members` + + **Changes**: New in Zulip 6.0 (feature level 127). + parameters: + - $ref: "#/components/parameters/UserGroupId" + - $ref: "#/components/parameters/DirectMemberOnly" + responses: + "200": + description: Success + content: + application/json: + schema: + allOf: + - $ref: "#/components/schemas/JsonSuccessBase" + - $ref: "#/components/schemas/SuccessDescription" + - additionalProperties: false + properties: + result: {} + msg: {} + members: + type: array + items: + type: integer + description: | + A list containing the user IDs of members of the user group. + example: + {"msg": "", "result": "success", "members": [10, 12]} /user_groups/{user_group_id}: patch: operationId: update-user-group diff --git a/zerver/tests/test_user_groups.py b/zerver/tests/test_user_groups.py index 97f0a9e91d..7704e32f48 100644 --- a/zerver/tests/test_user_groups.py +++ b/zerver/tests/test_user_groups.py @@ -941,3 +941,41 @@ class UserGroupAPITestCase(UserGroupTestCase): self.client_get(f"/json/user_groups/{admins_group.id}/members/{othello.id}").content ) self.assertFalse(result_dict["is_user_group_member"]) + + def test_get_user_group_members(self) -> None: + realm = get_realm("zulip") + iago = self.example_user("iago") + desdemona = self.example_user("desdemona") + shiva = self.example_user("shiva") + moderators_group = UserGroup.objects.get( + name="@role:moderators", realm=realm, is_system_group=True + ) + self.login("iago") + + # Test invalid user group id + result = self.client_get("/json/user_groups/25/members") + self.assert_json_error(result, "Invalid user group") + + result_dict = orjson.loads( + self.client_get(f"/json/user_groups/{moderators_group.id}/members").content + ) + self.assertCountEqual(result_dict["members"], [desdemona.id, iago.id, shiva.id]) + + params = {"direct_member_only": orjson.dumps(True).decode()} + result_dict = orjson.loads( + self.client_get(f"/json/user_groups/{moderators_group.id}/members", info=params).content + ) + self.assertCountEqual(result_dict["members"], [shiva.id]) + + # User not part of a group can also get its members. + self.login("hamlet") + result_dict = orjson.loads( + self.client_get(f"/json/user_groups/{moderators_group.id}/members").content + ) + self.assertCountEqual(result_dict["members"], [desdemona.id, iago.id, shiva.id]) + + params = {"direct_member_only": orjson.dumps(True).decode()} + result_dict = orjson.loads( + self.client_get(f"/json/user_groups/{moderators_group.id}/members", info=params).content + ) + self.assertCountEqual(result_dict["members"], [shiva.id]) diff --git a/zerver/views/user_groups.py b/zerver/views/user_groups.py index c3f89e2022..f46de916c1 100644 --- a/zerver/views/user_groups.py +++ b/zerver/views/user_groups.py @@ -22,6 +22,7 @@ from zerver.lib.user_groups import ( access_user_groups_as_potential_subgroups, get_direct_memberships_of_users, get_user_group_direct_members, + get_user_group_member_ids, is_user_in_group, user_groups_in_realm_serialized, ) @@ -240,3 +241,21 @@ def get_is_user_group_member( ) }, ) + + +@require_member_or_admin +@has_request_variables +def get_user_group_members( + request: HttpRequest, + user_profile: UserProfile, + user_group_id: int = REQ(json_validator=check_int, path_only=True), + direct_member_only: bool = REQ(json_validator=check_bool, default=False), +) -> HttpResponse: + user_group = access_user_group_by_id(user_group_id, user_profile, for_read=True) + + return json_success( + request, + data={ + "members": get_user_group_member_ids(user_group, direct_member_only=direct_member_only) + }, + ) diff --git a/zproject/urls.py b/zproject/urls.py index 84f6327c98..5dc185ff76 100644 --- a/zproject/urls.py +++ b/zproject/urls.py @@ -178,6 +178,7 @@ from zerver.views.user_groups import ( edit_user_group, get_is_user_group_member, get_user_group, + get_user_group_members, update_subgroups_of_user_group, update_user_group_backend, ) @@ -373,7 +374,11 @@ v1_api_and_json_patterns = [ rest_path("user_groups", GET=get_user_group), rest_path("user_groups/create", POST=add_user_group), rest_path("user_groups/", PATCH=edit_user_group, DELETE=delete_user_group), - rest_path("user_groups//members", POST=update_user_group_backend), + rest_path( + "user_groups//members", + GET=get_user_group_members, + POST=update_user_group_backend, + ), rest_path("user_groups//subgroups", POST=update_subgroups_of_user_group), rest_path( "user_groups//members/", GET=get_is_user_group_member