Skip to content

Commit 95ea2df

Browse files
authored
Add more check for stopwatch read or list (go-gitea#36340)
1 parent ed5720a commit 95ea2df

9 files changed

Lines changed: 160 additions & 7 deletions

File tree

models/issues/stopwatch.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import (
1212
user_model "code.gitea.io/gitea/models/user"
1313
"code.gitea.io/gitea/modules/timeutil"
1414
"code.gitea.io/gitea/modules/util"
15+
16+
"xorm.io/builder"
1517
)
1618

1719
// Stopwatch represents a stopwatch for time tracking.
@@ -232,3 +234,14 @@ func CancelStopwatch(ctx context.Context, user *user_model.User, issue *Issue) (
232234
})
233235
return ok, err
234236
}
237+
238+
// RemoveStopwatchesByRepoID removes all stopwatches for a user in a specific repository
239+
// this function should be called before removing all the issues of the repository
240+
func RemoveStopwatchesByRepoID(ctx context.Context, userID, repoID int64) error {
241+
_, err := db.GetEngine(ctx).
242+
Where("`stopwatch`.user_id = ?", userID).
243+
And(builder.In("`stopwatch`.issue_id",
244+
builder.Select("id").From("issue").Where(builder.Eq{"repo_id": repoID}))).
245+
Delete(new(Stopwatch))
246+
return err
247+
}

modules/eventsource/manager_run.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99

1010
activities_model "code.gitea.io/gitea/models/activities"
1111
issues_model "code.gitea.io/gitea/models/issues"
12+
user_model "code.gitea.io/gitea/models/user"
1213
"code.gitea.io/gitea/modules/graceful"
1314
"code.gitea.io/gitea/modules/json"
1415
"code.gitea.io/gitea/modules/log"
@@ -91,7 +92,13 @@ loop:
9192
}
9293

9394
for _, userStopwatches := range usersStopwatches {
94-
apiSWs, err := convert.ToStopWatches(ctx, userStopwatches.StopWatches)
95+
u, err := user_model.GetUserByID(ctx, userStopwatches.UserID)
96+
if err != nil {
97+
log.Error("Unable to get user %d: %v", userStopwatches.UserID, err)
98+
continue
99+
}
100+
101+
apiSWs, err := convert.ToStopWatches(ctx, u, userStopwatches.StopWatches)
95102
if err != nil {
96103
if !issues_model.IsErrIssueNotExist(err) {
97104
log.Error("Unable to APIFormat stopwatches: %v", err)

routers/api/v1/repo/issue_stopwatch.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ func GetStopwatches(ctx *context.APIContext) {
224224
return
225225
}
226226

227-
apiSWs, err := convert.ToStopWatches(ctx, sws)
227+
apiSWs, err := convert.ToStopWatches(ctx, ctx.Doer, sws)
228228
if err != nil {
229229
ctx.APIErrorInternal(err)
230230
return

routers/web/user/stop_watch.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ func GetStopwatches(ctx *context.Context) {
2929
return
3030
}
3131

32-
apiSWs, err := convert.ToStopWatches(ctx, sws)
32+
apiSWs, err := convert.ToStopWatches(ctx, ctx.Doer, sws)
3333
if err != nil {
3434
ctx.HTTPError(http.StatusInternalServerError, err.Error())
3535
return

services/convert/issue.go

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"strings"
1111

1212
issues_model "code.gitea.io/gitea/models/issues"
13+
access_model "code.gitea.io/gitea/models/perm/access"
1314
repo_model "code.gitea.io/gitea/models/repo"
1415
user_model "code.gitea.io/gitea/models/user"
1516
"code.gitea.io/gitea/modules/label"
@@ -163,11 +164,12 @@ func ToTrackedTime(ctx context.Context, doer *user_model.User, t *issues_model.T
163164
}
164165

165166
// ToStopWatches convert Stopwatch list to api.StopWatches
166-
func ToStopWatches(ctx context.Context, sws []*issues_model.Stopwatch) (api.StopWatches, error) {
167+
func ToStopWatches(ctx context.Context, doer *user_model.User, sws []*issues_model.Stopwatch) (api.StopWatches, error) {
167168
result := api.StopWatches(make([]api.StopWatch, 0, len(sws)))
168169

169170
issueCache := make(map[int64]*issues_model.Issue)
170171
repoCache := make(map[int64]*repo_model.Repository)
172+
permCache := make(map[int64]access_model.Permission)
171173
var (
172174
issue *issues_model.Issue
173175
repo *repo_model.Repository
@@ -182,13 +184,30 @@ func ToStopWatches(ctx context.Context, sws []*issues_model.Stopwatch) (api.Stop
182184
if err != nil {
183185
return nil, err
184186
}
187+
issueCache[sw.IssueID] = issue
185188
}
186189
repo, ok = repoCache[issue.RepoID]
187190
if !ok {
188191
repo, err = repo_model.GetRepositoryByID(ctx, issue.RepoID)
189192
if err != nil {
190-
return nil, err
193+
log.Error("GetRepositoryByID(%d): %v", issue.RepoID, err)
194+
continue
195+
}
196+
repoCache[issue.RepoID] = repo
197+
}
198+
199+
// ADD: Check user permissions
200+
perm, ok := permCache[repo.ID]
201+
if !ok {
202+
perm, err = access_model.GetUserRepoPermission(ctx, repo, doer)
203+
if err != nil {
204+
continue
191205
}
206+
permCache[repo.ID] = perm
207+
}
208+
209+
if !perm.CanReadIssuesOrPulls(issue.IsPull) {
210+
continue
192211
}
193212

194213
result = append(result, api.StopWatch{

services/convert/issue_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@ import (
88
"testing"
99
"time"
1010

11+
"code.gitea.io/gitea/models/db"
1112
issues_model "code.gitea.io/gitea/models/issues"
1213
repo_model "code.gitea.io/gitea/models/repo"
1314
"code.gitea.io/gitea/models/unittest"
15+
user_model "code.gitea.io/gitea/models/user"
1416
"code.gitea.io/gitea/modules/setting"
1517
api "code.gitea.io/gitea/modules/structs"
1618
"code.gitea.io/gitea/modules/timeutil"
@@ -55,3 +57,29 @@ func TestMilestone_APIFormat(t *testing.T) {
5557
Deadline: milestone.DeadlineUnix.AsTimePtr(),
5658
}, *ToAPIMilestone(milestone))
5759
}
60+
61+
func TestToStopWatchesRespectsPermissions(t *testing.T) {
62+
assert.NoError(t, unittest.PrepareTestDatabase())
63+
64+
ctx := t.Context()
65+
publicSW := unittest.AssertExistsAndLoadBean(t, &issues_model.Stopwatch{ID: 1})
66+
privateIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: 3})
67+
privateSW := &issues_model.Stopwatch{IssueID: privateIssue.ID, UserID: 5}
68+
assert.NoError(t, db.Insert(ctx, privateSW))
69+
assert.NotZero(t, privateSW.ID)
70+
71+
regularUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5})
72+
adminUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
73+
74+
sws := []*issues_model.Stopwatch{publicSW, privateSW}
75+
76+
visible, err := ToStopWatches(ctx, regularUser, sws)
77+
assert.NoError(t, err)
78+
assert.Len(t, visible, 1)
79+
assert.Equal(t, "repo1", visible[0].RepoName)
80+
81+
visibleAdmin, err := ToStopWatches(ctx, adminUser, sws)
82+
assert.NoError(t, err)
83+
assert.Len(t, visibleAdmin, 2)
84+
assert.ElementsMatch(t, []string{"repo1", "repo3"}, []string{visibleAdmin[0].RepoName, visibleAdmin[1].RepoName})
85+
}

services/org/team_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"strings"
99
"testing"
1010

11+
issues_model "code.gitea.io/gitea/models/issues"
1112
"code.gitea.io/gitea/models/organization"
1213
"code.gitea.io/gitea/models/perm"
1314
access_model "code.gitea.io/gitea/models/perm/access"
@@ -62,6 +63,36 @@ func TestTeam_RemoveMember(t *testing.T) {
6263
assert.True(t, organization.IsErrLastOrgOwner(err))
6364
}
6465

66+
func TestRemoveTeamMemberRemovesSubscriptionsAndStopwatches(t *testing.T) {
67+
assert.NoError(t, unittest.PrepareTestDatabase())
68+
69+
ctx := t.Context()
70+
team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 2})
71+
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
72+
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3})
73+
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{RepoID: repo.ID})
74+
75+
assert.NoError(t, repo_model.WatchRepo(ctx, user, repo, true))
76+
assert.NoError(t, issues_model.CreateOrUpdateIssueWatch(ctx, user.ID, issue.ID, true))
77+
ok, err := issues_model.CreateIssueStopwatch(ctx, user, issue)
78+
assert.NoError(t, err)
79+
assert.True(t, ok)
80+
81+
assert.NoError(t, RemoveTeamMember(ctx, team, user))
82+
83+
watch, err := repo_model.GetWatch(ctx, user.ID, repo.ID)
84+
assert.NoError(t, err)
85+
assert.False(t, repo_model.IsWatchMode(watch.Mode))
86+
87+
_, exists, err := issues_model.GetIssueWatch(ctx, user.ID, issue.ID)
88+
assert.NoError(t, err)
89+
assert.False(t, exists)
90+
91+
hasStopwatch, _, _, err := issues_model.HasUserStopwatch(ctx, user.ID)
92+
assert.NoError(t, err)
93+
assert.False(t, hasStopwatch)
94+
}
95+
6596
func TestNewTeam(t *testing.T) {
6697
assert.NoError(t, unittest.PrepareTestDatabase())
6798

services/repository/collaboration.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,11 @@ func ReconsiderWatches(ctx context.Context, repo *repo_model.Repository, user *u
120120
return err
121121
}
122122

123+
// Remove all stopwatches a user has running in the repository
124+
if err := issues_model.RemoveStopwatchesByRepoID(ctx, user.ID, repo.ID); err != nil {
125+
return err
126+
}
127+
123128
// Remove all IssueWatches a user has subscribed to in the repository
124129
return issues_model.RemoveIssueWatchersByRepoID(ctx, user.ID, repo.ID)
125130
}

services/repository/collaboration_test.go

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ package repository
66
import (
77
"testing"
88

9+
"code.gitea.io/gitea/models/db"
10+
issues_model "code.gitea.io/gitea/models/issues"
911
"code.gitea.io/gitea/models/perm"
12+
access_model "code.gitea.io/gitea/models/perm/access"
1013
repo_model "code.gitea.io/gitea/models/repo"
1114
"code.gitea.io/gitea/models/unittest"
1215
user_model "code.gitea.io/gitea/models/user"
@@ -32,8 +35,8 @@ func TestRepository_AddCollaborator(t *testing.T) {
3235
func TestRepository_DeleteCollaboration(t *testing.T) {
3336
assert.NoError(t, unittest.PrepareTestDatabase())
3437

35-
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
36-
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
38+
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 15})
39+
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 22})
3740

3841
assert.NoError(t, repo.LoadOwner(t.Context()))
3942
assert.NoError(t, DeleteCollaboration(t.Context(), repo, user))
@@ -44,3 +47,50 @@ func TestRepository_DeleteCollaboration(t *testing.T) {
4447

4548
unittest.CheckConsistencyFor(t, &repo_model.Repository{ID: repo.ID})
4649
}
50+
51+
func TestRepository_DeleteCollaborationRemovesSubscriptionsAndStopwatches(t *testing.T) {
52+
assert.NoError(t, unittest.PrepareTestDatabase())
53+
54+
ctx := t.Context()
55+
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 15})
56+
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 22})
57+
assert.NoError(t, repo.LoadOwner(ctx))
58+
assert.NoError(t, repo_model.WatchRepo(ctx, user, repo, true))
59+
60+
hasAccess, err := access_model.HasAnyUnitAccess(ctx, user.ID, repo)
61+
assert.NoError(t, err)
62+
assert.True(t, hasAccess)
63+
64+
issueCount, err := db.GetEngine(ctx).Where("repo_id=?", repo.ID).Count(new(issues_model.Issue))
65+
assert.NoError(t, err)
66+
tempIssue := &issues_model.Issue{
67+
RepoID: repo.ID,
68+
Index: issueCount + 1,
69+
PosterID: repo.OwnerID,
70+
Title: "temp issue",
71+
Content: "temp",
72+
}
73+
assert.NoError(t, db.Insert(ctx, tempIssue))
74+
assert.NoError(t, issues_model.CreateOrUpdateIssueWatch(ctx, user.ID, tempIssue.ID, true))
75+
ok, err := issues_model.CreateIssueStopwatch(ctx, user, tempIssue)
76+
assert.NoError(t, err)
77+
assert.True(t, ok)
78+
79+
assert.NoError(t, DeleteCollaboration(ctx, repo, user))
80+
81+
hasAccess, err = access_model.HasAnyUnitAccess(ctx, user.ID, repo)
82+
assert.NoError(t, err)
83+
assert.False(t, hasAccess)
84+
85+
watch, err := repo_model.GetWatch(ctx, user.ID, repo.ID)
86+
assert.NoError(t, err)
87+
assert.False(t, repo_model.IsWatchMode(watch.Mode))
88+
89+
_, exists, err := issues_model.GetIssueWatch(ctx, user.ID, tempIssue.ID)
90+
assert.NoError(t, err)
91+
assert.False(t, exists)
92+
93+
hasStopwatch, _, _, err := issues_model.HasUserStopwatch(ctx, user.ID)
94+
assert.NoError(t, err)
95+
assert.False(t, hasStopwatch)
96+
}

0 commit comments

Comments
 (0)