Skip to content

feat: allow external services to be authable #9996

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 21 commits into from
Oct 3, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Allow accepting any type as string
  • Loading branch information
kylecarbs committed Oct 2, 2023
commit 0fd1f7a7af4ee17d5dcac3801eba873330391343
2 changes: 1 addition & 1 deletion cli/cliui/externalauth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func TestExternalAuth(t *testing.T) {
return []codersdk.TemplateVersionExternalAuth{{
ID: "github",
DisplayName: "GitHub",
Type: codersdk.ExternalAuthProviderGitHub,
Type: codersdk.EnhancedExternalAuthProviderGitHub.String(),
Authenticated: fetched.Load(),
AuthenticateURL: "https://example.com/gitauth/github",
}}, nil
Expand Down
2 changes: 1 addition & 1 deletion cli/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -613,7 +613,7 @@ func TestCreateWithGitAuth(t *testing.T) {
OAuth2Config: &testutil.OAuth2Config{},
ID: "github",
Regex: regexp.MustCompile(`github\.com`),
Type: codersdk.ExternalAuthProviderGitHub,
Type: codersdk.EnhancedExternalAuthProviderGitHub.String(),
DisplayName: "GitHub",
}},
IncludeProvisionerDaemon: true,
Expand Down
4 changes: 2 additions & 2 deletions cmd/cliui/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -336,12 +336,12 @@ func main() {
count.Add(1)
return []codersdk.TemplateVersionExternalAuth{{
ID: "github",
Type: codersdk.ExternalAuthProviderGitHub,
Type: codersdk.EnhancedExternalAuthProviderGitHub.String(),
Authenticated: githubAuthed.Load(),
AuthenticateURL: "https://example.com/gitauth/github?redirect=" + url.QueryEscape("/gitauth?notify"),
}, {
ID: "gitlab",
Type: codersdk.ExternalAuthProviderGitLab,
Type: codersdk.EnhancedExternalAuthProviderGitLab.String(),
Authenticated: gitlabAuthed.Load(),
AuthenticateURL: "https://example.com/gitauth/gitlab?redirect=" + url.QueryEscape("/gitauth?notify"),
}}, nil
Expand Down
50 changes: 18 additions & 32 deletions coderd/externalauth/externalauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ type Config struct {
// ID is a unique identifier for the authenticator.
ID string
// Type is the type of provider.
Type codersdk.ExternalAuthProvider
Type string
// DeviceAuth is set if the provider uses the device flow.
DeviceAuth *DeviceAuth
// DisplayName is the name of the provider to display to the user.
Expand Down Expand Up @@ -117,7 +117,7 @@ validate:
// to the read replica in time.
//
// We do an exponential backoff here to give the write time to propagate.
if c.Type == codersdk.ExternalAuthProviderGitHub && r.Wait(retryCtx) {
if c.Type == string(codersdk.EnhancedExternalAuthProviderGitHub) && r.Wait(retryCtx) {
goto validate
}
// The token is no longer valid!
Expand Down Expand Up @@ -173,7 +173,7 @@ func (c *Config) ValidateToken(ctx context.Context, token string) (bool, *coders
}

var user *codersdk.ExternalAuthUser
if c.Type == codersdk.ExternalAuthProviderGitHub {
if c.Type == string(codersdk.EnhancedExternalAuthProviderGitHub) {
var ghUser github.User
err = json.NewDecoder(res.Body).Decode(&ghUser)
if err == nil {
Expand Down Expand Up @@ -219,7 +219,7 @@ func (c *Config) AppInstallations(ctx context.Context, token string) ([]codersdk
return nil, false, nil
}
installs := []codersdk.ExternalAuthAppInstallation{}
if c.Type == codersdk.ExternalAuthProviderGitHub {
if c.Type == string(codersdk.EnhancedExternalAuthProviderGitHub) {
var ghInstalls struct {
Installations []*github.Installation `json:"installations"`
}
Expand Down Expand Up @@ -368,24 +368,10 @@ func ConvertConfig(entries []codersdk.ExternalAuthConfig, accessURL *url.URL) ([
for _, entry := range entries {
entry := entry

var typ codersdk.ExternalAuthProvider
switch codersdk.ExternalAuthProvider(entry.Type) {
case codersdk.ExternalAuthProviderAzureDevops:
typ = codersdk.ExternalAuthProviderAzureDevops
case codersdk.ExternalAuthProviderBitBucket:
typ = codersdk.ExternalAuthProviderBitBucket
case codersdk.ExternalAuthProviderGitHub:
typ = codersdk.ExternalAuthProviderGitHub
case codersdk.ExternalAuthProviderGitLab:
typ = codersdk.ExternalAuthProviderGitLab
default:
typ = codersdk.ExternalAuthProvider(entry.Type)
}

// Applies defaults to the config entry.
// This allows users to very simply state that they type is "GitHub",
// apply their client secret and ID, and have the UI appear nicely.
applyDefaultsToConfig(typ, &entry)
applyDefaultsToConfig(&entry)

valid := httpapi.NameValid(entry.ID)
if valid != nil {
Expand All @@ -400,8 +386,8 @@ func ConvertConfig(entries []codersdk.ExternalAuthConfig, accessURL *url.URL) ([

_, exists := ids[entry.ID]
if exists {
if entry.ID == string(typ) {
return nil, xerrors.Errorf("multiple %s external auth providers provided. you must specify a unique id for each", typ)
if entry.ID == entry.Type {
return nil, xerrors.Errorf("multiple %s external auth providers provided. you must specify a unique id for each", entry.Type)
}
return nil, xerrors.Errorf("multiple external auth providers exist with the id %q. specify a unique id for each", entry.ID)
}
Expand Down Expand Up @@ -433,15 +419,15 @@ func ConvertConfig(entries []codersdk.ExternalAuthConfig, accessURL *url.URL) ([

var oauthConfig OAuth2Config = oc
// Azure DevOps uses JWT token authentication!
if typ == codersdk.ExternalAuthProviderAzureDevops {
if entry.Type == string(codersdk.EnhancedExternalAuthProviderAzureDevops) {
oauthConfig = &jwtConfig{oc}
}

cfg := &Config{
OAuth2Config: oauthConfig,
ID: entry.ID,
Regex: regex,
Type: typ,
Type: entry.Type,
NoRefresh: entry.NoRefresh,
ValidateURL: entry.ValidateURL,
AppInstallationsURL: entry.AppInstallationsURL,
Expand All @@ -468,8 +454,8 @@ func ConvertConfig(entries []codersdk.ExternalAuthConfig, accessURL *url.URL) ([
}

// applyDefaultsToConfig applies defaults to the config entry.
func applyDefaultsToConfig(typ codersdk.ExternalAuthProvider, config *codersdk.ExternalAuthConfig) {
defaults := defaults[typ]
func applyDefaultsToConfig(config *codersdk.ExternalAuthConfig) {
defaults := defaults[codersdk.EnhancedExternalAuthProvider(config.Type)]
if config.AuthURL == "" {
config.AuthURL = defaults.AuthURL
}
Expand Down Expand Up @@ -503,27 +489,27 @@ func applyDefaultsToConfig(typ codersdk.ExternalAuthProvider, config *codersdk.E

// Apply defaults if it's still empty...
if config.ID == "" {
config.ID = string(typ)
config.ID = config.Type
}
if config.DisplayName == "" {
config.DisplayName = string(typ)
config.DisplayName = config.Type
}
if config.DisplayIcon == "" {
// This is a key emoji.
config.DisplayIcon = "/emojis/1f511.png"
}
}

var defaults = map[codersdk.ExternalAuthProvider]codersdk.ExternalAuthConfig{
codersdk.ExternalAuthProviderAzureDevops: {
var defaults = map[codersdk.EnhancedExternalAuthProvider]codersdk.ExternalAuthConfig{
codersdk.EnhancedExternalAuthProviderAzureDevops: {
AuthURL: "https://app.vssps.visualstudio.com/oauth2/authorize",
TokenURL: "https://app.vssps.visualstudio.com/oauth2/token",
DisplayName: "Azure DevOps",
DisplayIcon: "/icon/azure-devops.svg",
Regex: `^(https?://)?dev\.azure\.com(/.*)?$`,
Scopes: []string{"vso.code_write"},
},
codersdk.ExternalAuthProviderBitBucket: {
codersdk.EnhancedExternalAuthProviderBitBucket: {
AuthURL: "https://bitbucket.org/site/oauth2/authorize",
TokenURL: "https://bitbucket.org/site/oauth2/access_token",
ValidateURL: "https://api.bitbucket.org/2.0/user",
Expand All @@ -532,7 +518,7 @@ var defaults = map[codersdk.ExternalAuthProvider]codersdk.ExternalAuthConfig{
Regex: `^(https?://)?bitbucket\.org(/.*)?$`,
Scopes: []string{"account", "repository:write"},
},
codersdk.ExternalAuthProviderGitLab: {
codersdk.EnhancedExternalAuthProviderGitLab: {
AuthURL: "https://gitlab.com/oauth/authorize",
TokenURL: "https://gitlab.com/oauth/token",
ValidateURL: "https://gitlab.com/oauth/token/info",
Expand All @@ -541,7 +527,7 @@ var defaults = map[codersdk.ExternalAuthProvider]codersdk.ExternalAuthConfig{
Regex: `^(https?://)?gitlab\.com(/.*)?$`,
Scopes: []string{"write_repository"},
},
codersdk.ExternalAuthProviderGitHub: {
codersdk.EnhancedExternalAuthProviderGitHub: {
AuthURL: xgithub.Endpoint.AuthURL,
TokenURL: xgithub.Endpoint.TokenURL,
ValidateURL: "https://api.github.com/user",
Expand Down
20 changes: 10 additions & 10 deletions coderd/externalauth/externalauth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ func TestRefreshToken(t *testing.T) {
}),
},
GitConfigOpt: func(cfg *externalauth.Config) {
cfg.Type = codersdk.ExternalAuthProviderGitHub
cfg.Type = codersdk.EnhancedExternalAuthProviderGitHub.String()
},
})

Expand Down Expand Up @@ -206,7 +206,7 @@ func TestRefreshToken(t *testing.T) {
}),
},
GitConfigOpt: func(cfg *externalauth.Config) {
cfg.Type = codersdk.ExternalAuthProviderGitHub
cfg.Type = codersdk.EnhancedExternalAuthProviderGitHub.String()
},
})

Expand Down Expand Up @@ -237,7 +237,7 @@ func TestRefreshToken(t *testing.T) {
}),
},
GitConfigOpt: func(cfg *externalauth.Config) {
cfg.Type = codersdk.ExternalAuthProviderGitHub
cfg.Type = codersdk.EnhancedExternalAuthProviderGitHub.String()
},
DB: db,
})
Expand Down Expand Up @@ -272,32 +272,32 @@ func TestConvertYAML(t *testing.T) {
}{{
Name: "InvalidID",
Input: []codersdk.ExternalAuthConfig{{
Type: string(codersdk.ExternalAuthProviderGitHub),
Type: string(codersdk.EnhancedExternalAuthProviderGitHub),
ID: "$hi$",
}},
Error: "doesn't have a valid id",
}, {
Name: "NoClientID",
Input: []codersdk.ExternalAuthConfig{{
Type: string(codersdk.ExternalAuthProviderGitHub),
Type: string(codersdk.EnhancedExternalAuthProviderGitHub),
}},
Error: "client_id must be provided",
}, {
Name: "DuplicateType",
Input: []codersdk.ExternalAuthConfig{{
Type: string(codersdk.ExternalAuthProviderGitHub),
Type: string(codersdk.EnhancedExternalAuthProviderGitHub),
ClientID: "example",
ClientSecret: "example",
}, {
Type: string(codersdk.ExternalAuthProviderGitHub),
Type: string(codersdk.EnhancedExternalAuthProviderGitHub),
ClientID: "example-2",
ClientSecret: "example-2",
}},
Error: "multiple github external auth providers provided",
}, {
Name: "InvalidRegex",
Input: []codersdk.ExternalAuthConfig{{
Type: string(codersdk.ExternalAuthProviderGitHub),
Type: string(codersdk.EnhancedExternalAuthProviderGitHub),
ClientID: "example",
ClientSecret: "example",
Regex: `\K`,
Expand All @@ -306,7 +306,7 @@ func TestConvertYAML(t *testing.T) {
}, {
Name: "NoDeviceURL",
Input: []codersdk.ExternalAuthConfig{{
Type: string(codersdk.ExternalAuthProviderGitLab),
Type: string(codersdk.EnhancedExternalAuthProviderGitLab),
ClientID: "example",
ClientSecret: "example",
DeviceFlow: true,
Expand All @@ -329,7 +329,7 @@ func TestConvertYAML(t *testing.T) {
t.Run("CustomScopesAndEndpoint", func(t *testing.T) {
t.Parallel()
config, err := externalauth.ConvertConfig([]codersdk.ExternalAuthConfig{{
Type: string(codersdk.ExternalAuthProviderGitLab),
Type: string(codersdk.EnhancedExternalAuthProviderGitLab),
ClientID: "id",
ClientSecret: "secret",
AuthURL: "https://auth.com",
Expand Down
20 changes: 10 additions & 10 deletions coderd/externalauth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func TestExternalAuthByID(t *testing.T) {
ExternalAuthConfigs: []*externalauth.Config{{
ID: "test",
OAuth2Config: &testutil.OAuth2Config{},
Type: codersdk.ExternalAuthProviderGitHub,
Type: codersdk.EnhancedExternalAuthProviderGitHub.String(),
}},
})
coderdtest.CreateFirstUser(t, client)
Expand All @@ -51,7 +51,7 @@ func TestExternalAuthByID(t *testing.T) {
ID: "test",
OAuth2Config: &testutil.OAuth2Config{},
// AzureDevops doesn't have a user endpoint!
Type: codersdk.ExternalAuthProviderAzureDevops,
Type: codersdk.EnhancedExternalAuthProviderAzureDevops.String(),
}},
})
coderdtest.CreateFirstUser(t, client)
Expand All @@ -75,7 +75,7 @@ func TestExternalAuthByID(t *testing.T) {
ID: "test",
ValidateURL: validateSrv.URL,
OAuth2Config: &testutil.OAuth2Config{},
Type: codersdk.ExternalAuthProviderGitHub,
Type: codersdk.EnhancedExternalAuthProviderGitHub.String(),
}},
})
coderdtest.CreateFirstUser(t, client)
Expand Down Expand Up @@ -116,7 +116,7 @@ func TestExternalAuthByID(t *testing.T) {
ValidateURL: srv.URL + "/user",
AppInstallationsURL: srv.URL + "/installs",
OAuth2Config: &testutil.OAuth2Config{},
Type: codersdk.ExternalAuthProviderGitHub,
Type: codersdk.EnhancedExternalAuthProviderGitHub.String(),
}},
})
coderdtest.CreateFirstUser(t, client)
Expand Down Expand Up @@ -249,7 +249,7 @@ func TestGitAuthCallback(t *testing.T) {
OAuth2Config: &testutil.OAuth2Config{},
ID: "github",
Regex: regexp.MustCompile(`github\.com`),
Type: codersdk.ExternalAuthProviderGitHub,
Type: codersdk.EnhancedExternalAuthProviderGitHub.String(),
}},
})
user := coderdtest.CreateFirstUser(t, client)
Expand Down Expand Up @@ -278,7 +278,7 @@ func TestGitAuthCallback(t *testing.T) {
OAuth2Config: &testutil.OAuth2Config{},
ID: "github",
Regex: regexp.MustCompile(`github\.com`),
Type: codersdk.ExternalAuthProviderGitHub,
Type: codersdk.EnhancedExternalAuthProviderGitHub.String(),
}},
})
resp := coderdtest.RequestExternalAuthCallback(t, "github", client)
Expand All @@ -292,7 +292,7 @@ func TestGitAuthCallback(t *testing.T) {
OAuth2Config: &testutil.OAuth2Config{},
ID: "github",
Regex: regexp.MustCompile(`github\.com`),
Type: codersdk.ExternalAuthProviderGitHub,
Type: codersdk.EnhancedExternalAuthProviderGitHub.String(),
}},
})
_ = coderdtest.CreateFirstUser(t, client)
Expand All @@ -319,7 +319,7 @@ func TestGitAuthCallback(t *testing.T) {
OAuth2Config: &testutil.OAuth2Config{},
ID: "github",
Regex: regexp.MustCompile(`github\.com`),
Type: codersdk.ExternalAuthProviderGitHub,
Type: codersdk.EnhancedExternalAuthProviderGitHub.String(),
}},
})
user := coderdtest.CreateFirstUser(t, client)
Expand Down Expand Up @@ -376,7 +376,7 @@ func TestGitAuthCallback(t *testing.T) {
},
ID: "github",
Regex: regexp.MustCompile(`github\.com`),
Type: codersdk.ExternalAuthProviderGitHub,
Type: codersdk.EnhancedExternalAuthProviderGitHub.String(),
NoRefresh: true,
}},
})
Expand Down Expand Up @@ -420,7 +420,7 @@ func TestGitAuthCallback(t *testing.T) {
OAuth2Config: &testutil.OAuth2Config{},
ID: "github",
Regex: regexp.MustCompile(`github\.com`),
Type: codersdk.ExternalAuthProviderGitHub,
Type: codersdk.EnhancedExternalAuthProviderGitHub.String(),
}},
})
user := coderdtest.CreateFirstUser(t, client)
Expand Down
2 changes: 1 addition & 1 deletion coderd/templateversions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ func TestTemplateVersionsExternalAuth(t *testing.T) {
OAuth2Config: &testutil.OAuth2Config{},
ID: "github",
Regex: regexp.MustCompile(`github\.com`),
Type: codersdk.ExternalAuthProviderGitHub,
Type: codersdk.EnhancedExternalAuthProviderGitHub.String(),
}},
})
user := coderdtest.CreateFirstUser(t, client)
Expand Down
Loading