Skip to content

Commit d93d5d7

Browse files
guillep2kzeripath
authored andcommitted
Backport: Ignore mentions for users with no access (#8395) (#8484)
* Ignore mentions for users with no access * Fix fmt
1 parent 5c3863c commit d93d5d7

File tree

5 files changed

+175
-40
lines changed

5 files changed

+175
-40
lines changed

models/issue.go

Lines changed: 122 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1462,46 +1462,18 @@ func getParticipantsByIssueID(e Engine, issueID int64) ([]*User, error) {
14621462
return users, e.In("id", userIDs).Find(&users)
14631463
}
14641464

1465-
// UpdateIssueMentions extracts mentioned people from content and
1466-
// updates issue-user relations for them.
1467-
func UpdateIssueMentions(e Engine, issueID int64, mentions []string) error {
1465+
// UpdateIssueMentions updates issue-user relations for mentioned users.
1466+
func UpdateIssueMentions(e Engine, issueID int64, mentions []*User) error {
14681467
if len(mentions) == 0 {
14691468
return nil
14701469
}
1471-
1472-
for i := range mentions {
1473-
mentions[i] = strings.ToLower(mentions[i])
1474-
}
1475-
users := make([]*User, 0, len(mentions))
1476-
1477-
if err := e.In("lower_name", mentions).Asc("lower_name").Find(&users); err != nil {
1478-
return fmt.Errorf("find mentioned users: %v", err)
1470+
ids := make([]int64, len(mentions))
1471+
for i, u := range mentions {
1472+
ids[i] = u.ID
14791473
}
1480-
1481-
ids := make([]int64, 0, len(mentions))
1482-
for _, user := range users {
1483-
ids = append(ids, user.ID)
1484-
if !user.IsOrganization() || user.NumMembers == 0 {
1485-
continue
1486-
}
1487-
1488-
memberIDs := make([]int64, 0, user.NumMembers)
1489-
orgUsers, err := getOrgUsersByOrgID(e, user.ID)
1490-
if err != nil {
1491-
return fmt.Errorf("GetOrgUsersByOrgID [%d]: %v", user.ID, err)
1492-
}
1493-
1494-
for _, orgUser := range orgUsers {
1495-
memberIDs = append(memberIDs, orgUser.ID)
1496-
}
1497-
1498-
ids = append(ids, memberIDs...)
1499-
}
1500-
15011474
if err := UpdateIssueUsersByMentions(e, issueID, ids); err != nil {
15021475
return fmt.Errorf("UpdateIssueUsersByMentions: %v", err)
15031476
}
1504-
15051477
return nil
15061478
}
15071479

@@ -1854,3 +1826,120 @@ func (issue *Issue) updateClosedNum(e Engine) (err error) {
18541826
}
18551827
return
18561828
}
1829+
1830+
// ResolveMentionsByVisibility returns the users mentioned in an issue, removing those that
1831+
// don't have access to reading it. Teams are expanded into their users, but organizations are ignored.
1832+
func (issue *Issue) ResolveMentionsByVisibility(e Engine, doer *User, mentions []string) (users []*User, err error) {
1833+
if len(mentions) == 0 {
1834+
return
1835+
}
1836+
if err = issue.loadRepo(e); err != nil {
1837+
return
1838+
}
1839+
resolved := make(map[string]bool, 20)
1840+
names := make([]string, 0, 20)
1841+
resolved[doer.LowerName] = true
1842+
for _, name := range mentions {
1843+
name := strings.ToLower(name)
1844+
if _, ok := resolved[name]; ok {
1845+
continue
1846+
}
1847+
resolved[name] = false
1848+
names = append(names, name)
1849+
}
1850+
1851+
if err := issue.Repo.getOwner(e); err != nil {
1852+
return nil, err
1853+
}
1854+
1855+
if issue.Repo.Owner.IsOrganization() {
1856+
// Since there can be users with names that match the name of a team,
1857+
// if the team exists and can read the issue, the team takes precedence.
1858+
teams := make([]*Team, 0, len(names))
1859+
if err := e.
1860+
Join("INNER", "team_repo", "team_repo.team_id = team.id").
1861+
Where("team_repo.repo_id=?", issue.Repo.ID).
1862+
In("team.lower_name", names).
1863+
Find(&teams); err != nil {
1864+
return nil, fmt.Errorf("find mentioned teams: %v", err)
1865+
}
1866+
if len(teams) != 0 {
1867+
checked := make([]int64, 0, len(teams))
1868+
unittype := UnitTypeIssues
1869+
if issue.IsPull {
1870+
unittype = UnitTypePullRequests
1871+
}
1872+
for _, team := range teams {
1873+
if team.Authorize >= AccessModeOwner {
1874+
checked = append(checked, team.ID)
1875+
resolved[team.LowerName] = true
1876+
continue
1877+
}
1878+
has, err := e.Get(&TeamUnit{OrgID: issue.Repo.Owner.ID, TeamID: team.ID, Type: unittype})
1879+
if err != nil {
1880+
return nil, fmt.Errorf("get team units (%d): %v", team.ID, err)
1881+
}
1882+
if has {
1883+
checked = append(checked, team.ID)
1884+
resolved[team.LowerName] = true
1885+
}
1886+
}
1887+
if len(checked) != 0 {
1888+
teamusers := make([]*User, 0, 20)
1889+
if err := e.
1890+
Join("INNER", "team_user", "team_user.uid = `user`.id").
1891+
In("`team_user`.team_id", checked).
1892+
And("`user`.is_active = ?", true).
1893+
And("`user`.prohibit_login = ?", false).
1894+
Find(&teamusers); err != nil {
1895+
return nil, fmt.Errorf("get teams users: %v", err)
1896+
}
1897+
if len(teamusers) > 0 {
1898+
users = make([]*User, 0, len(teamusers))
1899+
for _, user := range teamusers {
1900+
if already, ok := resolved[user.LowerName]; !ok || !already {
1901+
users = append(users, user)
1902+
resolved[user.LowerName] = true
1903+
}
1904+
}
1905+
}
1906+
}
1907+
}
1908+
1909+
// Remove names already in the list to avoid querying the database if pending names remain
1910+
names = make([]string, 0, len(resolved))
1911+
for name, already := range resolved {
1912+
if !already {
1913+
names = append(names, name)
1914+
}
1915+
}
1916+
if len(names) == 0 {
1917+
return
1918+
}
1919+
}
1920+
1921+
unchecked := make([]*User, 0, len(names))
1922+
if err := e.
1923+
Where("`user`.is_active = ?", true).
1924+
And("`user`.prohibit_login = ?", false).
1925+
In("`user`.lower_name", names).
1926+
Find(&unchecked); err != nil {
1927+
return nil, fmt.Errorf("find mentioned users: %v", err)
1928+
}
1929+
for _, user := range unchecked {
1930+
if already := resolved[user.LowerName]; already || user.IsOrganization() {
1931+
continue
1932+
}
1933+
// Normal users must have read access to the referencing issue
1934+
perm, err := getUserRepoPermission(e, issue.Repo, user)
1935+
if err != nil {
1936+
return nil, fmt.Errorf("getUserRepoPermission [%d]: %v", user.ID, err)
1937+
}
1938+
if !perm.CanReadIssuesOrPulls(issue.IsPull) {
1939+
continue
1940+
}
1941+
users = append(users, user)
1942+
}
1943+
1944+
return
1945+
}

models/issue_comment.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -387,11 +387,18 @@ func (c *Comment) MailParticipants(opType ActionType, issue *Issue) (err error)
387387
}
388388

389389
func (c *Comment) mailParticipants(e Engine, opType ActionType, issue *Issue) (err error) {
390-
mentions := markup.FindAllMentions(c.Content)
391-
if err = UpdateIssueMentions(e, c.IssueID, mentions); err != nil {
390+
rawMentions := markup.FindAllMentions(c.Content)
391+
userMentions, err := issue.ResolveMentionsByVisibility(e, c.Poster, rawMentions)
392+
if err != nil {
393+
return fmt.Errorf("ResolveMentionsByVisibility [%d]: %v", c.IssueID, err)
394+
}
395+
if err = UpdateIssueMentions(e, c.IssueID, userMentions); err != nil {
392396
return fmt.Errorf("UpdateIssueMentions [%d]: %v", c.IssueID, err)
393397
}
394-
398+
mentions := make([]string, len(userMentions))
399+
for i, u := range userMentions {
400+
mentions[i] = u.LowerName
401+
}
395402
if len(c.Content) > 0 {
396403
if err = mailIssueCommentToParticipants(e, issue, c.Poster, c.Content, c, mentions); err != nil {
397404
log.Error("mailIssueCommentToParticipants: %v", err)

models/issue_mail.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,11 +123,18 @@ func (issue *Issue) MailParticipants(doer *User, opType ActionType) (err error)
123123
}
124124

125125
func (issue *Issue) mailParticipants(e Engine, doer *User, opType ActionType) (err error) {
126-
mentions := markup.FindAllMentions(issue.Content)
127-
128-
if err = UpdateIssueMentions(e, issue.ID, mentions); err != nil {
126+
rawMentions := markup.FindAllMentions(issue.Content)
127+
userMentions, err := issue.ResolveMentionsByVisibility(e, doer, rawMentions)
128+
if err != nil {
129+
return fmt.Errorf("ResolveMentionsByVisibility [%d]: %v", issue.ID, err)
130+
}
131+
if err = UpdateIssueMentions(e, issue.ID, userMentions); err != nil {
129132
return fmt.Errorf("UpdateIssueMentions [%d]: %v", issue.ID, err)
130133
}
134+
mentions := make([]string, len(userMentions))
135+
for i, u := range userMentions {
136+
mentions[i] = u.LowerName
137+
}
131138

132139
if len(issue.Content) > 0 {
133140
if err = mailIssueCommentToParticipants(e, issue, doer, issue.Content, nil, mentions); err != nil {

models/issue_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,3 +320,35 @@ func TestIssue_SearchIssueIDsByKeyword(t *testing.T) {
320320
assert.EqualValues(t, 1, total)
321321
assert.EqualValues(t, []int64{1}, ids)
322322
}
323+
324+
func TestIssue_ResolveMentions(t *testing.T) {
325+
assert.NoError(t, PrepareTestDatabase())
326+
327+
testSuccess := func(owner, repo, doer string, mentions []string, expected []int64) {
328+
o := AssertExistsAndLoadBean(t, &User{LowerName: owner}).(*User)
329+
r := AssertExistsAndLoadBean(t, &Repository{OwnerID: o.ID, LowerName: repo}).(*Repository)
330+
issue := &Issue{RepoID: r.ID}
331+
d := AssertExistsAndLoadBean(t, &User{LowerName: doer}).(*User)
332+
resolved, err := issue.ResolveMentionsByVisibility(x, d, mentions)
333+
assert.NoError(t, err)
334+
ids := make([]int64, len(resolved))
335+
for i, user := range resolved {
336+
ids[i] = user.ID
337+
}
338+
sort.Slice(ids, func(i, j int) bool { return ids[i] < ids[j] })
339+
assert.EqualValues(t, expected, ids)
340+
}
341+
342+
// Public repo, existing user
343+
testSuccess("user2", "repo1", "user1", []string{"user5"}, []int64{5})
344+
// Public repo, non-existing user
345+
testSuccess("user2", "repo1", "user1", []string{"nonexisting"}, []int64{})
346+
// Public repo, doer
347+
testSuccess("user2", "repo1", "user1", []string{"user1"}, []int64{})
348+
// Private repo, team member
349+
testSuccess("user17", "big_test_private_4", "user20", []string{"user2"}, []int64{2})
350+
// Private repo, not a team member
351+
testSuccess("user17", "big_test_private_4", "user20", []string{"user5"}, []int64{})
352+
// Private repo, whole team
353+
testSuccess("user17", "big_test_private_4", "user15", []string{"owners"}, []int64{18})
354+
}

models/org_team.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ func (t *Team) UnitEnabled(tp UnitType) bool {
252252

253253
func (t *Team) unitEnabled(e Engine, tp UnitType) bool {
254254
if err := t.getUnits(e); err != nil {
255-
log.Warn("Error loading repository (ID: %d) units: %s", t.ID, err.Error())
255+
log.Warn("Error loading team (ID: %d) units: %s", t.ID, err.Error())
256256
}
257257

258258
for _, unit := range t.Units {

0 commit comments

Comments
 (0)