Skip to content

Commit 4c924bf

Browse files
authored
Limit org member view of restricted users (#32211)
currently restricted users can only see the repos of teams in orgs they are part at. they also should only see the users that are also part at the same team. --- *Sponsored by Kithara Software GmbH*
1 parent 2763766 commit 4c924bf

File tree

4 files changed

+108
-3
lines changed

4 files changed

+108
-3
lines changed

models/fixtures/org_user.yml

+6
Original file line numberDiff line numberDiff line change
@@ -129,3 +129,9 @@
129129
uid: 2
130130
org_id: 35
131131
is_public: true
132+
133+
-
134+
id: 23
135+
uid: 20
136+
org_id: 17
137+
is_public: false

models/fixtures/user.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -623,7 +623,7 @@
623623
num_stars: 0
624624
num_repos: 2
625625
num_teams: 3
626-
num_members: 4
626+
num_members: 5
627627
visibility: 0
628628
repo_admin_change_team_access: false
629629
theme: ""

models/organization/org.go

+31-2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"code.gitea.io/gitea/modules/util"
2323

2424
"xorm.io/builder"
25+
"xorm.io/xorm"
2526
)
2627

2728
// ________ .__ __ .__
@@ -205,11 +206,28 @@ func (opts FindOrgMembersOpts) PublicOnly() bool {
205206
return opts.Doer == nil || !(opts.IsDoerMember || opts.Doer.IsAdmin)
206207
}
207208

209+
// applyTeamMatesOnlyFilter make sure restricted users only see public team members and there own team mates
210+
func (opts FindOrgMembersOpts) applyTeamMatesOnlyFilter(sess *xorm.Session) {
211+
if opts.Doer != nil && opts.IsDoerMember && opts.Doer.IsRestricted {
212+
teamMates := builder.Select("DISTINCT team_user.uid").
213+
From("team_user").
214+
Where(builder.In("team_user.team_id", getUserTeamIDsQueryBuilder(opts.OrgID, opts.Doer.ID))).
215+
And(builder.Eq{"team_user.org_id": opts.OrgID})
216+
217+
sess.And(
218+
builder.In("org_user.uid", teamMates).
219+
Or(builder.Eq{"org_user.is_public": true}),
220+
)
221+
}
222+
}
223+
208224
// CountOrgMembers counts the organization's members
209225
func CountOrgMembers(ctx context.Context, opts *FindOrgMembersOpts) (int64, error) {
210226
sess := db.GetEngine(ctx).Where("org_id=?", opts.OrgID)
211227
if opts.PublicOnly() {
212-
sess.And("is_public = ?", true)
228+
sess = sess.And("is_public = ?", true)
229+
} else {
230+
opts.applyTeamMatesOnlyFilter(sess)
213231
}
214232

215233
return sess.Count(new(OrgUser))
@@ -533,7 +551,9 @@ func GetOrgsCanCreateRepoByUserID(ctx context.Context, userID int64) ([]*Organiz
533551
func GetOrgUsersByOrgID(ctx context.Context, opts *FindOrgMembersOpts) ([]*OrgUser, error) {
534552
sess := db.GetEngine(ctx).Where("org_id=?", opts.OrgID)
535553
if opts.PublicOnly() {
536-
sess.And("is_public = ?", true)
554+
sess = sess.And("is_public = ?", true)
555+
} else {
556+
opts.applyTeamMatesOnlyFilter(sess)
537557
}
538558

539559
if opts.ListOptions.PageSize > 0 {
@@ -664,6 +684,15 @@ func (org *Organization) getUserTeamIDs(ctx context.Context, userID int64) ([]in
664684
Find(&teamIDs)
665685
}
666686

687+
func getUserTeamIDsQueryBuilder(orgID, userID int64) *builder.Builder {
688+
return builder.Select("team.id").From("team").
689+
InnerJoin("team_user", "team_user.team_id = team.id").
690+
Where(builder.Eq{
691+
"team_user.org_id": orgID,
692+
"team_user.uid": userID,
693+
})
694+
}
695+
667696
// TeamsWithAccessToRepo returns all teams that have given access level to the repository.
668697
func (org *Organization) TeamsWithAccessToRepo(ctx context.Context, repoID int64, mode perm.AccessMode) ([]*Team, error) {
669698
return GetTeamsWithAccessToRepo(ctx, org.ID, repoID, mode)

models/organization/org_test.go

+70
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package organization_test
55

66
import (
7+
"slices"
78
"sort"
89
"testing"
910

@@ -181,6 +182,75 @@ func TestIsPublicMembership(t *testing.T) {
181182
test(unittest.NonexistentID, unittest.NonexistentID, false)
182183
}
183184

185+
func TestRestrictedUserOrgMembers(t *testing.T) {
186+
assert.NoError(t, unittest.PrepareTestDatabase())
187+
188+
restrictedUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{
189+
ID: 29,
190+
IsRestricted: true,
191+
})
192+
if !assert.True(t, restrictedUser.IsRestricted) {
193+
return // ensure fixtures return restricted user
194+
}
195+
196+
testCases := []struct {
197+
name string
198+
opts *organization.FindOrgMembersOpts
199+
expectedUIDs []int64
200+
}{
201+
{
202+
name: "restricted user sees public members and teammates",
203+
opts: &organization.FindOrgMembersOpts{
204+
OrgID: 17, // org17 where user29 is in team9
205+
Doer: restrictedUser,
206+
IsDoerMember: true,
207+
},
208+
expectedUIDs: []int64{2, 15, 20, 29}, // Public members (2) + teammates in team9 (15, 20, 29)
209+
},
210+
{
211+
name: "restricted user sees only public members when not member",
212+
opts: &organization.FindOrgMembersOpts{
213+
OrgID: 3, // org3 where user29 is not a member
214+
Doer: restrictedUser,
215+
},
216+
expectedUIDs: []int64{2, 28}, // Only public members
217+
},
218+
{
219+
name: "non logged in only shows public members",
220+
opts: &organization.FindOrgMembersOpts{
221+
OrgID: 3,
222+
},
223+
expectedUIDs: []int64{2, 28}, // Only public members
224+
},
225+
{
226+
name: "non restricted user sees all members",
227+
opts: &organization.FindOrgMembersOpts{
228+
OrgID: 17,
229+
Doer: unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 15}),
230+
IsDoerMember: true,
231+
},
232+
expectedUIDs: []int64{2, 15, 18, 20, 29}, // All members
233+
},
234+
}
235+
236+
for _, tc := range testCases {
237+
t.Run(tc.name, func(t *testing.T) {
238+
count, err := organization.CountOrgMembers(db.DefaultContext, tc.opts)
239+
assert.NoError(t, err)
240+
assert.EqualValues(t, len(tc.expectedUIDs), count)
241+
242+
members, err := organization.GetOrgUsersByOrgID(db.DefaultContext, tc.opts)
243+
assert.NoError(t, err)
244+
memberUIDs := make([]int64, 0, len(members))
245+
for _, member := range members {
246+
memberUIDs = append(memberUIDs, member.UID)
247+
}
248+
slices.Sort(memberUIDs)
249+
assert.EqualValues(t, tc.expectedUIDs, memberUIDs)
250+
})
251+
}
252+
}
253+
184254
func TestFindOrgs(t *testing.T) {
185255
assert.NoError(t, unittest.PrepareTestDatabase())
186256

0 commit comments

Comments
 (0)