Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions cli/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -1006,7 +1006,7 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
}
options.WebPushDispatcher = webpusher

githubOAuth2ConfigParams, err := getGithubOAuth2ConfigParams(ctx, options.Database, vals)
githubOAuth2ConfigParams, err := getGithubOAuth2ConfigParams(ctx, options.Logger, options.Database, vals)
if err != nil {
return xerrors.Errorf("get github oauth2 config params: %w", err)
}
Expand Down Expand Up @@ -2213,7 +2213,7 @@ func maybeAppendDefaultGithubExternalAuthProvider(
}), nil
}

func getGithubOAuth2ConfigParams(ctx context.Context, db database.Store, vals *codersdk.DeploymentValues) (*githubOAuth2ConfigParams, error) {
func getGithubOAuth2ConfigParams(ctx context.Context, logger slog.Logger, db database.Store, vals *codersdk.DeploymentValues) (*githubOAuth2ConfigParams, error) {
params := githubOAuth2ConfigParams{
accessURL: vals.AccessURL.Value(),
clientID: vals.OAuth2.Github.ClientID.String(),
Expand Down Expand Up @@ -2250,6 +2250,16 @@ func getGithubOAuth2ConfigParams(ctx context.Context, db database.Store, vals *c
params.deviceFlow = GithubOAuth2DefaultProviderDeviceFlow
if len(params.allowOrgs) == 0 {
params.allowEveryone = GithubOAuth2DefaultProviderAllowEveryone
} else {
// The default provider is a GitHub App, which can only see memberships
// in organizations that have installed it. If the app isn't installed
// in an allowed organization, every login from that organization is
// rejected as "not a member".
logger.Warn(ctx, "the default GitHub OAuth provider can only see memberships in organizations that have installed the Coder GitHub app; "+
"users cannot log in until the app is installed in each allowed organization, or a custom GitHub OAuth app is configured",
slog.F("allowed_orgs", params.allowOrgs),
slog.F("install_url", coderd.GithubOAuth2DefaultProviderInstallURL),
)
}

return &params, nil
Expand Down
27 changes: 27 additions & 0 deletions coderd/userauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -772,6 +772,20 @@ type GithubOAuth2Config struct {
DefaultProviderConfigured bool
}

const (
// GithubOAuth2DefaultProviderInstallURL is where admins install the
// default Coder GitHub app so it can see organization and team
// memberships.
GithubOAuth2DefaultProviderInstallURL = "https://github.com/apps/coder/installations/select_target"

// githubOAuth2DefaultProviderRemediation explains why the default GitHub
// app can fail org and team membership checks, and how to fix it. It is
// appended to login rejection messages and mirrored by the server startup
// warning and the GitHub auth docs, so keep those in sync.
githubOAuth2DefaultProviderRemediation = "The default GitHub OAuth provider can only see organizations and teams that have installed the Coder GitHub app. " +
"Install it from " + GithubOAuth2DefaultProviderInstallURL + " for each authorized organization, or configure a custom GitHub OAuth app."
)

func (*GithubOAuth2Config) PKCESupported() []promoauth.Oauth2PKCEChallengeMethod {
return []promoauth.Oauth2PKCEChallengeMethod{promoauth.PKCEChallengeMethodSha256}
}
Expand Down Expand Up @@ -931,6 +945,13 @@ func (api *API) userOAuth2Github(rw http.ResponseWriter, r *http.Request) {
if len(selectedMemberships) == 0 {
status := http.StatusUnauthorized
msg := "You aren't a member of the authorized Github organizations!"
if api.GithubOAuth2Config.DefaultProviderConfigured {
// The default provider is a GitHub App, so it can only report
// memberships in organizations that have installed it. Without
// this hint, users in an allowed organization see a confusing
// rejection with no way to discover the missing installation.
msg += " " + githubOAuth2DefaultProviderRemediation
}
Comment thread
matifali marked this conversation as resolved.
if api.GithubOAuth2Config.DeviceFlowEnabled {
// In the device flow, the error is rendered client-side.
httpapi.Write(ctx, rw, status, codersdk.Response{
Expand Down Expand Up @@ -977,6 +998,12 @@ func (api *API) userOAuth2Github(rw http.ResponseWriter, r *http.Request) {
}
if allowedTeam == nil {
msg := fmt.Sprintf("You aren't a member of an authorized team in the %v Github organization(s)!", organizationNames)
if api.GithubOAuth2Config.DefaultProviderConfigured {
// Team visibility has the same limitation as org visibility:
// the default GitHub App cannot see teams in organizations
// where it isn't installed.
msg += " " + githubOAuth2DefaultProviderRemediation
}
status := http.StatusUnauthorized
if api.GithubOAuth2Config.DeviceFlowEnabled {
// In the device flow, the error is rendered client-side.
Expand Down
57 changes: 57 additions & 0 deletions coderd/userauth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,63 @@ func TestUserOAuth2Github(t *testing.T) {

resp := oauth2Callback(t, client)
require.Equal(t, http.StatusUnauthorized, resp.StatusCode)
location, err := resp.Location()
require.NoError(t, err)
require.NotContains(t, location.Query().Get("message"), "Coder GitHub app")
})
t.Run("NotInAllowedOrganizationDefaultProvider", func(t *testing.T) {
Comment thread
matifali marked this conversation as resolved.
t.Parallel()
client := coderdtest.New(t, &coderdtest.Options{
GithubOAuth2Config: &coderd.GithubOAuth2Config{
OAuth2Config: &testutil.OAuth2Config{},
DefaultProviderConfigured: true,
AllowOrganizations: []string{"coder"},
ListOrganizationMemberships: func(ctx context.Context, client *http.Client) ([]*github.Membership, error) {
// The default provider is a GitHub App, so it reports no
// memberships for organizations it isn't installed in.
return []*github.Membership{}, nil
},
},
})

resp := oauth2Callback(t, client)
require.Equal(t, http.StatusUnauthorized, resp.StatusCode)
// The error must tell the user how to fix the likely cause: the Coder
// GitHub app isn't installed in the allowed organization.
location, err := resp.Location()
require.NoError(t, err)
require.Contains(t, location.Query().Get("message"), "Coder GitHub app")
require.Contains(t, location.Query().Get("message"), "https://github.com/apps/coder")
})
t.Run("NotInAllowedOrganizationDefaultProviderDeviceFlow", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, &coderdtest.Options{
GithubOAuth2Config: &coderd.GithubOAuth2Config{
OAuth2Config: &testutil.OAuth2Config{},
DefaultProviderConfigured: true,
AllowOrganizations: []string{"coder"},
ListOrganizationMemberships: func(ctx context.Context, client *http.Client) ([]*github.Membership, error) {
return []*github.Membership{}, nil
},
DeviceFlowEnabled: true,
ExchangeDeviceCode: func(_ context.Context, _ string) (*oauth2.Token, error) {
return &oauth2.Token{
AccessToken: "access_token",
RefreshToken: "refresh_token",
Expiry: time.Now().Add(time.Hour),
}, nil
},
},
})

resp := oauth2Callback(t, client)
require.Equal(t, http.StatusUnauthorized, resp.StatusCode)
// In the device flow the error is rendered client-side, so the hint
// must arrive in the response body Detail rather than the redirect.
var apiErr codersdk.Response
require.NoError(t, json.NewDecoder(resp.Body).Decode(&apiErr))
require.Contains(t, apiErr.Detail, "Coder GitHub app")
require.Contains(t, apiErr.Detail, "https://github.com/apps/coder")
})
t.Run("NotInAllowedTeam", func(t *testing.T) {
t.Parallel()
Expand Down
5 changes: 5 additions & 0 deletions docs/admin/users/github-auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ To use the default configuration:
CODER_OAUTH2_GITHUB_ALLOWED_ORGS="your-org"
```

> [!IMPORTANT]
> The default GitHub app can only see memberships in organizations where it is installed.
> If you set `CODER_OAUTH2_GITHUB_ALLOWED_ORGS` without installing the app in each allowed organization, all logins fail with "You aren't a member of the authorized Github organizations!", including the first admin login on a fresh deployment.
> Install the app for each organization at the [Coder app on GitHub](https://github.com/apps/coder/installations/select_target).

## Disable the Default GitHub App

You can disable the default GitHub app by [configuring your own app](#step-1-configure-the-oauth-application-in-github)
Expand Down
Loading