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

Commit d33b87c

Browse files
authored
support getting other orgs on dotcom from the GraphQL API (#63941)
Previously, the GraphQL API on dotcom only let org members query for an org (through `organization(name: ...)` or otherwise). This made sense as a strict security safeguard in a world where an org had only private resources, not public resources. However, with search contexts, saved searches, and now the new prompt library, we want orgs on dotcom to be able to create things that (if we or they intentionally make them public) all users can see, and can see the association with the org owner. That requires all users to be able to query for the org and see its name. We continue to enforce the secrecy of much org data: members (only org members can list the other members of the org), settings (only org members can view this). **But the name, displayName, and existence of an org will now be considered public.** ## Test plan In dotcom mode, view an organization.
1 parent f633713 commit d33b87c

File tree

4 files changed

+5
-219
lines changed

4 files changed

+5
-219
lines changed

cmd/frontend/graphqlbackend/org.go

Lines changed: 1 addition & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package graphqlbackend
22

33
import (
44
"context"
5-
"fmt"
65

76
"github.com/graph-gophers/graphql-go"
87
"github.com/graph-gophers/graphql-go/relay"
@@ -28,50 +27,14 @@ func (r *schemaResolver) Organization(ctx context.Context, args struct{ Name str
2827
if err != nil {
2928
return nil, err
3029
}
31-
// 🚨 SECURITY: Only org members can get org details on Cloud
32-
if dotcom.SourcegraphDotComMode() {
33-
hasAccess := func() error {
34-
if auth.CheckOrgAccess(ctx, r.db, org.ID) == nil {
35-
return nil
36-
}
37-
38-
if a := sgactor.FromContext(ctx); a.IsAuthenticated() {
39-
_, err = r.db.OrgInvitations().GetPending(ctx, org.ID, a.UID)
40-
if err == nil {
41-
return nil
42-
}
43-
}
44-
45-
// NOTE: We want to present a unified error to unauthorized users to prevent
46-
// them from differentiating service states by different error messages.
47-
return &database.OrgNotFoundError{Message: fmt.Sprintf("name %s", args.Name)}
48-
}
49-
if err := hasAccess(); err != nil {
50-
// site admin can access org ID
51-
if auth.CheckCurrentUserIsSiteAdmin(ctx, r.db) == nil {
52-
if featureflag.FromContext(ctx).GetBoolOr("auditlog-expansion", false) {
53-
54-
// Log action for site admin vieweing an organization's details in dotcom
55-
if err := r.db.SecurityEventLogs().LogSecurityEvent(ctx, database.SecurityEventNameDotComOrgViewed, "", uint32(actor.FromContext(ctx).UID), "", "BACKEND", args); err != nil {
56-
r.logger.Warn("Error logging security event", log.Error(err))
57-
58-
}
59-
}
60-
onlyOrgID := &types.Org{ID: org.ID}
61-
return &OrgResolver{db: r.db, org: onlyOrgID}, nil
62-
}
63-
return nil, err
64-
}
65-
}
6630

6731
if featureflag.FromContext(ctx).GetBoolOr("auditlog-expansion", false) {
68-
6932
// Log action for siteadmin viewing an organization's details
7033
if err := r.db.SecurityEventLogs().LogSecurityEvent(ctx, database.SecurityEventNameOrgViewed, "", uint32(actor.FromContext(ctx).UID), "", "BACKEND", args); err != nil {
7134
r.logger.Warn("Error logging security event", log.Error(err))
72-
7335
}
7436
}
37+
7538
return &OrgResolver{db: r.db, org: org}, nil
7639
}
7740

@@ -93,28 +56,6 @@ func OrgByID(ctx context.Context, db database.DB, id graphql.ID) (*OrgResolver,
9356
}
9457

9558
func OrgByIDInt32(ctx context.Context, db database.DB, orgID int32) (*OrgResolver, error) {
96-
return orgByIDInt32WithForcedAccess(ctx, db, orgID, false)
97-
}
98-
99-
func orgByIDInt32WithForcedAccess(ctx context.Context, db database.DB, orgID int32, forceAccess bool) (*OrgResolver, error) {
100-
// 🚨 SECURITY: Only org members can get org details on Cloud
101-
// And all invited users by email
102-
if !forceAccess && dotcom.SourcegraphDotComMode() {
103-
err := auth.CheckOrgAccess(ctx, db, orgID)
104-
if err != nil {
105-
hasAccess := false
106-
// allow invited user to view org details
107-
if a := sgactor.FromContext(ctx); a.IsAuthenticated() {
108-
_, err := db.OrgInvitations().GetPending(ctx, orgID, a.UID)
109-
if err == nil {
110-
hasAccess = true
111-
}
112-
}
113-
if !hasAccess {
114-
return nil, &database.OrgNotFoundError{Message: fmt.Sprintf("id %d", orgID)}
115-
}
116-
}
117-
}
11859
org, err := db.Orgs().GetByID(ctx, orgID)
11960
if err != nil {
12061
return nil, err

cmd/frontend/graphqlbackend/org_invitation.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ func UnmarshalOrgInvitationID(id graphql.ID) (orgInvitationID int64, err error)
4848
}
4949

5050
func (r *organizationInvitationResolver) Organization(ctx context.Context) (*OrgResolver, error) {
51-
return orgByIDInt32WithForcedAccess(ctx, r.db, r.v.OrgID, r.v.RecipientEmail != "")
51+
return OrgByIDInt32(ctx, r.db, r.v.OrgID)
5252
}
5353

5454
func (r *organizationInvitationResolver) Sender(ctx context.Context) (*UserResolver, error) {

cmd/frontend/graphqlbackend/org_test.go

Lines changed: 3 additions & 157 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ func TestOrganization(t *testing.T) {
4343
db.UsersFunc.SetDefaultReturn(users)
4444
db.OrgMembersFunc.SetDefaultReturn(orgMembers)
4545

46-
t.Run("anyone can access by default", func(t *testing.T) {
46+
t.Run("can access organizations", func(t *testing.T) {
4747
RunTests(t, []*Test{
4848
{
4949
Schema: mustParseGraphQLSchema(t, db),
@@ -64,160 +64,6 @@ func TestOrganization(t *testing.T) {
6464
},
6565
})
6666
})
67-
68-
t.Run("users not invited or not a member cannot access on Sourcegraph.com", func(t *testing.T) {
69-
dotcom.MockSourcegraphDotComMode(t, true)
70-
71-
RunTests(t, []*Test{
72-
{
73-
Schema: mustParseGraphQLSchema(t, db),
74-
Query: `
75-
{
76-
organization(name: "acme") {
77-
name
78-
}
79-
}
80-
`,
81-
ExpectedResult: `
82-
{
83-
"organization": null
84-
}
85-
`,
86-
ExpectedErrors: []*gqlerrors.QueryError{
87-
{
88-
Message: "org not found: name acme",
89-
Path: []any{"organization"},
90-
},
91-
},
92-
},
93-
})
94-
})
95-
96-
t.Run("org members can access on Sourcegraph.com", func(t *testing.T) {
97-
dotcom.MockSourcegraphDotComMode(t, true)
98-
99-
ctx := actor.WithActor(context.Background(), &actor.Actor{UID: 1})
100-
101-
users := dbmocks.NewMockUserStore()
102-
users.GetByCurrentAuthUserFunc.SetDefaultReturn(&types.User{ID: 1, SiteAdmin: false}, nil)
103-
104-
orgMembers := dbmocks.NewMockOrgMemberStore()
105-
orgMembers.GetByOrgIDAndUserIDFunc.SetDefaultReturn(&types.OrgMembership{OrgID: 1, UserID: 1}, nil)
106-
107-
db := dbmocks.NewMockDBFrom(db)
108-
db.UsersFunc.SetDefaultReturn(users)
109-
db.OrgMembersFunc.SetDefaultReturn(orgMembers)
110-
111-
RunTests(t, []*Test{
112-
{
113-
Schema: mustParseGraphQLSchema(t, db),
114-
Context: ctx,
115-
Query: `
116-
{
117-
organization(name: "acme") {
118-
name
119-
}
120-
}
121-
`,
122-
ExpectedResult: `
123-
{
124-
"organization": {
125-
"name": "acme"
126-
}
127-
}
128-
`,
129-
},
130-
})
131-
})
132-
133-
t.Run("invited users can access on Sourcegraph.com", func(t *testing.T) {
134-
dotcom.MockSourcegraphDotComMode(t, true)
135-
136-
ctx := actor.WithActor(context.Background(), &actor.Actor{UID: 1})
137-
138-
users := dbmocks.NewMockUserStore()
139-
users.GetByCurrentAuthUserFunc.SetDefaultReturn(&types.User{ID: 1, SiteAdmin: false}, nil)
140-
141-
orgMembers := dbmocks.NewMockOrgMemberStore()
142-
orgMembers.GetByOrgIDAndUserIDFunc.SetDefaultReturn(nil, &database.ErrOrgMemberNotFound{})
143-
144-
orgInvites := dbmocks.NewMockOrgInvitationStore()
145-
orgInvites.GetPendingFunc.SetDefaultReturn(nil, nil)
146-
147-
db := dbmocks.NewMockDBFrom(db)
148-
db.OrgsFunc.SetDefaultReturn(orgs)
149-
db.UsersFunc.SetDefaultReturn(users)
150-
db.OrgMembersFunc.SetDefaultReturn(orgMembers)
151-
db.OrgInvitationsFunc.SetDefaultReturn(orgInvites)
152-
153-
RunTests(t, []*Test{
154-
{
155-
Schema: mustParseGraphQLSchema(t, db),
156-
Context: ctx,
157-
Query: `
158-
{
159-
organization(name: "acme") {
160-
name
161-
}
162-
}
163-
`,
164-
ExpectedResult: `
165-
{
166-
"organization": {
167-
"name": "acme"
168-
}
169-
}
170-
`,
171-
},
172-
})
173-
})
174-
175-
t.Run("invited users can access org by ID on Sourcegraph.com", func(t *testing.T) {
176-
dotcom.MockSourcegraphDotComMode(t, true)
177-
178-
ctx := actor.WithActor(context.Background(), &actor.Actor{UID: 1})
179-
180-
users := dbmocks.NewMockUserStore()
181-
users.GetByCurrentAuthUserFunc.SetDefaultReturn(&types.User{ID: 1, SiteAdmin: false}, nil)
182-
183-
orgMembers := dbmocks.NewMockOrgMemberStore()
184-
orgMembers.GetByOrgIDAndUserIDFunc.SetDefaultReturn(nil, &database.ErrOrgMemberNotFound{})
185-
186-
orgInvites := dbmocks.NewMockOrgInvitationStore()
187-
orgInvites.GetPendingFunc.SetDefaultReturn(nil, nil)
188-
189-
db := dbmocks.NewMockDBFrom(db)
190-
db.OrgsFunc.SetDefaultReturn(orgs)
191-
db.UsersFunc.SetDefaultReturn(users)
192-
db.OrgMembersFunc.SetDefaultReturn(orgMembers)
193-
db.OrgInvitationsFunc.SetDefaultReturn(orgInvites)
194-
195-
RunTests(t, []*Test{
196-
{
197-
Schema: mustParseGraphQLSchema(t, db),
198-
Context: ctx,
199-
Query: `
200-
{
201-
node(id: "T3JnOjE=") {
202-
__typename
203-
id
204-
... on Org {
205-
name
206-
}
207-
}
208-
}
209-
`,
210-
ExpectedResult: `
211-
{
212-
"node": {
213-
"__typename":"Org",
214-
"id":"T3JnOjE=", "name":"acme"
215-
}
216-
}
217-
`,
218-
},
219-
})
220-
})
22167
}
22268

22369
func TestOrganizationMembers(t *testing.T) {
@@ -327,8 +173,8 @@ func TestOrganizationMembers(t *testing.T) {
327173
`,
328174
ExpectedErrors: []*gqlerrors.QueryError{
329175
{
330-
Message: "org not found: name acme",
331-
Path: []any{"organization"},
176+
Message: "current user is not an org member",
177+
Path: []any{"organization", "members"},
332178
},
333179
},
334180
},

internal/database/security_event_logs.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,6 @@ const (
9393
SecurityEventNameOrgCreated SecurityEventName = "OrganizationCreated"
9494
SecurityEventNameOrgUpdated SecurityEventName = "OrganizationUpdated"
9595
SecurityEventNameOrgSettingsViewed SecurityEventName = "OrganizationSettingsViewed"
96-
SecurityEventNameDotComOrgViewed SecurityEventName = "DotComOrganizationViewed"
9796

9897
SecurityEventNameOutboundReqViewed SecurityEventName = "OutboundRequestViewed"
9998

0 commit comments

Comments
 (0)