diff --git a/api/organisations/permissions/permissions.py b/api/organisations/permissions/permissions.py index 7207c4cba286..82e42bf32420 100644 --- a/api/organisations/permissions/permissions.py +++ b/api/organisations/permissions/permissions.py @@ -127,7 +127,7 @@ def has_permission(self, request, view): return False return ( - view.action in ("list", "my_groups") + view.action in ("list", "my_groups", "summaries") and request.user.belongs_to(organisation.id) or view.detail is True # delegate to has_object_permission / get_queryset or request.user.has_organisation_permission( diff --git a/api/tests/unit/organisations/test_unit_organisations_views.py b/api/tests/unit/organisations/test_unit_organisations_views.py index 33f019035b48..e21b71b5da61 100644 --- a/api/tests/unit/organisations/test_unit_organisations_views.py +++ b/api/tests/unit/organisations/test_unit_organisations_views.py @@ -1640,3 +1640,58 @@ def test_list_organisations_shows_dunning( _subscription = response.data["results"][0]["subscription"] assert _subscription["id"] == subscription.id assert _subscription["billing_status"] == SUBSCRIPTION_BILLING_STATUS_DUNNING + + +def test_list_group_summaries( + organisation: Organisation, staff_client: APIClient +) -> None: + # Given + user_permission_group_1 = UserPermissionGroup.objects.create( + organisation=organisation, name="group1" + ) + user_permission_group_2 = UserPermissionGroup.objects.create( + organisation=organisation, name="group2" + ) + + url = reverse( + "api-v1:organisations:organisation-groups-summaries", args=[organisation.id] + ) + + # When + response = staff_client.get(url) + + # Then + assert response.status_code == status.HTTP_200_OK + + response_json = response.json() + assert response_json["count"] == 2 + assert response_json["results"][0] == { + "id": user_permission_group_1.id, + "name": user_permission_group_1.name, + } + assert response_json["results"][1] == { + "id": user_permission_group_2.id, + "name": user_permission_group_2.name, + } + + +def test_user_from_another_organisation_cannot_list_group_summaries( + organisation: Organisation, api_client: APIClient +) -> None: + # Given + UserPermissionGroup.objects.create(organisation=organisation, name="group1") + + organisation_2 = Organisation.objects.create(name="org2") + org2_user = FFAdminUser.objects.create(email="org2user@example.com") + org2_user.add_organisation(organisation_2) + api_client.force_authenticate(org2_user) + + url = reverse( + "api-v1:organisations:organisation-groups-summaries", args=[organisation.id] + ) + + # When + response = api_client.get(url) + + # Then + assert response.status_code == status.HTTP_403_FORBIDDEN diff --git a/api/users/views.py b/api/users/views.py index 5e6cd85890c3..d4c115084c13 100644 --- a/api/users/views.py +++ b/api/users/views.py @@ -176,14 +176,16 @@ def get_queryset(self): organisation = Organisation.objects.get(id=organisation_pk) qs = UserPermissionGroup.objects.filter(organisation=organisation) - if not self.request.user.has_organisation_permission( - organisation, MANAGE_USER_GROUPS + if ( + self.action != "summaries" + and not self.request.user.has_organisation_permission( + organisation, MANAGE_USER_GROUPS + ) ): + # my_groups and summaries return a very cut down set of data, we can safely allow all users + # of the groups / organisation to retrieve them in this case, otherwise they must be a group admin. q = Q(userpermissiongroupmembership__ffadminuser=self.request.user) if self.action != "my_groups": - # my-groups returns a very cut down set of data, we can safely allow all users - # of the groups to retrieve them in this case, otherwise they must be a group - # admin. q = q & Q(userpermissiongroupmembership__group_admin=True) qs = qs.filter(q) @@ -192,7 +194,7 @@ def get_queryset(self): def get_serializer_class(self): if self.action == "retrieve": return UserPermissionGroupSerializerDetail - elif self.action == "my_groups": + elif self.action in ("my_groups", "summaries"): return UserPermissionGroupSummarySerializer return ListUserPermissionGroupSerializer @@ -258,6 +260,13 @@ def my_groups(self, request: Request, organisation_pk: int) -> Response: """ return self.list(request, organisation_pk) + @action(detail=False, methods=["GET"]) + def summaries(self, request: Request, organisation_pk: int) -> Response: + """ + Returns a list of summary group objects for all groups in the organisation. + """ + return self.list(request, organisation_pk) + @api_view(["POST"]) @permission_classes([IsAuthenticated, NestedIsOrganisationAdminPermission])