Skip to content

Commit e282378

Browse files
oiokigggritso
andauthored
fix(dashboards): stricter permission check when dashboards cover all/my projects (#78615)
When Open Membership is disabled, it is expected to have more granular access to certain objects that are associated with projects. First version of project-level access on dashboards was implemented in #70228 However, dashboards that cover "All Projects" or "My Projects" do not have explicit project ids, therefore we need to do a different check. After this PR, we will allow access to such dashboards only in these cases: * if Open Membership is enabled; * if actor is a Manager/Owner (having `org:write` scope); * if actor is the original creator of a dashboard. --------- Co-authored-by: George Gritsouk <[email protected]>
1 parent 4aa527a commit e282378

File tree

2 files changed

+78
-3
lines changed

2 files changed

+78
-3
lines changed

src/sentry/api/endpoints/organization_dashboards.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,25 @@ def has_object_permission(self, request: Request, view, obj):
5050
return super().has_object_permission(request, view, obj)
5151

5252
if isinstance(obj, Dashboard):
53-
for project in obj.projects.all():
54-
if not request.access.has_project_access(project):
55-
return False
53+
# 1. Dashboard contains certain projects
54+
if obj.projects.exists():
55+
return request.access.has_projects_access(obj.projects.all())
56+
57+
# 2. Dashboard covers all projects or all my projects
58+
59+
# allow when Open Membership
60+
if obj.organization.flags.allow_joinleave:
61+
return True
62+
63+
# allow for Managers and Owners
64+
if request.access.has_scope("org:write"):
65+
return True
66+
67+
# allow for creator
68+
if request.user.id == obj.created_by_id:
69+
return True
70+
71+
return False
5672

5773
return True
5874

tests/sentry/api/endpoints/test_organization_dashboard_details.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,65 @@ def test_disallow_delete_when_no_project_access(self):
384384
assert response.status_code == 403
385385
assert response.data == {"detail": "You do not have permission to perform this action."}
386386

387+
def test_disallow_delete_all_projects_dashboard_when_no_open_membership(self):
388+
# disable Open Membership
389+
self.organization.flags.allow_joinleave = False
390+
self.organization.save()
391+
392+
dashboard = Dashboard.objects.create(
393+
title="Dashboard For All Projects",
394+
created_by_id=self.user.id,
395+
organization=self.organization,
396+
filters={"all_projects": True},
397+
)
398+
399+
# user has no access to all the projects
400+
user_no_team = self.create_user(is_superuser=False)
401+
self.create_member(
402+
user=user_no_team, organization=self.organization, role="member", teams=[]
403+
)
404+
self.login_as(user_no_team)
405+
406+
response = self.do_request("delete", self.url(dashboard.id))
407+
assert response.status_code == 403
408+
assert response.data == {"detail": "You do not have permission to perform this action."}
409+
410+
# owner is allowed to delete
411+
self.owner = self.create_member(
412+
user=self.create_user(), organization=self.organization, role="owner"
413+
)
414+
self.login_as(self.owner)
415+
response = self.do_request("delete", self.url(dashboard.id))
416+
assert response.status_code == 204
417+
418+
def test_disallow_delete_my_projects_dashboard_when_no_open_membership(self):
419+
# disable Open Membership
420+
self.organization.flags.allow_joinleave = False
421+
self.organization.save()
422+
423+
dashboard = Dashboard.objects.create(
424+
title="Dashboard For My Projects",
425+
created_by_id=self.user.id,
426+
organization=self.organization,
427+
# no 'filter' field means the dashboard covers all available projects
428+
)
429+
430+
# user has no access to all the projects
431+
user_no_team = self.create_user(is_superuser=False)
432+
self.create_member(
433+
user=user_no_team, organization=self.organization, role="member", teams=[]
434+
)
435+
self.login_as(user_no_team)
436+
437+
response = self.do_request("delete", self.url(dashboard.id))
438+
assert response.status_code == 403
439+
assert response.data == {"detail": "You do not have permission to perform this action."}
440+
441+
# creator is allowed to delete
442+
self.login_as(self.user)
443+
response = self.do_request("delete", self.url(dashboard.id))
444+
assert response.status_code == 204
445+
387446
def test_dashboard_does_not_exist(self):
388447
response = self.do_request("delete", self.url(1234567890))
389448
assert response.status_code == 404

0 commit comments

Comments
 (0)