Browse Source

fix(dashboards): add dashboard object permission check (#70228)

Alexander Tarasov 10 months ago
parent
commit
6cfa94275b

+ 4 - 0
src/sentry/api/endpoints/organization_dashboard_details.py

@@ -86,6 +86,8 @@ class OrganizationDashboardDetailsEndpoint(OrganizationDashboardBase):
         if not features.has(EDIT_FEATURE, organization, actor=request.user):
             return Response(status=404)
 
+        self.check_object_permissions(request, dashboard)
+
         num_dashboards = Dashboard.objects.filter(organization=organization).count()
         num_tombstones = DashboardTombstone.objects.filter(organization=organization).count()
 
@@ -118,6 +120,8 @@ class OrganizationDashboardDetailsEndpoint(OrganizationDashboardBase):
         if not features.has(EDIT_FEATURE, organization, actor=request.user):
             return Response(status=404)
 
+        self.check_object_permissions(request, dashboard)
+
         tombstone = None
         if isinstance(dashboard, dict):
             tombstone = dashboard["id"]

+ 12 - 0
src/sentry/api/endpoints/organization_dashboards.py

@@ -17,6 +17,7 @@ from sentry.api.serializers import serialize
 from sentry.api.serializers.models.dashboard import DashboardListSerializer
 from sentry.api.serializers.rest_framework import DashboardSerializer
 from sentry.models.dashboard import Dashboard
+from sentry.models.organization import Organization
 
 MAX_RETRIES = 10
 DUPLICATE_TITLE_PATTERN = r"(.*) copy(?:$|\s(\d+))"
@@ -30,6 +31,17 @@ class OrganizationDashboardsPermission(OrganizationPermission):
         "DELETE": ["org:read", "org:write", "org:admin"],
     }
 
+    def has_object_permission(self, request: Request, view, obj):
+        if isinstance(obj, Organization):
+            return super().has_object_permission(request, view, obj)
+
+        if isinstance(obj, Dashboard):
+            for project in obj.projects.all():
+                if not request.access.has_project_access(project):
+                    return False
+
+        return True
+
 
 @region_silo_endpoint
 class OrganizationDashboardsEndpoint(OrganizationEndpoint):

+ 40 - 0
tests/sentry/api/endpoints/test_organization_dashboard_details.py

@@ -234,6 +234,25 @@ class OrganizationDashboardDetailsDeleteTest(OrganizationDashboardDetailsTestCas
         self.create_user_member_role()
         self.test_delete()
 
+    def test_disallow_delete_when_no_project_access(self):
+        # disable Open Membership
+        self.organization.flags.allow_joinleave = False
+        self.organization.save()
+
+        # assign a project to a dashboard
+        self.dashboard.projects.set([self.project])
+
+        # user has no access to the above 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.do_request("delete", self.url(self.dashboard.id))
+        assert response.status_code == 403
+        assert response.data == {"detail": "You do not have permission to perform this action."}
+
     def test_dashboard_does_not_exist(self):
         response = self.do_request("delete", self.url(1234567890))
         assert response.status_code == 404
@@ -338,6 +357,27 @@ class OrganizationDashboardDetailsPutTest(OrganizationDashboardDetailsTestCase):
         assert response.status_code == 409, response.data
         assert list(response.data) == ["Dashboard with that title already exists."]
 
+    def test_disallow_put_when_no_project_access(self):
+        # disable Open Membership
+        self.organization.flags.allow_joinleave = False
+        self.organization.save()
+
+        # assign a project to a dashboard
+        self.dashboard.projects.set([self.project])
+
+        # user has no access to the above 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.do_request(
+            "put", self.url(self.dashboard.id), data={"title": "Dashboard Hello"}
+        )
+        assert response.status_code == 403, response.data
+        assert response.data == {"detail": "You do not have permission to perform this action."}
+
     def test_add_widget(self):
         data: dict[str, Any] = {
             "title": "First dashboard",