From 738bcd0c77312547e6959eb7d2893a8a0aab1366 Mon Sep 17 00:00:00 2001 From: kolaente Date: Mon, 1 Jun 2026 14:28:53 +0200 Subject: [PATCH] fix(api/v2): scope project view delete to its parent project --- pkg/models/project_view.go | 7 +++++++ pkg/webtests/huma_project_view_test.go | 9 +++++++++ 2 files changed, 16 insertions(+) diff --git a/pkg/models/project_view.go b/pkg/models/project_view.go index 0703539df..50e20756b 100644 --- a/pkg/models/project_view.go +++ b/pkg/models/project_view.go @@ -269,6 +269,13 @@ func (pv *ProjectView) ReadOne(s *xorm.Session, _ web.Auth) (err error) { // @Failure 500 {object} models.Message "Internal error" // @Router /projects/{project}/views/{id} [delete] func (pv *ProjectView) Delete(s *xorm.Session, _ web.Auth) (err error) { + // Resolve the view under the path project first: the buckets/positions below are deleted by + // view id alone, so without this guard a delete scoped to the wrong parent project would still + // wipe another project's buckets and positions while matching zero project_views rows. + if _, err = GetProjectViewByIDAndProject(s, pv.ID, pv.ProjectID); err != nil { + return err + } + _, err = s. Where("id = ? AND project_id = ?", pv.ID, pv.ProjectID). Delete(&ProjectView{}) diff --git a/pkg/webtests/huma_project_view_test.go b/pkg/webtests/huma_project_view_test.go index 0b1537576..10d00d0c8 100644 --- a/pkg/webtests/huma_project_view_test.go +++ b/pkg/webtests/huma_project_view_test.go @@ -143,6 +143,15 @@ func TestProjectView(t *testing.T) { assert.Equal(t, http.StatusNoContent, rec.Code) assert.Empty(t, rec.Body.String()) }) + t.Run("View from another project", func(t *testing.T) { + // view 5 belongs to project 2. testuser1 admins the path project (1), + // so CanDelete passes — but the view isn't under project 1. The Delete + // guard must 404 before touching project 2's buckets/positions, which + // the old code wiped via a project_view_id-only delete. + _, err := owned.testDeleteWithUser(nil, map[string]string{"view": "5"}) + require.Error(t, err) + assert.Equal(t, http.StatusNotFound, getHTTPErrorCode(err)) + }) t.Run("Forbidden", func(t *testing.T) { _, err := forbidden.testDeleteWithUser(nil, map[string]string{"view": "5"}) require.Error(t, err)