mirror of
https://github.com/coder/coder.git
synced 2025-07-03 16:13:58 +00:00
* Add git auth providers schema * Pipe git auth providers to the schema * Add git auth providers to the API * Add gitauth endpoint to query authenticated state * Add endpoint to query git state * Use BroadcastChannel to automatically authenticate with Git * Add error validation for submitting the create workspace form * Fix panic on template dry-run * Add tests for the template version Git auth endpoint * Show error if no gitauth is configured * Add gitauth to cliui * Fix unused method receiver * Fix linting errors * Fix dbauthz querier test * Fix make gen * Add JavaScript test for git auth * Fix bad error message * Fix provisionerd test race See https://github.com/coder/coder/actions/runs/4277960646/jobs/7447232814 * Fix requested changes * Add comment to CreateWorkspacePageView
129 lines
3.8 KiB
Go
129 lines
3.8 KiB
Go
package gitauth
|
|
|
|
import (
|
|
"fmt"
|
|
"net/url"
|
|
"regexp"
|
|
|
|
"golang.org/x/oauth2"
|
|
"golang.org/x/xerrors"
|
|
|
|
"github.com/coder/coder/coderd/httpapi"
|
|
"github.com/coder/coder/coderd/httpmw"
|
|
"github.com/coder/coder/codersdk"
|
|
)
|
|
|
|
// Config is used for authentication for Git operations.
|
|
type Config struct {
|
|
httpmw.OAuth2Config
|
|
// ID is a unique identifier for the authenticator.
|
|
ID string
|
|
// Regex is a regexp that URLs will match against.
|
|
Regex *regexp.Regexp
|
|
// Type is the type of provider.
|
|
Type codersdk.GitProvider
|
|
// NoRefresh stops Coder from using the refresh token
|
|
// to renew the access token.
|
|
//
|
|
// Some organizations have security policies that require
|
|
// re-authentication for every token.
|
|
NoRefresh bool
|
|
// ValidateURL ensures an access token is valid before
|
|
// returning it to the user. If omitted, tokens will
|
|
// not be validated before being returned.
|
|
ValidateURL string
|
|
}
|
|
|
|
// ConvertConfig converts the SDK configuration entry format
|
|
// to the parsed and ready-to-consume in coderd provider type.
|
|
func ConvertConfig(entries []codersdk.GitAuthConfig, accessURL *url.URL) ([]*Config, error) {
|
|
ids := map[string]struct{}{}
|
|
configs := []*Config{}
|
|
for _, entry := range entries {
|
|
var typ codersdk.GitProvider
|
|
switch codersdk.GitProvider(entry.Type) {
|
|
case codersdk.GitProviderAzureDevops:
|
|
typ = codersdk.GitProviderAzureDevops
|
|
case codersdk.GitProviderBitBucket:
|
|
typ = codersdk.GitProviderBitBucket
|
|
case codersdk.GitProviderGitHub:
|
|
typ = codersdk.GitProviderGitHub
|
|
case codersdk.GitProviderGitLab:
|
|
typ = codersdk.GitProviderGitLab
|
|
default:
|
|
return nil, xerrors.Errorf("unknown git provider type: %q", entry.Type)
|
|
}
|
|
if entry.ID == "" {
|
|
// Default to the type.
|
|
entry.ID = string(typ)
|
|
}
|
|
if valid := httpapi.NameValid(entry.ID); valid != nil {
|
|
return nil, xerrors.Errorf("git auth provider %q doesn't have a valid id: %w", entry.ID, valid)
|
|
}
|
|
|
|
_, exists := ids[entry.ID]
|
|
if exists {
|
|
if entry.ID == string(typ) {
|
|
return nil, xerrors.Errorf("multiple %s git auth providers provided. you must specify a unique id for each", typ)
|
|
}
|
|
return nil, xerrors.Errorf("multiple git providers exist with the id %q. specify a unique id for each", entry.ID)
|
|
}
|
|
ids[entry.ID] = struct{}{}
|
|
|
|
if entry.ClientID == "" {
|
|
return nil, xerrors.Errorf("%q git auth provider: client_id must be provided", entry.ID)
|
|
}
|
|
if entry.ClientSecret == "" {
|
|
return nil, xerrors.Errorf("%q git auth provider: client_secret must be provided", entry.ID)
|
|
}
|
|
authRedirect, err := accessURL.Parse(fmt.Sprintf("/gitauth/%s/callback", entry.ID))
|
|
if err != nil {
|
|
return nil, xerrors.Errorf("parse gitauth callback url: %w", err)
|
|
}
|
|
regex := regex[typ]
|
|
if entry.Regex != "" {
|
|
regex, err = regexp.Compile(entry.Regex)
|
|
if err != nil {
|
|
return nil, xerrors.Errorf("compile regex for git auth provider %q: %w", entry.ID, entry.Regex)
|
|
}
|
|
}
|
|
|
|
oauth2Config := &oauth2.Config{
|
|
ClientID: entry.ClientID,
|
|
ClientSecret: entry.ClientSecret,
|
|
Endpoint: endpoint[typ],
|
|
RedirectURL: authRedirect.String(),
|
|
Scopes: scope[typ],
|
|
}
|
|
|
|
if entry.AuthURL != "" {
|
|
oauth2Config.Endpoint.AuthURL = entry.AuthURL
|
|
}
|
|
if entry.TokenURL != "" {
|
|
oauth2Config.Endpoint.TokenURL = entry.TokenURL
|
|
}
|
|
if entry.Scopes != nil && len(entry.Scopes) > 0 {
|
|
oauth2Config.Scopes = entry.Scopes
|
|
}
|
|
if entry.ValidateURL == "" {
|
|
entry.ValidateURL = validateURL[typ]
|
|
}
|
|
|
|
var oauthConfig httpmw.OAuth2Config = oauth2Config
|
|
// Azure DevOps uses JWT token authentication!
|
|
if typ == codersdk.GitProviderAzureDevops {
|
|
oauthConfig = newJWTOAuthConfig(oauth2Config)
|
|
}
|
|
|
|
configs = append(configs, &Config{
|
|
OAuth2Config: oauthConfig,
|
|
ID: entry.ID,
|
|
Regex: regex,
|
|
Type: typ,
|
|
NoRefresh: entry.NoRefresh,
|
|
ValidateURL: entry.ValidateURL,
|
|
})
|
|
}
|
|
return configs, nil
|
|
}
|