Skip to content
This repository was archived by the owner on Sep 30, 2024. It is now read-only.

Commit 85359ac

Browse files
authored
allow non-site admins to list their orgs' members on dotcom (#63934)
Previously, only site admins could see the members of an org, even of orgs that they were a member of. This restriction does not make any sense and makes the orgs feature broken on dotcom. ## Test plan Added test.
1 parent 43df26f commit 85359ac

File tree

3 files changed

+127
-16
lines changed

3 files changed

+127
-16
lines changed

cmd/frontend/graphqlbackend/org.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -163,9 +163,11 @@ func (o *OrgResolver) Members(ctx context.Context, args struct {
163163
Query *string
164164
},
165165
) (*graphqlutil.ConnectionResolver[*UserResolver], error) {
166-
// 🚨 SECURITY: Verify listing users is allowed.
167-
if err := checkMembersAccess(ctx, o.db); err != nil {
168-
return nil, err
166+
// 🚨 SECURITY: On dotcom, only an org's members can list its members.
167+
if dotcom.SourcegraphDotComMode() {
168+
if err := auth.CheckOrgAccess(ctx, o.db, o.org.ID); err != nil {
169+
return nil, err
170+
}
169171
}
170172

171173
connectionStore := &membersConnectionStore{

cmd/frontend/graphqlbackend/org_test.go

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,123 @@ func TestOrganization(t *testing.T) {
220220
})
221221
}
222222

223+
func TestOrganizationMembers(t *testing.T) {
224+
users := dbmocks.NewMockUserStore()
225+
users.ListByOrgFunc.SetDefaultReturn([]*types.User{
226+
{ID: 1, Username: "alice"},
227+
{ID: 2, Username: "bob"},
228+
}, nil)
229+
230+
orgMembers := dbmocks.NewMockOrgMemberStore()
231+
orgMembers.GetByOrgIDAndUserIDFunc.SetDefaultHook(func(ctx context.Context, orgID, userID int32) (*types.OrgMembership, error) {
232+
if orgID == 1 && userID == 1 {
233+
return &types.OrgMembership{OrgID: 1, UserID: 1}, nil
234+
}
235+
return nil, &database.ErrOrgMemberNotFound{}
236+
})
237+
238+
orgs := dbmocks.NewMockOrgStore()
239+
mockedOrg := types.Org{ID: 1, Name: "acme"}
240+
orgs.GetByNameFunc.SetDefaultReturn(&mockedOrg, nil)
241+
242+
db := dbmocks.NewMockDB()
243+
db.OrgsFunc.SetDefaultReturn(orgs)
244+
db.UsersFunc.SetDefaultReturn(users)
245+
db.OrgMembersFunc.SetDefaultReturn(orgMembers)
246+
247+
t.Run("org members can list members", func(t *testing.T) {
248+
users.GetByCurrentAuthUserFunc.SetDefaultReturn(&types.User{Username: "alice", ID: 1}, nil)
249+
for _, isDotcom := range []bool{true, false} {
250+
t.Run(fmt.Sprintf("dotcom=%v", isDotcom), func(t *testing.T) {
251+
dotcom.MockSourcegraphDotComMode(t, isDotcom)
252+
RunTests(t, []*Test{
253+
{
254+
Schema: mustParseGraphQLSchema(t, db),
255+
Query: `
256+
{
257+
organization(name: "acme") {
258+
members {
259+
nodes { username }
260+
}
261+
}
262+
}
263+
`,
264+
ExpectedResult: `
265+
{
266+
"organization": {
267+
"members": {
268+
"nodes": [{"username": "alice"}, {"username": "bob"}]
269+
}
270+
}
271+
}
272+
`,
273+
},
274+
})
275+
})
276+
}
277+
})
278+
279+
t.Run("non-members", func(t *testing.T) {
280+
users.GetByCurrentAuthUserFunc.SetDefaultReturn(&types.User{Username: "xavier", ID: 10}, nil)
281+
282+
t.Run("can list members on non-dotcom", func(t *testing.T) {
283+
dotcom.MockSourcegraphDotComMode(t, false)
284+
RunTests(t, []*Test{
285+
{
286+
Schema: mustParseGraphQLSchema(t, db),
287+
Query: `
288+
{
289+
organization(name: "acme") {
290+
members {
291+
nodes { username }
292+
}
293+
}
294+
}
295+
`,
296+
ExpectedResult: `
297+
{
298+
"organization": {
299+
"members": {
300+
"nodes": [{"username": "alice"}, {"username": "bob"}]
301+
}
302+
}
303+
}
304+
`,
305+
},
306+
})
307+
})
308+
309+
t.Run("cannot list members on dotcom", func(t *testing.T) {
310+
dotcom.MockSourcegraphDotComMode(t, true)
311+
RunTests(t, []*Test{
312+
{
313+
Schema: mustParseGraphQLSchema(t, db),
314+
Query: `
315+
{
316+
organization(name: "acme") {
317+
members {
318+
nodes { username }
319+
}
320+
}
321+
}
322+
`,
323+
ExpectedResult: `
324+
{
325+
"organization": null
326+
}
327+
`,
328+
ExpectedErrors: []*gqlerrors.QueryError{
329+
{
330+
Message: "org not found: name acme",
331+
Path: []any{"organization"},
332+
},
333+
},
334+
},
335+
})
336+
})
337+
})
338+
}
339+
223340
func TestCreateOrganization(t *testing.T) {
224341
userID := int32(1)
225342

cmd/frontend/graphqlbackend/users.go

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,11 @@ type usersArgs struct {
2424
}
2525

2626
func (r *schemaResolver) Users(ctx context.Context, args *usersArgs) (*userConnectionResolver, error) {
27-
// 🚨 SECURITY: Verify listing users is allowed.
28-
if err := checkMembersAccess(ctx, r.db); err != nil {
29-
return nil, err
27+
// 🚨 SECURITY: Only site admins can list all users on sourcegraph.com.
28+
if dotcom.SourcegraphDotComMode() {
29+
if err := auth.CheckCurrentUserIsSiteAdmin(ctx, r.db); err != nil {
30+
return nil, err
31+
}
3032
}
3133

3234
opt := database.UsersListOptions{
@@ -141,13 +143,3 @@ func (r *userConnectionResolver) PageInfo(ctx context.Context) (*graphqlutil.Pag
141143
}
142144
return graphqlutil.HasNextPage(false), nil
143145
}
144-
145-
func checkMembersAccess(ctx context.Context, db database.DB) error {
146-
// 🚨 SECURITY: Only site admins can list users on sourcegraph.com.
147-
if dotcom.SourcegraphDotComMode() {
148-
if err := auth.CheckCurrentUserIsSiteAdmin(ctx, db); err != nil {
149-
return err
150-
}
151-
}
152-
return nil
153-
}

0 commit comments

Comments
 (0)