mirror of
https://github.com/coder/coder.git
synced 2025-07-09 11:45:56 +00:00
feat: implement bitbucket-server external auth defaults (#10520)
* feat: implement bitbucket-server external auth defaults Bitbucket cloud != Bitbucket server Add reasonable defaults for server * change "bitbucket" to "bitbucket-cloud"
This commit is contained in:
@ -9,6 +9,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
@ -494,7 +495,36 @@ func ConvertConfig(entries []codersdk.ExternalAuthConfig, accessURL *url.URL) ([
|
|||||||
|
|
||||||
// applyDefaultsToConfig applies defaults to the config entry.
|
// applyDefaultsToConfig applies defaults to the config entry.
|
||||||
func applyDefaultsToConfig(config *codersdk.ExternalAuthConfig) {
|
func applyDefaultsToConfig(config *codersdk.ExternalAuthConfig) {
|
||||||
defaults := defaults[codersdk.EnhancedExternalAuthProvider(config.Type)]
|
configType := codersdk.EnhancedExternalAuthProvider(config.Type)
|
||||||
|
if configType == "bitbucket" {
|
||||||
|
// For backwards compatibility, we need to support the "bitbucket" string.
|
||||||
|
configType = codersdk.EnhancedExternalAuthProviderBitBucketCloud
|
||||||
|
defer func() {
|
||||||
|
// The config type determines the config ID (if unset). So change the legacy
|
||||||
|
// type to the correct new type after the defaults have been configured.
|
||||||
|
config.Type = string(codersdk.EnhancedExternalAuthProviderBitBucketCloud)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
// If static defaults exist, apply them.
|
||||||
|
if defaults, ok := staticDefaults[configType]; ok {
|
||||||
|
copyDefaultSettings(config, defaults)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dynamic defaults
|
||||||
|
switch codersdk.EnhancedExternalAuthProvider(config.Type) {
|
||||||
|
case codersdk.EnhancedExternalAuthProviderBitBucketServer:
|
||||||
|
copyDefaultSettings(config, bitbucketServerDefaults(config))
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
// No defaults for this type. We still want to run this apply with
|
||||||
|
// an empty set of defaults.
|
||||||
|
copyDefaultSettings(config, codersdk.ExternalAuthConfig{})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyDefaultSettings(config *codersdk.ExternalAuthConfig, defaults codersdk.ExternalAuthConfig) {
|
||||||
if config.AuthURL == "" {
|
if config.AuthURL == "" {
|
||||||
config.AuthURL = defaults.AuthURL
|
config.AuthURL = defaults.AuthURL
|
||||||
}
|
}
|
||||||
@ -542,7 +572,43 @@ func applyDefaultsToConfig(config *codersdk.ExternalAuthConfig) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var defaults = map[codersdk.EnhancedExternalAuthProvider]codersdk.ExternalAuthConfig{
|
func bitbucketServerDefaults(config *codersdk.ExternalAuthConfig) codersdk.ExternalAuthConfig {
|
||||||
|
defaults := codersdk.ExternalAuthConfig{
|
||||||
|
DisplayName: "Bitbucket Server",
|
||||||
|
Scopes: []string{"PUBLIC_REPOS", "REPO_READ", "REPO_WRITE"},
|
||||||
|
DisplayIcon: "/icon/bitbucket.svg",
|
||||||
|
}
|
||||||
|
// Bitbucket servers will have some base url, e.g. https://bitbucket.coder.com.
|
||||||
|
// We will grab this from the Auth URL. This choice is a bit arbitrary,
|
||||||
|
// but we need to require at least 1 field to be populated.
|
||||||
|
if config.AuthURL == "" {
|
||||||
|
// No auth url, means we cannot guess the urls.
|
||||||
|
return defaults
|
||||||
|
}
|
||||||
|
|
||||||
|
auth, err := url.Parse(config.AuthURL)
|
||||||
|
if err != nil {
|
||||||
|
// We need a valid URL to continue with.
|
||||||
|
return defaults
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate Regex, ValidateURL, and TokenURL.
|
||||||
|
// Default regex should be anything using the same host as the auth url.
|
||||||
|
defaults.Regex = fmt.Sprintf(`^(https?://)?%s(/.*)?$`, strings.ReplaceAll(auth.Host, ".", `\.`))
|
||||||
|
|
||||||
|
tokenURL := auth.ResolveReference(&url.URL{Path: "/rest/oauth2/latest/token"})
|
||||||
|
defaults.TokenURL = tokenURL.String()
|
||||||
|
|
||||||
|
// validate needs to return a 200 when logged in and a 401 when unauthenticated.
|
||||||
|
// This endpoint returns the count of the number of PR's in the authenticated
|
||||||
|
// user's inbox. Which will work perfectly for our use case.
|
||||||
|
validate := auth.ResolveReference(&url.URL{Path: "/rest/api/latest/inbox/pull-requests/count"})
|
||||||
|
defaults.ValidateURL = validate.String()
|
||||||
|
|
||||||
|
return defaults
|
||||||
|
}
|
||||||
|
|
||||||
|
var staticDefaults = map[codersdk.EnhancedExternalAuthProvider]codersdk.ExternalAuthConfig{
|
||||||
codersdk.EnhancedExternalAuthProviderAzureDevops: {
|
codersdk.EnhancedExternalAuthProviderAzureDevops: {
|
||||||
AuthURL: "https://app.vssps.visualstudio.com/oauth2/authorize",
|
AuthURL: "https://app.vssps.visualstudio.com/oauth2/authorize",
|
||||||
TokenURL: "https://app.vssps.visualstudio.com/oauth2/token",
|
TokenURL: "https://app.vssps.visualstudio.com/oauth2/token",
|
||||||
@ -551,7 +617,7 @@ var defaults = map[codersdk.EnhancedExternalAuthProvider]codersdk.ExternalAuthCo
|
|||||||
Regex: `^(https?://)?dev\.azure\.com(/.*)?$`,
|
Regex: `^(https?://)?dev\.azure\.com(/.*)?$`,
|
||||||
Scopes: []string{"vso.code_write"},
|
Scopes: []string{"vso.code_write"},
|
||||||
},
|
},
|
||||||
codersdk.EnhancedExternalAuthProviderBitBucket: {
|
codersdk.EnhancedExternalAuthProviderBitBucketCloud: {
|
||||||
AuthURL: "https://bitbucket.org/site/oauth2/authorize",
|
AuthURL: "https://bitbucket.org/site/oauth2/authorize",
|
||||||
TokenURL: "https://bitbucket.org/site/oauth2/access_token",
|
TokenURL: "https://bitbucket.org/site/oauth2/access_token",
|
||||||
ValidateURL: "https://api.bitbucket.org/2.0/user",
|
ValidateURL: "https://api.bitbucket.org/2.0/user",
|
||||||
|
81
coderd/externalauth/externalauth_internal_test.go
Normal file
81
coderd/externalauth/externalauth_internal_test.go
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
package externalauth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/coder/coder/v2/codersdk"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_bitbucketServerConfigDefaults(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
bbType := string(codersdk.EnhancedExternalAuthProviderBitBucketServer)
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
config *codersdk.ExternalAuthConfig
|
||||||
|
expected codersdk.ExternalAuthConfig
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
// Very few fields are statically defined for Bitbucket Server.
|
||||||
|
name: "EmptyBitbucketServer",
|
||||||
|
config: &codersdk.ExternalAuthConfig{
|
||||||
|
Type: bbType,
|
||||||
|
},
|
||||||
|
expected: codersdk.ExternalAuthConfig{
|
||||||
|
Type: bbType,
|
||||||
|
ID: bbType,
|
||||||
|
DisplayName: "Bitbucket Server",
|
||||||
|
Scopes: []string{"PUBLIC_REPOS", "REPO_READ", "REPO_WRITE"},
|
||||||
|
DisplayIcon: "/icon/bitbucket.svg",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Only the AuthURL is required for defaults to work.
|
||||||
|
name: "AuthURL",
|
||||||
|
config: &codersdk.ExternalAuthConfig{
|
||||||
|
Type: bbType,
|
||||||
|
AuthURL: "https://bitbucket.example.com/login/oauth/authorize",
|
||||||
|
},
|
||||||
|
expected: codersdk.ExternalAuthConfig{
|
||||||
|
Type: bbType,
|
||||||
|
ID: bbType,
|
||||||
|
AuthURL: "https://bitbucket.example.com/login/oauth/authorize",
|
||||||
|
TokenURL: "https://bitbucket.example.com/rest/oauth2/latest/token",
|
||||||
|
ValidateURL: "https://bitbucket.example.com/rest/api/latest/inbox/pull-requests/count",
|
||||||
|
Scopes: []string{"PUBLIC_REPOS", "REPO_READ", "REPO_WRITE"},
|
||||||
|
Regex: `^(https?://)?bitbucket\.example\.com(/.*)?$`,
|
||||||
|
DisplayName: "Bitbucket Server",
|
||||||
|
DisplayIcon: "/icon/bitbucket.svg",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Ensure backwards compatibility. The type should update to "bitbucket-cloud",
|
||||||
|
// but the ID and other fields should remain the same.
|
||||||
|
name: "BitbucketLegacy",
|
||||||
|
config: &codersdk.ExternalAuthConfig{
|
||||||
|
Type: "bitbucket",
|
||||||
|
},
|
||||||
|
expected: codersdk.ExternalAuthConfig{
|
||||||
|
Type: string(codersdk.EnhancedExternalAuthProviderBitBucketCloud),
|
||||||
|
ID: "bitbucket", // Legacy ID remains unchanged
|
||||||
|
AuthURL: "https://bitbucket.org/site/oauth2/authorize",
|
||||||
|
TokenURL: "https://bitbucket.org/site/oauth2/access_token",
|
||||||
|
ValidateURL: "https://api.bitbucket.org/2.0/user",
|
||||||
|
DisplayName: "BitBucket",
|
||||||
|
DisplayIcon: "/icon/bitbucket.svg",
|
||||||
|
Regex: `^(https?://)?bitbucket\.org(/.*)?$`,
|
||||||
|
Scopes: []string{"account", "repository:write"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
tt := tt
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
applyDefaultsToConfig(tt.config)
|
||||||
|
require.Equal(t, tt.expected, *tt.config)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -2452,7 +2452,8 @@ func createExternalAuthResponse(typ, token string, extra pqtype.NullRawMessage)
|
|||||||
Username: "oauth2",
|
Username: "oauth2",
|
||||||
Password: token,
|
Password: token,
|
||||||
}
|
}
|
||||||
case string(codersdk.EnhancedExternalAuthProviderBitBucket):
|
case string(codersdk.EnhancedExternalAuthProviderBitBucketCloud), string(codersdk.EnhancedExternalAuthProviderBitBucketServer):
|
||||||
|
// The string "bitbucket" was a legacy parameter that needs to still be supported.
|
||||||
// https://support.atlassian.com/bitbucket-cloud/docs/use-oauth-on-bitbucket-cloud/#Cloning-a-repository-with-an-access-token
|
// https://support.atlassian.com/bitbucket-cloud/docs/use-oauth-on-bitbucket-cloud/#Cloning-a-repository-with-an-access-token
|
||||||
resp = agentsdk.ExternalAuthResponse{
|
resp = agentsdk.ExternalAuthResponse{
|
||||||
Username: "x-token-auth",
|
Username: "x-token-auth",
|
||||||
|
@ -21,7 +21,8 @@ func (e EnhancedExternalAuthProvider) Git() bool {
|
|||||||
switch e {
|
switch e {
|
||||||
case EnhancedExternalAuthProviderGitHub,
|
case EnhancedExternalAuthProviderGitHub,
|
||||||
EnhancedExternalAuthProviderGitLab,
|
EnhancedExternalAuthProviderGitLab,
|
||||||
EnhancedExternalAuthProviderBitBucket,
|
EnhancedExternalAuthProviderBitBucketCloud,
|
||||||
|
EnhancedExternalAuthProviderBitBucketServer,
|
||||||
EnhancedExternalAuthProviderAzureDevops:
|
EnhancedExternalAuthProviderAzureDevops:
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
@ -33,7 +34,10 @@ const (
|
|||||||
EnhancedExternalAuthProviderAzureDevops EnhancedExternalAuthProvider = "azure-devops"
|
EnhancedExternalAuthProviderAzureDevops EnhancedExternalAuthProvider = "azure-devops"
|
||||||
EnhancedExternalAuthProviderGitHub EnhancedExternalAuthProvider = "github"
|
EnhancedExternalAuthProviderGitHub EnhancedExternalAuthProvider = "github"
|
||||||
EnhancedExternalAuthProviderGitLab EnhancedExternalAuthProvider = "gitlab"
|
EnhancedExternalAuthProviderGitLab EnhancedExternalAuthProvider = "gitlab"
|
||||||
EnhancedExternalAuthProviderBitBucket EnhancedExternalAuthProvider = "bitbucket"
|
// EnhancedExternalAuthProviderBitBucketCloud is the Bitbucket Cloud provider.
|
||||||
|
// Not to be confused with the self-hosted 'EnhancedExternalAuthProviderBitBucketServer'
|
||||||
|
EnhancedExternalAuthProviderBitBucketCloud EnhancedExternalAuthProvider = "bitbucket-cloud"
|
||||||
|
EnhancedExternalAuthProviderBitBucketServer EnhancedExternalAuthProvider = "bitbucket-server"
|
||||||
EnhancedExternalAuthProviderSlack EnhancedExternalAuthProvider = "slack"
|
EnhancedExternalAuthProviderSlack EnhancedExternalAuthProvider = "slack"
|
||||||
EnhancedExternalAuthProviderJFrog EnhancedExternalAuthProvider = "jfrog"
|
EnhancedExternalAuthProviderJFrog EnhancedExternalAuthProvider = "jfrog"
|
||||||
)
|
)
|
||||||
|
6
site/src/api/typesGenerated.ts
generated
6
site/src/api/typesGenerated.ts
generated
@ -1683,14 +1683,16 @@ export const DisplayApps: DisplayApp[] = [
|
|||||||
// From codersdk/externalauth.go
|
// From codersdk/externalauth.go
|
||||||
export type EnhancedExternalAuthProvider =
|
export type EnhancedExternalAuthProvider =
|
||||||
| "azure-devops"
|
| "azure-devops"
|
||||||
| "bitbucket"
|
| "bitbucket-cloud"
|
||||||
|
| "bitbucket-server"
|
||||||
| "github"
|
| "github"
|
||||||
| "gitlab"
|
| "gitlab"
|
||||||
| "jfrog"
|
| "jfrog"
|
||||||
| "slack";
|
| "slack";
|
||||||
export const EnhancedExternalAuthProviders: EnhancedExternalAuthProvider[] = [
|
export const EnhancedExternalAuthProviders: EnhancedExternalAuthProvider[] = [
|
||||||
"azure-devops",
|
"azure-devops",
|
||||||
"bitbucket",
|
"bitbucket-cloud",
|
||||||
|
"bitbucket-server",
|
||||||
"github",
|
"github",
|
||||||
"gitlab",
|
"gitlab",
|
||||||
"jfrog",
|
"jfrog",
|
||||||
|
Reference in New Issue
Block a user