diff --git a/pkg/integrations/project_test.go b/pkg/integrations/project_test.go index e2bcfef9e37..963d5021cda 100644 --- a/pkg/integrations/project_test.go +++ b/pkg/integrations/project_test.go @@ -43,7 +43,6 @@ func TestProject(t *testing.T) { assert.Contains(t, rec.Body.String(), `Test3`) // Shared directly via users_project assert.Contains(t, rec.Body.String(), `Test12`) // Shared via parent project assert.NotContains(t, rec.Body.String(), `Test5`) - assert.NotContains(t, rec.Body.String(), `Test21`) // Archived through parent project assert.NotContains(t, rec.Body.String(), `Test22`) // Archived directly }) t.Run("Search", func(t *testing.T) { diff --git a/pkg/integrations/task_collection_test.go b/pkg/integrations/task_collection_test.go index 5dfe4fc3bb3..75d5f209b34 100644 --- a/pkg/integrations/task_collection_test.go +++ b/pkg/integrations/task_collection_test.go @@ -366,7 +366,7 @@ func TestTaskCollection(t *testing.T) { t.Run("by priority", func(t *testing.T) { rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}}, nil) assert.NoError(t, err) - assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`) + assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":35,"title":"task #35","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":21,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":[{"id":2,"name":"","username":"user2","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}],"labels":[{"id":4,"title":"Label #4 - visible via other task","description":"","hex_color":"","created_by":{"id":2,"name":"","username":"user2","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"},"created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}],"hex_color":"","percent_done":0,"identifier":"test21-1","index":1,"related_tasks":{"related":[{"id":1,"title":"task #1","description":"Lorem Ipsum","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"","index":1,"related_tasks":null,"attachments":null,"cover_image_attachment_id":0,"is_favorite":true,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":2,"kanban_position":0,"created_by":null},{"id":1,"title":"task #1","description":"Lorem Ipsum","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"","index":1,"related_tasks":null,"attachments":null,"cover_image_attachment_id":0,"is_favorite":true,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":2,"kanban_position":0,"created_by":null}]},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":19,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`) }) t.Run("by priority desc", func(t *testing.T) { rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"desc"}}, nil) @@ -376,7 +376,7 @@ func TestTaskCollection(t *testing.T) { t.Run("by priority asc", func(t *testing.T) { rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"asc"}}, nil) assert.NoError(t, err) - assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`) + assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":35,"title":"task #35","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":21,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":[{"id":2,"name":"","username":"user2","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}],"labels":[{"id":4,"title":"Label #4 - visible via other task","description":"","hex_color":"","created_by":{"id":2,"name":"","username":"user2","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"},"created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}],"hex_color":"","percent_done":0,"identifier":"test21-1","index":1,"related_tasks":{"related":[{"id":1,"title":"task #1","description":"Lorem Ipsum","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"","index":1,"related_tasks":null,"attachments":null,"cover_image_attachment_id":0,"is_favorite":true,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":2,"kanban_position":0,"created_by":null},{"id":1,"title":"task #1","description":"Lorem Ipsum","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"","index":1,"related_tasks":null,"attachments":null,"cover_image_attachment_id":0,"is_favorite":true,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":2,"kanban_position":0,"created_by":null}]},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":19,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`) }) // should equal duedate asc t.Run("by due_date", func(t *testing.T) { diff --git a/pkg/models/project.go b/pkg/models/project.go index 4fc64883088..97e4183980b 100644 --- a/pkg/models/project.go +++ b/pkg/models/project.go @@ -366,6 +366,14 @@ func getUserProjectsStatement(parentProjectIDs []int64, userID int64, search str parentCondition = builder.Or( builder.IsNull{"l.parent_project_id"}, builder.Eq{"l.parent_project_id": 0}, + // else check for shared sub projects with a parent + builder.And( + builder.Or( + builder.NotNull{"tm2.user_id"}, + builder.NotNull{"ul.user_id"}, + ), + builder.NotNull{"l.parent_project_id"}, + ), ) projectCol := "id" if len(parentProjectIDs) > 0 { @@ -419,8 +427,18 @@ func getAllProjectsForUser(s *xorm.Session, userID int64, parentProjectIDs []int return 0, 0, err } + parentIDsMap := make(map[int64]bool, len(parentProjectIDs)) + for _, id := range parentProjectIDs { + parentIDsMap[id] = true + } + newParentIDs := []int64{} for _, project := range currentProjects { + // Filter out parent project ids which we're not looking for to avoid leaking + // information about parent projects + if !parentIDsMap[project.ParentProjectID] { + project.ParentProjectID = 0 + } newParentIDs = append(newParentIDs, project.ID) } diff --git a/pkg/models/project_test.go b/pkg/models/project_test.go index c0efd95851e..ba796fac04c 100644 --- a/pkg/models/project_test.go +++ b/pkg/models/project_test.go @@ -345,7 +345,7 @@ func TestProject_ReadAll(t *testing.T) { projects := []*Project{} _, _, err := getAllProjectsForUser(s, 1, nil, &projectOptions{}, &projects, 0) assert.NoError(t, err) - assert.Equal(t, 23, len(projects)) + assert.Equal(t, 25, len(projects)) _ = s.Close() }) t.Run("only child projects for one project", func(t *testing.T) { @@ -361,12 +361,12 @@ func TestProject_ReadAll(t *testing.T) { assert.NoError(t, err) assert.Equal(t, reflect.TypeOf(projects3).Kind(), reflect.Slice) ls := projects3.([]*Project) - assert.Equal(t, 25, len(ls)) + assert.Equal(t, 27, len(ls)) assert.Equal(t, int64(3), ls[0].ID) // Project 3 has a position of 1 and should be sorted first assert.Equal(t, int64(1), ls[1].ID) assert.Equal(t, int64(6), ls[2].ID) - assert.Equal(t, int64(-1), ls[23].ID) - assert.Equal(t, int64(-2), ls[24].ID) + assert.Equal(t, int64(-1), ls[25].ID) + assert.Equal(t, int64(-2), ls[26].ID) _ = s.Close() }) t.Run("projects for nonexistant user", func(t *testing.T) { diff --git a/pkg/models/task_collection_test.go b/pkg/models/task_collection_test.go index e89da00676a..5da5631c7d1 100644 --- a/pkg/models/task_collection_test.go +++ b/pkg/models/task_collection_test.go @@ -608,6 +608,54 @@ func TestTaskCollection_ReadAll(t *testing.T) { Created: time.Unix(1543626724, 0).In(loc), Updated: time.Unix(1543626724, 0).In(loc), } + task35 := &Task{ + ID: 35, + Title: "task #35", + Identifier: "test21-1", + Index: 1, + CreatedByID: 1, + CreatedBy: user1, + ProjectID: 21, + Assignees: []*user.User{ + user2, + }, + Labels: []*Label{ + label4, + }, + RelatedTasks: map[RelationKind][]*Task{ + RelationKindRelated: { + { + ID: 1, + Title: "task #1", + Description: "Lorem Ipsum", + Index: 1, + CreatedByID: 1, + ProjectID: 1, + IsFavorite: true, + Created: time.Unix(1543626724, 0).In(loc), + Updated: time.Unix(1543626724, 0).In(loc), + BucketID: 1, + Position: 2, + }, + { + ID: 1, + Title: "task #1", + Description: "Lorem Ipsum", + Index: 1, + CreatedByID: 1, + ProjectID: 1, + IsFavorite: true, + Created: time.Unix(1543626724, 0).In(loc), + Updated: time.Unix(1543626724, 0).In(loc), + BucketID: 1, + Position: 2, + }, + }, + }, + BucketID: 19, + Created: time.Unix(1543626724, 0).In(loc), + Updated: time.Unix(1543626724, 0).In(loc), + } type fields struct { ProjectID int64 @@ -679,6 +727,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { task31, task32, task33, + task35, }, wantErr: false, }, @@ -691,6 +740,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { }, args: defaultArgs, want: []*Task{ + task35, task33, task32, task31, @@ -821,6 +871,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { task31, task32, task33, + task35, }, wantErr: false, }, @@ -891,6 +942,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { task31, // has nil dates task32, // has nil dates task33, // has nil dates + task35, // has nil dates }, wantErr: false, }, @@ -1000,6 +1052,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { args: defaultArgs, want: []*Task{ task30, + task35, }, wantErr: false, }, @@ -1046,6 +1099,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { args: defaultArgs, want: []*Task{ task30, + task35, }, wantErr: false, }, @@ -1060,6 +1114,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { want: []*Task{ task1, task2, + task35, }, wantErr: false, }, @@ -1146,6 +1201,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { task31, task32, task33, + task35, }, }, { @@ -1162,6 +1218,7 @@ func TestTaskCollection_ReadAll(t *testing.T) { task6, task5, // The other ones don't have a due date + task35, task33, task32, task31,