Skip to content

Commit d766d0c

Browse files
daviianlunny
authored andcommitted
Prevent sending emails and notifications to inactive users (#2384)
* Filter inactive users before sending emails or creating browser notifications Signed-off-by: David Schneiderbauer <[email protected]> * fix formatting issues Signed-off-by: David Schneiderbauer <[email protected]> * included requested changes Signed-off-by: David Schneiderbauer <[email protected]> * optimized database queries * rebasing new master and add tablenames for clarification in xorm queries * remove escaped quotationmarks using backticks Signed-off-by: David Schneiderbauer <[email protected]>
1 parent b496e3e commit d766d0c

13 files changed

+85
-21
lines changed

models/fixtures/repository.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
num_pulls: 2
1010
num_closed_pulls: 0
1111
num_milestones: 2
12-
num_watches: 2
12+
num_watches: 3
1313

1414
-
1515
id: 2

models/fixtures/watch.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,8 @@
77
id: 2
88
user_id: 4
99
repo_id: 1
10+
11+
-
12+
id: 3
13+
user_id: 10
14+
repo_id: 1

models/issue.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1204,8 +1204,11 @@ func GetParticipantsByIssueID(issueID int64) ([]*User, error) {
12041204
func getParticipantsByIssueID(e Engine, issueID int64) ([]*User, error) {
12051205
userIDs := make([]int64, 0, 5)
12061206
if err := e.Table("comment").Cols("poster_id").
1207-
Where("issue_id = ?", issueID).
1208-
And("type = ?", CommentTypeComment).
1207+
Where("`comment`.issue_id = ?", issueID).
1208+
And("`comment`.type = ?", CommentTypeComment).
1209+
And("`user`.is_active = ?", true).
1210+
And("`user`.prohibit_login = ?", false).
1211+
Join("INNER", "user", "`user`.id = `comment`.poster_id").
12091212
Distinct("poster_id").
12101213
Find(&userIDs); err != nil {
12111214
return nil, fmt.Errorf("get poster IDs: %v", err)

models/issue_mail.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,13 @@ func mailIssueCommentToParticipants(e Engine, issue *Issue, doer *User, comment
3636
return fmt.Errorf("getParticipantsByIssueID [issue_id: %d]: %v", issue.ID, err)
3737
}
3838

39-
// In case the issue poster is not watching the repository,
39+
// In case the issue poster is not watching the repository and is active,
4040
// even if we have duplicated in watchers, can be safely filtered out.
41-
if issue.PosterID != doer.ID {
41+
poster, err := GetUserByID(issue.PosterID)
42+
if err != nil {
43+
return fmt.Errorf("GetUserByID [%d]: %v", issue.PosterID, err)
44+
}
45+
if issue.PosterID != doer.ID && poster.IsActive && !poster.ProhibitLogin {
4246
participants = append(participants, issue.Poster)
4347
}
4448

models/issue_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,8 @@ func TestGetParticipantsByIssueID(t *testing.T) {
8080
// User 1 is issue1 poster (see fixtures/issue.yml)
8181
// User 2 only labeled issue1 (see fixtures/comment.yml)
8282
// Users 3 and 5 made actual comments (see fixtures/comment.yml)
83-
checkParticipants(1, []int{3, 5})
83+
// User 3 is inactive, thus not active participant
84+
checkParticipants(1, []int{5})
8485
}
8586

8687
func TestIssue_AddLabel(t *testing.T) {

models/issue_watch.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,10 @@ func GetIssueWatchers(issueID int64) ([]*IssueWatch, error) {
9090

9191
func getIssueWatchers(e Engine, issueID int64) (watches []*IssueWatch, err error) {
9292
err = e.
93-
Where("issue_id = ?", issueID).
93+
Where("`issue_watch`.issue_id = ?", issueID).
94+
And("`user`.is_active = ?", true).
95+
And("`user`.prohibit_login = ?", false).
96+
Join("INNER", "user", "`user`.id = `issue_watch`.user_id").
9497
Find(&watches)
9598
return
9699
}

models/issue_watch_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ func TestGetIssueWatchers(t *testing.T) {
4343

4444
iws, err := GetIssueWatchers(1)
4545
assert.NoError(t, err)
46+
// Watcher is inactive, thus 0
47+
assert.Equal(t, 0, len(iws))
48+
49+
iws, err = GetIssueWatchers(2)
50+
assert.NoError(t, err)
4651
assert.Equal(t, 1, len(iws))
4752

4853
iws, err = GetIssueWatchers(5)

models/migrations/migrations.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@ var migrations = []Migration{
130130
NewMigration("adds time tracking and stopwatches", addTimetracking),
131131
// v40 -> v41
132132
NewMigration("migrate protected branch struct", migrateProtectedBranchStruct),
133+
// v41 -> v42
134+
NewMigration("add default value to user prohibit_login", addDefaultValueToUserProhibitLogin),
133135
}
134136

135137
// Migrate database to current version

models/migrations/v41.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Copyright 2017 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package migrations
6+
7+
import (
8+
"fmt"
9+
10+
"code.gitea.io/gitea/models"
11+
12+
"github.com/go-xorm/xorm"
13+
)
14+
15+
func addDefaultValueToUserProhibitLogin(x *xorm.Engine) (err error) {
16+
user := &models.User{
17+
ProhibitLogin: false,
18+
}
19+
20+
if _, err := x.Where("`prohibit_login` IS NULL").Cols("prohibit_login").Update(user); err != nil {
21+
return err
22+
}
23+
24+
dialect := x.Dialect().DriverName()
25+
26+
switch dialect {
27+
case "mysql":
28+
_, err = x.Exec("ALTER TABLE user MODIFY `prohibit_login` tinyint(1) NOT NULL DEFAULT 0")
29+
case "postgres":
30+
_, err = x.Exec("ALTER TABLE \"user\" ALTER COLUMN `prohibit_login` SET NOT NULL, ALTER COLUMN `prohibit_login` SET DEFAULT false")
31+
case "mssql":
32+
// xorm already set DEFAULT 0 for data type BIT in mssql
33+
_, err = x.Exec(`ALTER TABLE [user] ALTER COLUMN "prohibit_login" BIT NOT NULL`)
34+
case "sqlite3":
35+
}
36+
37+
if err != nil {
38+
return fmt.Errorf("Error changing user prohibit_login column definition: %v", err)
39+
}
40+
41+
return err
42+
}

models/notification_test.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,8 @@ func TestCreateOrUpdateIssueNotifications(t *testing.T) {
1616

1717
assert.NoError(t, CreateOrUpdateIssueNotifications(issue, 2))
1818

19-
notf := AssertExistsAndLoadBean(t, &Notification{UserID: 1, IssueID: issue.ID}).(*Notification)
20-
assert.Equal(t, NotificationStatusUnread, notf.Status)
21-
notf = AssertExistsAndLoadBean(t, &Notification{UserID: 4, IssueID: issue.ID}).(*Notification)
19+
// Two watchers are inactive, thus only notification for user 10 is created
20+
notf := AssertExistsAndLoadBean(t, &Notification{UserID: 10, IssueID: issue.ID}).(*Notification)
2221
assert.Equal(t, NotificationStatusUnread, notf.Status)
2322
CheckConsistencyFor(t, &Issue{ID: issue.ID})
2423
}

models/repo_watch.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,11 @@ func WatchRepo(userID, repoID int64, watch bool) (err error) {
5151

5252
func getWatchers(e Engine, repoID int64) ([]*Watch, error) {
5353
watches := make([]*Watch, 0, 10)
54-
return watches, e.Find(&watches, &Watch{RepoID: repoID})
54+
return watches, e.Where("`watch`.repo_id=?", repoID).
55+
And("`user`.is_active=?", true).
56+
And("`user`.prohibit_login=?", false).
57+
Join("INNER", "user", "`user`.id = `watch`.user_id").
58+
Find(&watches)
5559
}
5660

5761
// GetWatchers returns all watchers of given repository.

models/repo_watch_test.go

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ func TestGetWatchers(t *testing.T) {
4040
repo := AssertExistsAndLoadBean(t, &Repository{ID: 1}).(*Repository)
4141
watches, err := GetWatchers(repo.ID)
4242
assert.NoError(t, err)
43-
assert.Len(t, watches, repo.NumWatches)
43+
// Two watchers are inactive, thus minus 2
44+
assert.Len(t, watches, repo.NumWatches-2)
4445
for _, watch := range watches {
4546
assert.EqualValues(t, repo.ID, watch.RepoID)
4647
}
@@ -77,21 +78,16 @@ func TestNotifyWatchers(t *testing.T) {
7778
}
7879
assert.NoError(t, NotifyWatchers(action))
7980

81+
// Two watchers are inactive, thus action is only created for user 8, 10
8082
AssertExistsAndLoadBean(t, &Action{
8183
ActUserID: action.ActUserID,
82-
UserID: 1,
83-
RepoID: action.RepoID,
84-
OpType: action.OpType,
85-
})
86-
AssertExistsAndLoadBean(t, &Action{
87-
ActUserID: action.ActUserID,
88-
UserID: 4,
84+
UserID: 8,
8985
RepoID: action.RepoID,
9086
OpType: action.OpType,
9187
})
9288
AssertExistsAndLoadBean(t, &Action{
9389
ActUserID: action.ActUserID,
94-
UserID: 8,
90+
UserID: 10,
9591
RepoID: action.RepoID,
9692
OpType: action.OpType,
9793
})

models/user.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ type User struct {
111111
AllowGitHook bool
112112
AllowImportLocal bool // Allow migrate repository by local path
113113
AllowCreateOrganization bool `xorm:"DEFAULT true"`
114-
ProhibitLogin bool
114+
ProhibitLogin bool `xorm:"NOT NULL DEFAULT false"`
115115

116116
// Avatar
117117
Avatar string `xorm:"VARCHAR(2048) NOT NULL"`

0 commit comments

Comments
 (0)