mirror of
https://github.com/coder/coder.git
synced 2025-07-08 11:39:50 +00:00
feat: Add allow everyone option to GitHub OAuth2 logins (#5086)
* feat: Add allow everyone option for GitHub OAuth * fix: Detect team when multiple orgs are present Co-authored-by: 李董睿煊 <dongruixuan@hotmail.com>
This commit is contained in:
committed by
GitHub
parent
f262fb4811
commit
9fb710a04f
@ -38,6 +38,7 @@ type GithubOAuth2Config struct {
|
||||
TeamMembership func(ctx context.Context, client *http.Client, org, team, username string) (*github.Membership, error)
|
||||
|
||||
AllowSignups bool
|
||||
AllowEveryone bool
|
||||
AllowOrganizations []string
|
||||
AllowTeams []GithubOAuth2Team
|
||||
}
|
||||
@ -57,32 +58,38 @@ func (api *API) userOAuth2Github(rw http.ResponseWriter, r *http.Request) {
|
||||
)
|
||||
|
||||
oauthClient := oauth2.NewClient(ctx, oauth2.StaticTokenSource(state.Token))
|
||||
memberships, err := api.GithubOAuth2Config.ListOrganizationMemberships(ctx, oauthClient)
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error fetching authenticated Github user organizations.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
var selectedMembership *github.Membership
|
||||
for _, membership := range memberships {
|
||||
if membership.GetState() != "active" {
|
||||
continue
|
||||
|
||||
var selectedMemberships []*github.Membership
|
||||
var organizationNames []string
|
||||
if !api.GithubOAuth2Config.AllowEveryone {
|
||||
memberships, err := api.GithubOAuth2Config.ListOrganizationMemberships(ctx, oauthClient)
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error fetching authenticated Github user organizations.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
for _, allowed := range api.GithubOAuth2Config.AllowOrganizations {
|
||||
if *membership.Organization.Login != allowed {
|
||||
|
||||
for _, membership := range memberships {
|
||||
if membership.GetState() != "active" {
|
||||
continue
|
||||
}
|
||||
selectedMembership = membership
|
||||
break
|
||||
for _, allowed := range api.GithubOAuth2Config.AllowOrganizations {
|
||||
if *membership.Organization.Login != allowed {
|
||||
continue
|
||||
}
|
||||
selectedMemberships = append(selectedMemberships, membership)
|
||||
organizationNames = append(organizationNames, membership.Organization.GetLogin())
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(selectedMemberships) == 0 {
|
||||
httpapi.Write(ctx, rw, http.StatusUnauthorized, codersdk.Response{
|
||||
Message: "You aren't a member of the authorized Github organizations!",
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
if selectedMembership == nil {
|
||||
httpapi.Write(ctx, rw, http.StatusUnauthorized, codersdk.Response{
|
||||
Message: "You aren't a member of the authorized Github organizations!",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
ghUser, err := api.GithubOAuth2Config.AuthenticatedUser(ctx, oauthClient)
|
||||
@ -95,24 +102,29 @@ func (api *API) userOAuth2Github(rw http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// The default if no teams are specified is to allow all.
|
||||
if len(api.GithubOAuth2Config.AllowTeams) > 0 {
|
||||
if !api.GithubOAuth2Config.AllowEveryone && len(api.GithubOAuth2Config.AllowTeams) > 0 {
|
||||
var allowedTeam *github.Membership
|
||||
for _, allowTeam := range api.GithubOAuth2Config.AllowTeams {
|
||||
if allowTeam.Organization != *selectedMembership.Organization.Login {
|
||||
// This needs to continue because multiple organizations
|
||||
// could exist in the allow/team listings.
|
||||
continue
|
||||
if allowedTeam != nil {
|
||||
break
|
||||
}
|
||||
for _, selectedMembership := range selectedMemberships {
|
||||
if allowTeam.Organization != *selectedMembership.Organization.Login {
|
||||
// This needs to continue because multiple organizations
|
||||
// could exist in the allow/team listings.
|
||||
continue
|
||||
}
|
||||
|
||||
allowedTeam, err = api.GithubOAuth2Config.TeamMembership(ctx, oauthClient, allowTeam.Organization, allowTeam.Slug, *ghUser.Login)
|
||||
// The calling user may not have permission to the requested team!
|
||||
if err != nil {
|
||||
continue
|
||||
allowedTeam, err = api.GithubOAuth2Config.TeamMembership(ctx, oauthClient, allowTeam.Organization, allowTeam.Slug, *ghUser.Login)
|
||||
// The calling user may not have permission to the requested team!
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
if allowedTeam == nil {
|
||||
httpapi.Write(ctx, rw, http.StatusUnauthorized, codersdk.Response{
|
||||
Message: fmt.Sprintf("You aren't a member of an authorized team in the %s Github organization!", *selectedMembership.Organization.Login),
|
||||
Message: fmt.Sprintf("You aren't a member of an authorized team in the %v Github organization(s)!", organizationNames),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
@ -318,6 +318,124 @@ func TestUserOAuth2Github(t *testing.T) {
|
||||
resp := oauth2Callback(t, client)
|
||||
require.Equal(t, http.StatusTemporaryRedirect, resp.StatusCode)
|
||||
})
|
||||
t.Run("SignupAllowedTeamInFirstOrganization", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t, &coderdtest.Options{
|
||||
GithubOAuth2Config: &coderd.GithubOAuth2Config{
|
||||
AllowSignups: true,
|
||||
AllowOrganizations: []string{"coder", "nil"},
|
||||
AllowTeams: []coderd.GithubOAuth2Team{{"coder", "backend"}},
|
||||
OAuth2Config: &oauth2Config{},
|
||||
ListOrganizationMemberships: func(ctx context.Context, client *http.Client) ([]*github.Membership, error) {
|
||||
return []*github.Membership{
|
||||
{
|
||||
State: &stateActive,
|
||||
Organization: &github.Organization{
|
||||
Login: github.String("coder"),
|
||||
},
|
||||
},
|
||||
{
|
||||
State: &stateActive,
|
||||
Organization: &github.Organization{
|
||||
Login: github.String("nil"),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
TeamMembership: func(ctx context.Context, client *http.Client, org, team, username string) (*github.Membership, error) {
|
||||
return &github.Membership{}, nil
|
||||
},
|
||||
AuthenticatedUser: func(ctx context.Context, client *http.Client) (*github.User, error) {
|
||||
return &github.User{
|
||||
Login: github.String("mathias"),
|
||||
}, nil
|
||||
},
|
||||
ListEmails: func(ctx context.Context, client *http.Client) ([]*github.UserEmail, error) {
|
||||
return []*github.UserEmail{{
|
||||
Email: github.String("mathias@coder.com"),
|
||||
Verified: github.Bool(true),
|
||||
Primary: github.Bool(true),
|
||||
}}, nil
|
||||
},
|
||||
},
|
||||
})
|
||||
resp := oauth2Callback(t, client)
|
||||
require.Equal(t, http.StatusTemporaryRedirect, resp.StatusCode)
|
||||
})
|
||||
t.Run("SignupAllowedTeamInSecondOrganization", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t, &coderdtest.Options{
|
||||
GithubOAuth2Config: &coderd.GithubOAuth2Config{
|
||||
AllowSignups: true,
|
||||
AllowOrganizations: []string{"coder", "nil"},
|
||||
AllowTeams: []coderd.GithubOAuth2Team{{"nil", "null"}},
|
||||
OAuth2Config: &oauth2Config{},
|
||||
ListOrganizationMemberships: func(ctx context.Context, client *http.Client) ([]*github.Membership, error) {
|
||||
return []*github.Membership{
|
||||
{
|
||||
State: &stateActive,
|
||||
Organization: &github.Organization{
|
||||
Login: github.String("coder"),
|
||||
},
|
||||
},
|
||||
{
|
||||
State: &stateActive,
|
||||
Organization: &github.Organization{
|
||||
Login: github.String("nil"),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
TeamMembership: func(ctx context.Context, client *http.Client, org, team, username string) (*github.Membership, error) {
|
||||
return &github.Membership{}, nil
|
||||
},
|
||||
AuthenticatedUser: func(ctx context.Context, client *http.Client) (*github.User, error) {
|
||||
return &github.User{
|
||||
Login: github.String("mathias"),
|
||||
}, nil
|
||||
},
|
||||
ListEmails: func(ctx context.Context, client *http.Client) ([]*github.UserEmail, error) {
|
||||
return []*github.UserEmail{{
|
||||
Email: github.String("mathias@coder.com"),
|
||||
Verified: github.Bool(true),
|
||||
Primary: github.Bool(true),
|
||||
}}, nil
|
||||
},
|
||||
},
|
||||
})
|
||||
resp := oauth2Callback(t, client)
|
||||
require.Equal(t, http.StatusTemporaryRedirect, resp.StatusCode)
|
||||
})
|
||||
t.Run("SignupAllowEveryone", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t, &coderdtest.Options{
|
||||
GithubOAuth2Config: &coderd.GithubOAuth2Config{
|
||||
AllowSignups: true,
|
||||
AllowEveryone: true,
|
||||
OAuth2Config: &oauth2Config{},
|
||||
ListOrganizationMemberships: func(ctx context.Context, client *http.Client) ([]*github.Membership, error) {
|
||||
return []*github.Membership{}, nil
|
||||
},
|
||||
TeamMembership: func(ctx context.Context, client *http.Client, org, team, username string) (*github.Membership, error) {
|
||||
return nil, xerrors.New("no teams")
|
||||
},
|
||||
AuthenticatedUser: func(ctx context.Context, client *http.Client) (*github.User, error) {
|
||||
return &github.User{
|
||||
Login: github.String("mathias"),
|
||||
}, nil
|
||||
},
|
||||
ListEmails: func(ctx context.Context, client *http.Client) ([]*github.UserEmail, error) {
|
||||
return []*github.UserEmail{{
|
||||
Email: github.String("mathias@coder.com"),
|
||||
Verified: github.Bool(true),
|
||||
Primary: github.Bool(true),
|
||||
}}, nil
|
||||
},
|
||||
},
|
||||
})
|
||||
resp := oauth2Callback(t, client)
|
||||
require.Equal(t, http.StatusTemporaryRedirect, resp.StatusCode)
|
||||
})
|
||||
t.Run("SignupFailedInactiveInOrg", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t, &coderdtest.Options{
|
||||
|
Reference in New Issue
Block a user