From e402c76153b32f4314bb30dc1ca3a1790f5c9c08 Mon Sep 17 00:00:00 2001 From: Alexander Tarasov Date: Fri, 22 Nov 2024 08:25:23 +0100 Subject: [PATCH] fix(stats): hide stats if no project access (#81162) Second attempt of https://github.com/getsentry/sentry/pull/80447 Comparing with the previous attempt, we now also allow project stats for staff users (`or user.is_staff`). The superuser case is already covered in this code: https://github.com/getsentry/sentry/blob/2e5e0654c717adfa569be7875f44cb7e2bda4a74/src/sentry/api/serializers/models/project.py#L131-L136 `is_superuser` is True, therefore `has_access` is True, and the first condition in this snippet works: ``` if attrs["has_access"] or user.is_staff: ``` --- src/sentry/api/serializers/models/project.py | 18 ++--- .../endpoints/test_organization_projects.py | 67 ++++++++++++++++++- 2 files changed, 76 insertions(+), 9 deletions(-) diff --git a/src/sentry/api/serializers/models/project.py b/src/sentry/api/serializers/models/project.py index 9574a289bfb6c1..53ac97eb6c8997 100644 --- a/src/sentry/api/serializers/models/project.py +++ b/src/sentry/api/serializers/models/project.py @@ -770,14 +770,16 @@ def serialize( ) if not self._collapse(LATEST_DEPLOYS_KEY): context[LATEST_DEPLOYS_KEY] = attrs["deploys"] - if "stats" in attrs: - context.update(stats=attrs["stats"]) - if "transactionStats" in attrs: - context.update(transactionStats=attrs["transactionStats"]) - if "sessionStats" in attrs: - context.update(sessionStats=attrs["sessionStats"]) - if "options" in attrs: - context.update(options=attrs["options"]) + + if attrs["has_access"] or user.is_staff: + if "stats" in attrs: + context.update(stats=attrs["stats"]) + if "transactionStats" in attrs: + context.update(transactionStats=attrs["transactionStats"]) + if "sessionStats" in attrs: + context.update(sessionStats=attrs["sessionStats"]) + if "options" in attrs: + context.update(options=attrs["options"]) return context diff --git a/tests/sentry/api/endpoints/test_organization_projects.py b/tests/sentry/api/endpoints/test_organization_projects.py index 7f4cd3bb5ded58..d268e6c6c437d3 100644 --- a/tests/sentry/api/endpoints/test_organization_projects.py +++ b/tests/sentry/api/endpoints/test_organization_projects.py @@ -1,3 +1,4 @@ +import pytest from django.urls import reverse from sentry.models.apikey import ApiKey @@ -6,7 +7,7 @@ from sentry.testutils.silo import assume_test_silo_mode from sentry.testutils.skips import requires_snuba -pytestmark = [requires_snuba] +pytestmark = [pytest.mark.sentry_metrics, requires_snuba] class OrganizationProjectsTestBase(APITestCase): @@ -85,6 +86,70 @@ def test_with_stats(self): self.organization.slug, qs_params={"statsPeriod": "48h"}, status_code=400 ) + def test_staff_with_stats(self): + projects = [self.create_project(teams=[self.team])] + + # disable Open Membership + self.organization.flags.allow_joinleave = False + self.organization.save() + + staff_user = self.create_user(is_staff=True) + self.login_as(user=staff_user, staff=True) + + response = self.get_success_response( + self.organization.slug, + qs_params={"statsPeriod": "24h", "transactionStats": "1", "sessionStats": "1"}, + ) + self.check_valid_response(response, projects) + + assert "stats" in response.data[0] + assert "transactionStats" in response.data[0] + assert "sessionStats" in response.data[0] + + def test_superuser_with_stats(self): + projects = [self.create_project(teams=[self.team])] + + # disable Open Membership + self.organization.flags.allow_joinleave = False + self.organization.save() + + superuser = self.create_user(is_superuser=True) + self.login_as(user=superuser, superuser=True) + + response = self.get_success_response( + self.organization.slug, + qs_params={"statsPeriod": "24h", "transactionStats": "1", "sessionStats": "1"}, + ) + self.check_valid_response(response, projects) + + assert "stats" in response.data[0] + assert "transactionStats" in response.data[0] + assert "sessionStats" in response.data[0] + + def test_no_stats_if_no_project_access(self): + projects = [self.create_project(teams=[self.team])] + + # disable Open Membership + self.organization.flags.allow_joinleave = False + self.organization.save() + + # user has no access to the first project + user_no_team = self.create_user(is_superuser=False) + self.create_member( + user=user_no_team, organization=self.organization, role="member", teams=[] + ) + self.login_as(user_no_team) + + response = self.get_success_response( + self.organization.slug, + qs_params={"statsPeriod": "24h", "transactionStats": "1", "sessionStats": "1"}, + ) + self.check_valid_response(response, projects) + + assert "stats" not in response.data[0] + assert "transactionStats" not in response.data[0] + assert "sessionStats" not in response.data[0] + def test_search(self): project = self.create_project(teams=[self.team], name="bar", slug="bar")