Skip to content

Commit bd56131

Browse files
committed
server
1 parent 00606bf commit bd56131

File tree

2 files changed

+120
-42
lines changed

2 files changed

+120
-42
lines changed

cli/server.go

Lines changed: 114 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -688,24 +688,6 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
688688
}
689689
}
690690

691-
if vals.OAuth2.Github.ClientSecret != "" || vals.OAuth2.Github.DeviceFlow.Value() {
692-
options.GithubOAuth2Config, err = configureGithubOAuth2(
693-
oauthInstrument,
694-
vals.AccessURL.Value(),
695-
vals.OAuth2.Github.ClientID.String(),
696-
vals.OAuth2.Github.ClientSecret.String(),
697-
vals.OAuth2.Github.DeviceFlow.Value(),
698-
vals.OAuth2.Github.AllowSignups.Value(),
699-
vals.OAuth2.Github.AllowEveryone.Value(),
700-
vals.OAuth2.Github.AllowedOrgs,
701-
vals.OAuth2.Github.AllowedTeams,
702-
vals.OAuth2.Github.EnterpriseBaseURL.String(),
703-
)
704-
if err != nil {
705-
return xerrors.Errorf("configure github oauth2: %w", err)
706-
}
707-
}
708-
709691
// As OIDC clients can be confidential or public,
710692
// we should only check for a client id being set.
711693
// The underlying library handles the case of no
@@ -793,6 +775,20 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
793775
return xerrors.Errorf("set deployment id: %w", err)
794776
}
795777

778+
githubOAuth2ConfigParams, err := getGithubOAuth2ConfigParams(ctx, options.Database, vals)
779+
if err != nil {
780+
return xerrors.Errorf("get github oauth2 config params: %w", err)
781+
}
782+
if githubOAuth2ConfigParams != nil {
783+
options.GithubOAuth2Config, err = configureGithubOAuth2(
784+
oauthInstrument,
785+
githubOAuth2ConfigParams,
786+
)
787+
if err != nil {
788+
return xerrors.Errorf("configure github oauth2: %w", err)
789+
}
790+
}
791+
796792
options.RuntimeConfig = runtimeconfig.NewManager()
797793

798794
// This should be output before the logs start streaming.
@@ -1843,25 +1839,101 @@ func configureCAPool(tlsClientCAFile string, tlsConfig *tls.Config) error {
18431839
return nil
18441840
}
18451841

1846-
// TODO: convert the argument list to a struct, it's easy to mix up the order of the arguments
1847-
//
1842+
const (
1843+
// Client ID for https://github.com/apps/coder
1844+
GithubOAuth2DefaultProviderClientID = "Iv1.6a2b4b4aec4f4fe7"
1845+
GithubOAuth2DefaultProviderAllowEveryone = true
1846+
GithubOAuth2DefaultProviderDeviceFlow = true
1847+
)
1848+
1849+
type githubOAuth2ConfigParams struct {
1850+
accessURL *url.URL
1851+
clientID string
1852+
clientSecret string
1853+
deviceFlow bool
1854+
allowSignups bool
1855+
allowEveryone bool
1856+
allowOrgs []string
1857+
rawTeams []string
1858+
enterpriseBaseURL string
1859+
}
1860+
1861+
func getGithubOAuth2ConfigParams(ctx context.Context, db database.Store, vals *codersdk.DeploymentValues) (*githubOAuth2ConfigParams, error) {
1862+
params := githubOAuth2ConfigParams{
1863+
accessURL: vals.AccessURL.Value(),
1864+
clientID: vals.OAuth2.Github.ClientID.String(),
1865+
clientSecret: vals.OAuth2.Github.ClientSecret.String(),
1866+
deviceFlow: vals.OAuth2.Github.DeviceFlow.Value(),
1867+
allowSignups: vals.OAuth2.Github.AllowSignups.Value(),
1868+
allowEveryone: vals.OAuth2.Github.AllowEveryone.Value(),
1869+
allowOrgs: vals.OAuth2.Github.AllowedOrgs.Value(),
1870+
rawTeams: vals.OAuth2.Github.AllowedTeams.Value(),
1871+
enterpriseBaseURL: vals.OAuth2.Github.EnterpriseBaseURL.String(),
1872+
}
1873+
1874+
// If the user manually configured the GitHub OAuth2 provider,
1875+
// we won't add the default configuration.
1876+
if params.clientID != "" || params.clientSecret != "" || params.enterpriseBaseURL != "" {
1877+
return &params, nil
1878+
}
1879+
1880+
// Check if the user manually disabled the default GitHub OAuth2 provider.
1881+
if !vals.OAuth2.Github.DefaultProviderEnable.Value() {
1882+
return nil, nil //nolint:nilnil
1883+
}
1884+
1885+
// Check if the deployment is eligible for the default GitHub OAuth2 provider.
1886+
// We want to enable it only for new deployments, and avoid enabling it
1887+
// if a deployment was upgraded from an older version.
1888+
// nolint:gocritic // Requires system privileges
1889+
defaultEligible, err := db.GetOAuth2GithubDefaultEligible(dbauthz.AsSystemRestricted(ctx))
1890+
if err != nil && !errors.Is(err, sql.ErrNoRows) {
1891+
return nil, xerrors.Errorf("get github default eligible: %w", err)
1892+
}
1893+
defaultEligibleNotSet := errors.Is(err, sql.ErrNoRows)
1894+
1895+
if defaultEligibleNotSet {
1896+
// nolint:gocritic // User count requires system privileges
1897+
userCount, err := db.GetUserCount(dbauthz.AsSystemRestricted(ctx))
1898+
if err != nil {
1899+
return nil, xerrors.Errorf("get user count: %w", err)
1900+
}
1901+
// We check if a deployment is new by checking if it has any users.
1902+
defaultEligible = userCount == 0
1903+
// nolint:gocritic // Requires system privileges
1904+
if err := db.UpsertOAuth2GithubDefaultEligible(dbauthz.AsSystemRestricted(ctx), defaultEligible); err != nil {
1905+
return nil, xerrors.Errorf("upsert github default eligible: %w", err)
1906+
}
1907+
}
1908+
1909+
if !defaultEligible {
1910+
return nil, nil //nolint:nilnil
1911+
}
1912+
1913+
params.clientID = GithubOAuth2DefaultProviderClientID
1914+
params.allowEveryone = GithubOAuth2DefaultProviderAllowEveryone
1915+
params.deviceFlow = GithubOAuth2DefaultProviderDeviceFlow
1916+
1917+
return &params, nil
1918+
}
1919+
18481920
//nolint:revive // Ignore flag-parameter: parameter 'allowEveryone' seems to be a control flag, avoid control coupling (revive)
1849-
func configureGithubOAuth2(instrument *promoauth.Factory, accessURL *url.URL, clientID, clientSecret string, deviceFlow, allowSignups, allowEveryone bool, allowOrgs []string, rawTeams []string, enterpriseBaseURL string) (*coderd.GithubOAuth2Config, error) {
1850-
redirectURL, err := accessURL.Parse("/api/v2/users/oauth2/github/callback")
1921+
func configureGithubOAuth2(instrument *promoauth.Factory, params *githubOAuth2ConfigParams) (*coderd.GithubOAuth2Config, error) {
1922+
redirectURL, err := params.accessURL.Parse("/api/v2/users/oauth2/github/callback")
18511923
if err != nil {
18521924
return nil, xerrors.Errorf("parse github oauth callback url: %w", err)
18531925
}
1854-
if allowEveryone && len(allowOrgs) > 0 {
1926+
if params.allowEveryone && len(params.allowOrgs) > 0 {
18551927
return nil, xerrors.New("allow everyone and allowed orgs cannot be used together")
18561928
}
1857-
if allowEveryone && len(rawTeams) > 0 {
1929+
if params.allowEveryone && len(params.rawTeams) > 0 {
18581930
return nil, xerrors.New("allow everyone and allowed teams cannot be used together")
18591931
}
1860-
if !allowEveryone && len(allowOrgs) == 0 {
1932+
if !params.allowEveryone && len(params.allowOrgs) == 0 {
18611933
return nil, xerrors.New("allowed orgs is empty: must specify at least one org or allow everyone")
18621934
}
1863-
allowTeams := make([]coderd.GithubOAuth2Team, 0, len(rawTeams))
1864-
for _, rawTeam := range rawTeams {
1935+
allowTeams := make([]coderd.GithubOAuth2Team, 0, len(params.rawTeams))
1936+
for _, rawTeam := range params.rawTeams {
18651937
parts := strings.SplitN(rawTeam, "/", 2)
18661938
if len(parts) != 2 {
18671939
return nil, xerrors.Errorf("github team allowlist is formatted incorrectly. got %s; wanted <organization>/<team>", rawTeam)
@@ -1873,8 +1945,8 @@ func configureGithubOAuth2(instrument *promoauth.Factory, accessURL *url.URL, cl
18731945
}
18741946

18751947
endpoint := xgithub.Endpoint
1876-
if enterpriseBaseURL != "" {
1877-
enterpriseURL, err := url.Parse(enterpriseBaseURL)
1948+
if params.enterpriseBaseURL != "" {
1949+
enterpriseURL, err := url.Parse(params.enterpriseBaseURL)
18781950
if err != nil {
18791951
return nil, xerrors.Errorf("parse enterprise base url: %w", err)
18801952
}
@@ -1893,8 +1965,8 @@ func configureGithubOAuth2(instrument *promoauth.Factory, accessURL *url.URL, cl
18931965
}
18941966

18951967
instrumentedOauth := instrument.NewGithub("github-login", &oauth2.Config{
1896-
ClientID: clientID,
1897-
ClientSecret: clientSecret,
1968+
ClientID: params.clientID,
1969+
ClientSecret: params.clientSecret,
18981970
Endpoint: endpoint,
18991971
RedirectURL: redirectURL.String(),
19001972
Scopes: []string{
@@ -1906,17 +1978,17 @@ func configureGithubOAuth2(instrument *promoauth.Factory, accessURL *url.URL, cl
19061978

19071979
createClient := func(client *http.Client, source promoauth.Oauth2Source) (*github.Client, error) {
19081980
client = instrumentedOauth.InstrumentHTTPClient(client, source)
1909-
if enterpriseBaseURL != "" {
1910-
return github.NewEnterpriseClient(enterpriseBaseURL, "", client)
1981+
if params.enterpriseBaseURL != "" {
1982+
return github.NewEnterpriseClient(params.enterpriseBaseURL, "", client)
19111983
}
19121984
return github.NewClient(client), nil
19131985
}
19141986

19151987
var deviceAuth *externalauth.DeviceAuth
1916-
if deviceFlow {
1988+
if params.deviceFlow {
19171989
deviceAuth = &externalauth.DeviceAuth{
19181990
Config: instrumentedOauth,
1919-
ClientID: clientID,
1991+
ClientID: params.clientID,
19201992
TokenURL: endpoint.TokenURL,
19211993
Scopes: []string{"read:user", "read:org", "user:email"},
19221994
CodeURL: endpoint.DeviceAuthURL,
@@ -1925,9 +1997,9 @@ func configureGithubOAuth2(instrument *promoauth.Factory, accessURL *url.URL, cl
19251997

19261998
return &coderd.GithubOAuth2Config{
19271999
OAuth2Config: instrumentedOauth,
1928-
AllowSignups: allowSignups,
1929-
AllowEveryone: allowEveryone,
1930-
AllowOrganizations: allowOrgs,
2000+
AllowSignups: params.allowSignups,
2001+
AllowEveryone: params.allowEveryone,
2002+
AllowOrganizations: params.allowOrgs,
19312003
AllowTeams: allowTeams,
19322004
AuthenticatedUser: func(ctx context.Context, client *http.Client) (*github.User, error) {
19332005
api, err := createClient(client, promoauth.SourceGitAPIAuthUser)
@@ -1966,19 +2038,20 @@ func configureGithubOAuth2(instrument *promoauth.Factory, accessURL *url.URL, cl
19662038
team, _, err := api.Teams.GetTeamMembershipBySlug(ctx, org, teamSlug, username)
19672039
return team, err
19682040
},
1969-
DeviceFlowEnabled: deviceFlow,
2041+
DeviceFlowEnabled: params.deviceFlow,
19702042
ExchangeDeviceCode: func(ctx context.Context, deviceCode string) (*oauth2.Token, error) {
1971-
if !deviceFlow {
2043+
if !params.deviceFlow {
19722044
return nil, xerrors.New("device flow is not enabled")
19732045
}
19742046
return deviceAuth.ExchangeDeviceCode(ctx, deviceCode)
19752047
},
19762048
AuthorizeDevice: func(ctx context.Context) (*codersdk.ExternalAuthDevice, error) {
1977-
if !deviceFlow {
2049+
if !params.deviceFlow {
19782050
return nil, xerrors.New("device flow is not enabled")
19792051
}
19802052
return deviceAuth.AuthorizeDevice(ctx)
19812053
},
2054+
DefaultProviderConfigured: params.clientID == GithubOAuth2DefaultProviderClientID,
19822055
}, nil
19832056
}
19842057

coderd/userauth.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -765,6 +765,8 @@ type GithubOAuth2Config struct {
765765
AllowEveryone bool
766766
AllowOrganizations []string
767767
AllowTeams []GithubOAuth2Team
768+
769+
DefaultProviderConfigured bool
768770
}
769771

770772
func (c *GithubOAuth2Config) Exchange(ctx context.Context, code string, opts ...oauth2.AuthCodeOption) (*oauth2.Token, error) {
@@ -806,7 +808,10 @@ func (api *API) userAuthMethods(rw http.ResponseWriter, r *http.Request) {
806808
Password: codersdk.AuthMethod{
807809
Enabled: !api.DeploymentValues.DisablePasswordAuth.Value(),
808810
},
809-
Github: codersdk.AuthMethod{Enabled: api.GithubOAuth2Config != nil},
811+
Github: codersdk.GithubAuthMethod{
812+
Enabled: api.GithubOAuth2Config != nil,
813+
DefaultProviderConfigured: api.GithubOAuth2Config != nil && api.GithubOAuth2Config.DefaultProviderConfigured,
814+
},
810815
OIDC: codersdk.OIDCAuthMethod{
811816
AuthMethod: codersdk.AuthMethod{Enabled: api.OIDCConfig != nil},
812817
SignInText: signInText,

0 commit comments

Comments
 (0)