Skip to content

Commit b48a57a

Browse files
committed
server
1 parent f3b89d0 commit b48a57a

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
@@ -677,24 +677,6 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
677677
}
678678
}
679679

680-
if vals.OAuth2.Github.ClientSecret != "" || vals.OAuth2.Github.DeviceFlow.Value() {
681-
options.GithubOAuth2Config, err = configureGithubOAuth2(
682-
oauthInstrument,
683-
vals.AccessURL.Value(),
684-
vals.OAuth2.Github.ClientID.String(),
685-
vals.OAuth2.Github.ClientSecret.String(),
686-
vals.OAuth2.Github.DeviceFlow.Value(),
687-
vals.OAuth2.Github.AllowSignups.Value(),
688-
vals.OAuth2.Github.AllowEveryone.Value(),
689-
vals.OAuth2.Github.AllowedOrgs,
690-
vals.OAuth2.Github.AllowedTeams,
691-
vals.OAuth2.Github.EnterpriseBaseURL.String(),
692-
)
693-
if err != nil {
694-
return xerrors.Errorf("configure github oauth2: %w", err)
695-
}
696-
}
697-
698680
// As OIDC clients can be confidential or public,
699681
// we should only check for a client id being set.
700682
// The underlying library handles the case of no
@@ -782,6 +764,20 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
782764
return xerrors.Errorf("set deployment id: %w", err)
783765
}
784766

767+
githubOAuth2ConfigParams, err := getGithubOAuth2ConfigParams(ctx, options.Database, vals)
768+
if err != nil {
769+
return xerrors.Errorf("get github oauth2 config params: %w", err)
770+
}
771+
if githubOAuth2ConfigParams != nil {
772+
options.GithubOAuth2Config, err = configureGithubOAuth2(
773+
oauthInstrument,
774+
githubOAuth2ConfigParams,
775+
)
776+
if err != nil {
777+
return xerrors.Errorf("configure github oauth2: %w", err)
778+
}
779+
}
780+
785781
options.RuntimeConfig = runtimeconfig.NewManager()
786782

787783
// This should be output before the logs start streaming.
@@ -1832,25 +1828,101 @@ func configureCAPool(tlsClientCAFile string, tlsConfig *tls.Config) error {
18321828
return nil
18331829
}
18341830

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

18641936
endpoint := xgithub.Endpoint
1865-
if enterpriseBaseURL != "" {
1866-
enterpriseURL, err := url.Parse(enterpriseBaseURL)
1937+
if params.enterpriseBaseURL != "" {
1938+
enterpriseURL, err := url.Parse(params.enterpriseBaseURL)
18671939
if err != nil {
18681940
return nil, xerrors.Errorf("parse enterprise base url: %w", err)
18691941
}
@@ -1882,8 +1954,8 @@ func configureGithubOAuth2(instrument *promoauth.Factory, accessURL *url.URL, cl
18821954
}
18831955

18841956
instrumentedOauth := instrument.NewGithub("github-login", &oauth2.Config{
1885-
ClientID: clientID,
1886-
ClientSecret: clientSecret,
1957+
ClientID: params.clientID,
1958+
ClientSecret: params.clientSecret,
18871959
Endpoint: endpoint,
18881960
RedirectURL: redirectURL.String(),
18891961
Scopes: []string{
@@ -1895,17 +1967,17 @@ func configureGithubOAuth2(instrument *promoauth.Factory, accessURL *url.URL, cl
18951967

18961968
createClient := func(client *http.Client, source promoauth.Oauth2Source) (*github.Client, error) {
18971969
client = instrumentedOauth.InstrumentHTTPClient(client, source)
1898-
if enterpriseBaseURL != "" {
1899-
return github.NewEnterpriseClient(enterpriseBaseURL, "", client)
1970+
if params.enterpriseBaseURL != "" {
1971+
return github.NewEnterpriseClient(params.enterpriseBaseURL, "", client)
19001972
}
19011973
return github.NewClient(client), nil
19021974
}
19031975

19041976
var deviceAuth *externalauth.DeviceAuth
1905-
if deviceFlow {
1977+
if params.deviceFlow {
19061978
deviceAuth = &externalauth.DeviceAuth{
19071979
Config: instrumentedOauth,
1908-
ClientID: clientID,
1980+
ClientID: params.clientID,
19091981
TokenURL: endpoint.TokenURL,
19101982
Scopes: []string{"read:user", "read:org", "user:email"},
19111983
CodeURL: endpoint.DeviceAuthURL,
@@ -1914,9 +1986,9 @@ func configureGithubOAuth2(instrument *promoauth.Factory, accessURL *url.URL, cl
19141986

19151987
return &coderd.GithubOAuth2Config{
19161988
OAuth2Config: instrumentedOauth,
1917-
AllowSignups: allowSignups,
1918-
AllowEveryone: allowEveryone,
1919-
AllowOrganizations: allowOrgs,
1989+
AllowSignups: params.allowSignups,
1990+
AllowEveryone: params.allowEveryone,
1991+
AllowOrganizations: params.allowOrgs,
19201992
AllowTeams: allowTeams,
19211993
AuthenticatedUser: func(ctx context.Context, client *http.Client) (*github.User, error) {
19221994
api, err := createClient(client, promoauth.SourceGitAPIAuthUser)
@@ -1955,19 +2027,20 @@ func configureGithubOAuth2(instrument *promoauth.Factory, accessURL *url.URL, cl
19552027
team, _, err := api.Teams.GetTeamMembershipBySlug(ctx, org, teamSlug, username)
19562028
return team, err
19572029
},
1958-
DeviceFlowEnabled: deviceFlow,
2030+
DeviceFlowEnabled: params.deviceFlow,
19592031
ExchangeDeviceCode: func(ctx context.Context, deviceCode string) (*oauth2.Token, error) {
1960-
if !deviceFlow {
2032+
if !params.deviceFlow {
19612033
return nil, xerrors.New("device flow is not enabled")
19622034
}
19632035
return deviceAuth.ExchangeDeviceCode(ctx, deviceCode)
19642036
},
19652037
AuthorizeDevice: func(ctx context.Context) (*codersdk.ExternalAuthDevice, error) {
1966-
if !deviceFlow {
2038+
if !params.deviceFlow {
19672039
return nil, xerrors.New("device flow is not enabled")
19682040
}
19692041
return deviceAuth.AuthorizeDevice(ctx)
19702042
},
2043+
DefaultProviderConfigured: params.clientID == GithubOAuth2DefaultProviderClientID,
19712044
}, nil
19722045
}
19732046

coderd/userauth.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -757,6 +757,8 @@ type GithubOAuth2Config struct {
757757
AllowEveryone bool
758758
AllowOrganizations []string
759759
AllowTeams []GithubOAuth2Team
760+
761+
DefaultProviderConfigured bool
760762
}
761763

762764
func (c *GithubOAuth2Config) Exchange(ctx context.Context, code string, opts ...oauth2.AuthCodeOption) (*oauth2.Token, error) {
@@ -798,7 +800,10 @@ func (api *API) userAuthMethods(rw http.ResponseWriter, r *http.Request) {
798800
Password: codersdk.AuthMethod{
799801
Enabled: !api.DeploymentValues.DisablePasswordAuth.Value(),
800802
},
801-
Github: codersdk.AuthMethod{Enabled: api.GithubOAuth2Config != nil},
803+
Github: codersdk.GithubAuthMethod{
804+
Enabled: api.GithubOAuth2Config != nil,
805+
DefaultProviderConfigured: api.GithubOAuth2Config != nil && api.GithubOAuth2Config.DefaultProviderConfigured,
806+
},
802807
OIDC: codersdk.OIDCAuthMethod{
803808
AuthMethod: codersdk.AuthMethod{Enabled: api.OIDCConfig != nil},
804809
SignInText: signInText,

0 commit comments

Comments
 (0)