Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitdff6e97

Browse files
authored
feat: Add allowlist of GitHub teams for OAuth (#2849)
Fixes#2848.
1 parentc801da4 commitdff6e97

File tree

5 files changed

+134
-3
lines changed

5 files changed

+134
-3
lines changed

‎cli/cliflag/cliflag.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ func StringArrayVarP(flagset *pflag.FlagSet, ptr *[]string, name string, shortha
4747
def=strings.Split(val,",")
4848
}
4949
}
50-
flagset.StringArrayVarP(ptr,name,shorthand,def,usage)
50+
flagset.StringArrayVarP(ptr,name,shorthand,def,fmtUsage(usage,env))
5151
}
5252

5353
// Uint8VarP sets a uint8 flag on the given flag set.

‎cli/server.go

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ func server() *cobra.Command {
8282
oauth2GithubClientIDstring
8383
oauth2GithubClientSecretstring
8484
oauth2GithubAllowedOrganizations []string
85+
oauth2GithubAllowedTeams []string
8586
oauth2GithubAllowSignupsbool
8687
telemetryEnablebool
8788
telemetryURLstring
@@ -264,7 +265,7 @@ func server() *cobra.Command {
264265
}
265266

266267
ifoauth2GithubClientSecret!="" {
267-
options.GithubOAuth2Config,err=configureGithubOAuth2(accessURLParsed,oauth2GithubClientID,oauth2GithubClientSecret,oauth2GithubAllowSignups,oauth2GithubAllowedOrganizations)
268+
options.GithubOAuth2Config,err=configureGithubOAuth2(accessURLParsed,oauth2GithubClientID,oauth2GithubClientSecret,oauth2GithubAllowSignups,oauth2GithubAllowedOrganizations,oauth2GithubAllowedTeams)
268269
iferr!=nil {
269270
returnxerrors.Errorf("configure github oauth2: %w",err)
270271
}
@@ -535,6 +536,8 @@ func server() *cobra.Command {
535536
"Specifies a client secret to use for oauth2 with GitHub.")
536537
cliflag.StringArrayVarP(root.Flags(),&oauth2GithubAllowedOrganizations,"oauth2-github-allowed-orgs","","CODER_OAUTH2_GITHUB_ALLOWED_ORGS",nil,
537538
"Specifies organizations the user must be a member of to authenticate with GitHub.")
539+
cliflag.StringArrayVarP(root.Flags(),&oauth2GithubAllowedTeams,"oauth2-github-allowed-teams","","CODER_OAUTH2_GITHUB_ALLOWED_TEAMS",nil,
540+
"Specifies teams inside organizations the user must be a member of to authenticate with GitHub. Formatted as: <organization-name>/<team-slug>.")
538541
cliflag.BoolVarP(root.Flags(),&oauth2GithubAllowSignups,"oauth2-github-allow-signups","","CODER_OAUTH2_GITHUB_ALLOW_SIGNUPS",false,
539542
"Specifies whether new users can sign up with GitHub.")
540543
cliflag.BoolVarP(root.Flags(),&telemetryEnable,"telemetry","","CODER_TELEMETRY",true,"Specifies whether telemetry is enabled or not. Coder collects anonymized usage data to help improve our product.")
@@ -719,11 +722,22 @@ func configureTLS(listener net.Listener, tlsMinVersion, tlsClientAuth, tlsCertFi
719722
returntls.NewListener(listener,tlsConfig),nil
720723
}
721724

722-
funcconfigureGithubOAuth2(accessURL*url.URL,clientID,clientSecretstring,allowSignupsbool,allowOrgs []string) (*coderd.GithubOAuth2Config,error) {
725+
funcconfigureGithubOAuth2(accessURL*url.URL,clientID,clientSecretstring,allowSignupsbool,allowOrgs []string,rawTeams []string) (*coderd.GithubOAuth2Config,error) {
723726
redirectURL,err:=accessURL.Parse("/api/v2/users/oauth2/github/callback")
724727
iferr!=nil {
725728
returnnil,xerrors.Errorf("parse github oauth callback url: %w",err)
726729
}
730+
allowTeams:=make([]coderd.GithubOAuth2Team,0,len(rawTeams))
731+
for_,rawTeam:=rangerawTeams {
732+
parts:=strings.SplitN(rawTeam,"/",2)
733+
iflen(parts)!=2 {
734+
returnnil,xerrors.Errorf("github team allowlist is formatted incorrectly. got %s; wanted <organization>/<team>",rawTeam)
735+
}
736+
allowTeams=append(allowTeams, coderd.GithubOAuth2Team{
737+
Organization:parts[0],
738+
Slug:parts[1],
739+
})
740+
}
727741
return&coderd.GithubOAuth2Config{
728742
OAuth2Config:&oauth2.Config{
729743
ClientID:clientID,
@@ -738,6 +752,7 @@ func configureGithubOAuth2(accessURL *url.URL, clientID, clientSecret string, al
738752
},
739753
AllowSignups:allowSignups,
740754
AllowOrganizations:allowOrgs,
755+
AllowTeams:allowTeams,
741756
AuthenticatedUser:func(ctx context.Context,client*http.Client) (*github.User,error) {
742757
user,_,err:=github.NewClient(client).Users.Get(ctx,"")
743758
returnuser,err
@@ -749,9 +764,18 @@ func configureGithubOAuth2(accessURL *url.URL, clientID, clientSecret string, al
749764
ListOrganizationMemberships:func(ctx context.Context,client*http.Client) ([]*github.Membership,error) {
750765
memberships,_,err:=github.NewClient(client).Organizations.ListOrgMemberships(ctx,&github.ListOrgMembershipsOptions{
751766
State:"active",
767+
ListOptions: github.ListOptions{
768+
PerPage:100,
769+
},
752770
})
753771
returnmemberships,err
754772
},
773+
ListTeams:func(ctx context.Context,client*http.Client,orgstring) ([]*github.Team,error) {
774+
teams,_,err:=github.NewClient(client).Teams.ListTeams(ctx,org,&github.ListOptions{
775+
PerPage:100,
776+
})
777+
returnteams,err
778+
},
755779
},nil
756780
}
757781

‎coderd/database/db_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ import (
1414

1515
funcTestNestedInTx(t*testing.T) {
1616
t.Parallel()
17+
iftesting.Short() {
18+
t.SkipNow()
19+
}
1720

1821
uid:=uuid.New()
1922
sqlDB:=testSQLDB(t)

‎coderd/userauth.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,23 @@ import (
1717
"github.com/coder/coder/codersdk"
1818
)
1919

20+
// GithubOAuth2Team represents a team scoped to an organization.
21+
typeGithubOAuth2Teamstruct {
22+
Organizationstring
23+
Slugstring
24+
}
25+
2026
// GithubOAuth2Provider exposes required functions for the Github authentication flow.
2127
typeGithubOAuth2Configstruct {
2228
httpmw.OAuth2Config
2329
AuthenticatedUserfunc(ctx context.Context,client*http.Client) (*github.User,error)
2430
ListEmailsfunc(ctx context.Context,client*http.Client) ([]*github.UserEmail,error)
2531
ListOrganizationMembershipsfunc(ctx context.Context,client*http.Client) ([]*github.Membership,error)
32+
ListTeamsfunc(ctx context.Context,client*http.Client,orgstring) ([]*github.Team,error)
2633

2734
AllowSignupsbool
2835
AllowOrganizations []string
36+
AllowTeams []GithubOAuth2Team
2937
}
3038

3139
func (api*API)userAuthMethods(rw http.ResponseWriter,_*http.Request) {
@@ -64,6 +72,41 @@ func (api *API) userOAuth2Github(rw http.ResponseWriter, r *http.Request) {
6472
return
6573
}
6674

75+
// The default if no teams are specified is to allow all.
76+
iflen(api.GithubOAuth2Config.AllowTeams)>0 {
77+
teams,err:=api.GithubOAuth2Config.ListTeams(r.Context(),oauthClient,*selectedMembership.Organization.Login)
78+
iferr!=nil {
79+
httpapi.Write(rw,http.StatusInternalServerError, httpapi.Response{
80+
Message:"Failed to fetch teams from GitHub.",
81+
Detail:err.Error(),
82+
})
83+
return
84+
}
85+
86+
varallowedTeam*github.Team
87+
for_,team:=rangeteams {
88+
for_,allowTeam:=rangeapi.GithubOAuth2Config.AllowTeams {
89+
ifallowTeam.Organization!=*selectedMembership.Organization.Login {
90+
// This needs to continue because multiple organizations
91+
// could exist in the allow/team listings.
92+
continue
93+
}
94+
ifallowTeam.Slug!=*team.Slug {
95+
continue
96+
}
97+
allowedTeam=team
98+
break
99+
}
100+
}
101+
102+
ifallowedTeam==nil {
103+
httpapi.Write(rw,http.StatusUnauthorized, httpapi.Response{
104+
Message:fmt.Sprintf("You aren't a member of an authorized team in the %s Github organization!",*selectedMembership.Organization.Login),
105+
})
106+
return
107+
}
108+
}
109+
67110
emails,err:=api.GithubOAuth2Config.ListEmails(r.Context(),oauthClient)
68111
iferr!=nil {
69112
httpapi.Write(rw,http.StatusInternalServerError, httpapi.Response{

‎coderd/userauth_test.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,30 @@ func TestUserOAuth2Github(t *testing.T) {
7373
resp:=oauth2Callback(t,client)
7474
require.Equal(t,http.StatusUnauthorized,resp.StatusCode)
7575
})
76+
t.Run("NotInAllowedTeam",func(t*testing.T) {
77+
t.Parallel()
78+
client:=coderdtest.New(t,&coderdtest.Options{
79+
GithubOAuth2Config:&coderd.GithubOAuth2Config{
80+
AllowOrganizations: []string{"coder"},
81+
AllowTeams: []coderd.GithubOAuth2Team{{"another","something"}, {"coder","frontend"}},
82+
OAuth2Config:&oauth2Config{},
83+
ListOrganizationMemberships:func(ctx context.Context,client*http.Client) ([]*github.Membership,error) {
84+
return []*github.Membership{{
85+
Organization:&github.Organization{
86+
Login:github.String("coder"),
87+
},
88+
}},nil
89+
},
90+
ListTeams:func(ctx context.Context,client*http.Client,orgstring) ([]*github.Team,error) {
91+
return []*github.Team{{
92+
Slug:github.String("nope"),
93+
}},nil
94+
},
95+
},
96+
})
97+
resp:=oauth2Callback(t,client)
98+
require.Equal(t,http.StatusUnauthorized,resp.StatusCode)
99+
})
76100
t.Run("UnverifiedEmail",func(t*testing.T) {
77101
t.Parallel()
78102
client:=coderdtest.New(t,&coderdtest.Options{
@@ -184,6 +208,43 @@ func TestUserOAuth2Github(t *testing.T) {
184208
resp:=oauth2Callback(t,client)
185209
require.Equal(t,http.StatusTemporaryRedirect,resp.StatusCode)
186210
})
211+
t.Run("SignupAllowedTeam",func(t*testing.T) {
212+
t.Parallel()
213+
client:=coderdtest.New(t,&coderdtest.Options{
214+
GithubOAuth2Config:&coderd.GithubOAuth2Config{
215+
AllowSignups:true,
216+
AllowOrganizations: []string{"coder"},
217+
AllowTeams: []coderd.GithubOAuth2Team{{"coder","frontend"}},
218+
OAuth2Config:&oauth2Config{},
219+
ListOrganizationMemberships:func(ctx context.Context,client*http.Client) ([]*github.Membership,error) {
220+
return []*github.Membership{{
221+
Organization:&github.Organization{
222+
Login:github.String("coder"),
223+
},
224+
}},nil
225+
},
226+
ListTeams:func(ctx context.Context,client*http.Client,orgstring) ([]*github.Team,error) {
227+
return []*github.Team{{
228+
Slug:github.String("frontend"),
229+
}},nil
230+
},
231+
AuthenticatedUser:func(ctx context.Context,client*http.Client) (*github.User,error) {
232+
return&github.User{
233+
Login:github.String("kyle"),
234+
},nil
235+
},
236+
ListEmails:func(ctx context.Context,client*http.Client) ([]*github.UserEmail,error) {
237+
return []*github.UserEmail{{
238+
Email:github.String("kyle@coder.com"),
239+
Verified:github.Bool(true),
240+
Primary:github.Bool(true),
241+
}},nil
242+
},
243+
},
244+
})
245+
resp:=oauth2Callback(t,client)
246+
require.Equal(t,http.StatusTemporaryRedirect,resp.StatusCode)
247+
})
187248
}
188249

189250
funcoauth2Callback(t*testing.T,client*codersdk.Client)*http.Response {

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp