diff --git a/src/sentry/api/endpoints/debug_files.py b/src/sentry/api/endpoints/debug_files.py index c0ac4f8ba70b6..65cd999c75275 100644 --- a/src/sentry/api/endpoints/debug_files.py +++ b/src/sentry/api/endpoints/debug_files.py @@ -21,6 +21,7 @@ from sentry.api.exceptions import ResourceDoesNotExist from sentry.api.paginator import OffsetPaginator from sentry.api.serializers import serialize +from sentry.auth.access import Access from sentry.auth.superuser import is_active_superuser from sentry.auth.system import is_system_auth from sentry.constants import DEBUG_FILES_ROLE_DEFAULT, KNOWN_DIF_FORMATS @@ -35,6 +36,7 @@ create_files_from_dif_zip, ) from sentry.models.debugfile import ProguardArtifactRelease +from sentry.models.project import Project from sentry.models.release import get_artifact_counts from sentry.tasks.assemble import ( AssembleTask, @@ -87,6 +89,12 @@ def has_download_permission(request, project): return roles.get(current_role).priority >= roles.get(required_role).priority +def _has_delete_permission(access: Access, project: Project) -> bool: + if access.has_scope("project:write"): + return True + return access.has_project_scope(project, "project:write") + + @region_silo_endpoint class ProguardArtifactReleasesEndpoint(ProjectEndpoint): publish_status = { @@ -296,7 +304,7 @@ def on_results(difs: Sequence[ProjectDebugFile]): on_results=on_results, ) - def delete(self, request: Request, project) -> Response: + def delete(self, request: Request, project: Project) -> Response: """ Delete a specific Project's Debug Information File ``````````````````````````````````````````````````` @@ -310,8 +318,7 @@ def delete(self, request: Request, project) -> Response: :qparam string id: The id of the DIF to delete. :auth: required """ - - if request.GET.get("id") and (request.access.has_scope("project:write")): + if request.GET.get("id") and _has_delete_permission(request.access, project): with atomic_transaction(using=router.db_for_write(File)): debug_file = ( ProjectDebugFile.objects.filter(id=request.GET.get("id"), project_id=project.id) diff --git a/tests/sentry/api/endpoints/test_debug_files.py b/tests/sentry/api/endpoints/test_debug_files.py index 7b7a8f9a1ba25..fc7015c748ad9 100644 --- a/tests/sentry/api/endpoints/test_debug_files.py +++ b/tests/sentry/api/endpoints/test_debug_files.py @@ -283,6 +283,77 @@ def test_dsyms_search(self): dsyms = response.data assert len(dsyms) == 20 + def test_dsyms_delete_as_team_admin(self): + project = self.create_project(name="foo") + self.login_as(user=self.user) + + url = reverse( + "sentry-api-0-dsym-files", + kwargs={"organization_slug": project.organization.slug, "project_slug": project.slug}, + ) + response = self._upload_proguard(url, PROGUARD_UUID) + + assert response.status_code == 201 + assert len(response.data) == 1 + + url = reverse( + "sentry-api-0-associate-dsym-files", + kwargs={"organization_slug": project.organization.slug, "project_slug": project.slug}, + ) + response = self.client.post( + url, + { + "checksums": ["e6d3c5185dac63eddfdc1a5edfffa32d46103b44"], + "platform": "android", + "name": "MyApp", + "appId": "com.example.myapp", + "version": "1.0", + "build": "1", + }, + format="json", + ) + + url = reverse( + "sentry-api-0-dsym-files", + kwargs={"organization_slug": project.organization.slug, "project_slug": project.slug}, + ) + response = self.client.get(url) + download_id = response.data[0]["id"] + + assert response.status_code == 200 + + team_admin = self.create_user() + team_admin_without_access = self.create_user() + + self.create_member( + user=team_admin, + organization=project.organization, + role="member", + ) + self.create_member( + user=team_admin_without_access, + organization=project.organization, + role="member", + ) + self.create_team_membership(user=team_admin, team=self.team, role="admin") + self.create_team_membership( + user=team_admin_without_access, team=self.create_team(), role="admin" + ) + + # Team admin without project access can't delete + self.login_as(team_admin_without_access) + response = self.client.delete(url + "?id=" + download_id) + + assert response.status_code == 404, response.content + assert ProjectDebugFile.objects.count() == 1 + + # Team admin with project access can delete + self.login_as(team_admin) + response = self.client.delete(url + "?id=" + download_id) + + assert response.status_code == 204, response.content + assert ProjectDebugFile.objects.count() == 0 + def test_source_maps(self): project = self.create_project(name="foo")