diff --git a/src/sentry/discover/endpoints/bases.py b/src/sentry/discover/endpoints/bases.py index 905101c95558c1..9eccf57ba9c99c 100644 --- a/src/sentry/discover/endpoints/bases.py +++ b/src/sentry/discover/endpoints/bases.py @@ -1,4 +1,6 @@ from sentry.api.bases.organization import OrganizationPermission +from sentry.discover.models import DiscoverSavedQuery +from sentry.models.organization import Organization class DiscoverSavedQueryPermission(OrganizationPermission): @@ -9,3 +11,14 @@ class DiscoverSavedQueryPermission(OrganizationPermission): "PUT": ["org:read", "org:write", "org:admin"], "DELETE": ["org:read", "org:write", "org:admin"], } + + def has_object_permission(self, request, view, obj): + if isinstance(obj, Organization): + return super().has_object_permission(request, view, obj) + + if isinstance(obj, DiscoverSavedQuery): + for project in obj.projects.all(): + if not request.access.has_project_access(project): + return False + + return True diff --git a/src/sentry/discover/endpoints/discover_saved_query_detail.py b/src/sentry/discover/endpoints/discover_saved_query_detail.py index 8d44147108d623..938b090341ba7b 100644 --- a/src/sentry/discover/endpoints/discover_saved_query_detail.py +++ b/src/sentry/discover/endpoints/discover_saved_query_detail.py @@ -55,6 +55,8 @@ def get(self, request: Request, organization, query) -> Response: if not self.has_feature(organization, request): return self.respond(status=404) + self.check_object_permissions(request, query) + return Response(serialize(query), status=200) def put(self, request: Request, organization, query) -> Response: @@ -64,6 +66,8 @@ def put(self, request: Request, organization, query) -> Response: if not self.has_feature(organization, request): return self.respond(status=404) + self.check_object_permissions(request, query) + try: params = self.get_filter_params( request, organization, project_ids=request.data.get("projects") @@ -98,6 +102,8 @@ def delete(self, request: Request, organization, query) -> Response: if not self.has_feature(organization, request): return self.respond(status=404) + self.check_object_permissions(request, query) + query.delete() return Response(status=204) diff --git a/tests/snuba/api/endpoints/test_discover_saved_query_detail.py b/tests/snuba/api/endpoints/test_discover_saved_query_detail.py index 1acd0fc9d451fd..b0a6db7a41ef8e 100644 --- a/tests/snuba/api/endpoints/test_discover_saved_query_detail.py +++ b/tests/snuba/api/endpoints/test_discover_saved_query_detail.py @@ -38,6 +38,16 @@ def setUp(self): self.query_id_without_access = invalid.id + def setup_no_team_user(self): + # disable Open Membership + self.org.flags.allow_joinleave = False + self.org.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.org, role="member", teams=[]) + self.login_as(user_no_team) + def test_invalid_id(self): with pytest.raises(NoReverseMatch): reverse("sentry-api-0-discover-saved-query-detail", args=[self.org.slug, "not-an-id"]) @@ -120,6 +130,18 @@ def test_get_homepage_query(self): assert response.status_code == 404, response.content + def test_get_disallow_when_no_project_access(self): + self.setup_no_team_user() + + with self.feature(self.feature_name): + url = reverse( + "sentry-api-0-discover-saved-query-detail", args=[self.org.slug, self.query_id] + ) + response = self.client.get(url) + + assert response.status_code == 403, response.data + assert response.data == {"detail": "You do not have permission to perform this action."} + def test_put(self): with self.feature(self.feature_name): url = reverse( @@ -322,6 +344,31 @@ def test_put_org_without_access(self): assert response.status_code == 403, response.content + def test_put_disallow_when_no_project_access(self): + self.setup_no_team_user() + + with self.feature(self.feature_name): + url = reverse( + "sentry-api-0-discover-saved-query-detail", args=[self.org.slug, self.query_id] + ) + + response = self.client.put( + url, + { + "name": "New query", + "projects": self.project_ids, + "fields": [], + "range": "24h", + "limit": 20, + "conditions": [], + "aggregations": [], + "orderby": "-time", + }, + ) + + assert response.status_code == 403, response.data + assert response.data == {"detail": "You do not have permission to perform this action."} + def test_delete(self): with self.feature(self.feature_name): url = reverse( @@ -389,6 +436,19 @@ def test_delete_homepage_query(self): assert response.status_code == 404, response.content + def test_delete_disallow_when_no_project_access(self): + self.setup_no_team_user() + + with self.feature(self.feature_name): + url = reverse( + "sentry-api-0-discover-saved-query-detail", args=[self.org.slug, self.query_id] + ) + + response = self.client.delete(url) + + assert response.status_code == 403, response.data + assert response.data == {"detail": "You do not have permission to perform this action."} + class OrganizationDiscoverQueryVisitTest(APITestCase, SnubaTestCase): def setUp(self):