Skip to content

Commit f7a5742

Browse files
committed
feat: implement bitbucket-server external auth defaults
Bitbucket cloud != Bitbucket server Add reasonable defaults for server
1 parent 921b6eb commit f7a5742

File tree

5 files changed

+122
-7
lines changed

5 files changed

+122
-7
lines changed

coderd/externalauth/externalauth.go

+55-4
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"net/http"
1010
"net/url"
1111
"regexp"
12+
"strings"
1213
"time"
1314

1415
"golang.org/x/oauth2"
@@ -409,7 +410,7 @@ func ConvertConfig(entries []codersdk.ExternalAuthConfig, accessURL *url.URL) ([
409410
// Applies defaults to the config entry.
410411
// This allows users to very simply state that they type is "GitHub",
411412
// apply their client secret and ID, and have the UI appear nicely.
412-
applyDefaultsToConfig(&entry)
413+
configDefaults(&entry)
413414

414415
valid := httpapi.NameValid(entry.ID)
415416
if valid != nil {
@@ -490,8 +491,22 @@ func ConvertConfig(entries []codersdk.ExternalAuthConfig, accessURL *url.URL) ([
490491
}
491492

492493
// applyDefaultsToConfig applies defaults to the config entry.
493-
func applyDefaultsToConfig(config *codersdk.ExternalAuthConfig) {
494-
defaults := defaults[codersdk.EnhancedExternalAuthProvider(config.Type)]
494+
func configDefaults(config *codersdk.ExternalAuthConfig) {
495+
// If static defaults exist, apply them.
496+
if defaults, ok := staticDefaults[codersdk.EnhancedExternalAuthProvider(config.Type)]; ok {
497+
applyDefaultsToConfig(config, defaults)
498+
return
499+
}
500+
501+
// Dynamic defaults
502+
switch codersdk.EnhancedExternalAuthProvider(config.Type) {
503+
case codersdk.EnhancedExternalAuthProviderBitBucketServer:
504+
applyDefaultsToConfig(config, bitbucketServerDefaults(config))
505+
return
506+
}
507+
}
508+
509+
func applyDefaultsToConfig(config *codersdk.ExternalAuthConfig, defaults codersdk.ExternalAuthConfig) {
495510
if config.AuthURL == "" {
496511
config.AuthURL = defaults.AuthURL
497512
}
@@ -539,7 +554,43 @@ func applyDefaultsToConfig(config *codersdk.ExternalAuthConfig) {
539554
}
540555
}
541556

542-
var defaults = map[codersdk.EnhancedExternalAuthProvider]codersdk.ExternalAuthConfig{
557+
func bitbucketServerDefaults(config *codersdk.ExternalAuthConfig) codersdk.ExternalAuthConfig {
558+
defaults := codersdk.ExternalAuthConfig{
559+
DisplayName: "Bitbucket Server",
560+
Scopes: []string{"PUBLIC_REPOS", "REPO_READ", "REPO_WRITE"},
561+
DisplayIcon: "/icon/bitbucket.svg",
562+
}
563+
// Bitbucket servers will have some base url, e.g. https://bitbucket.coder.com.
564+
// We will grab this from the Auth URL. This choice is a bit arbitrary,
565+
// but we need to require at least 1 field to be populated.
566+
if config.AuthURL == "" {
567+
// No auth url, means we cannot guess the urls.
568+
return defaults
569+
}
570+
571+
auth, err := url.Parse(config.AuthURL)
572+
if err != nil {
573+
// We need a valid URL to continue with.
574+
return defaults
575+
}
576+
577+
// Populate Regex, ValidateURL, and TokenURL.
578+
// Default regex should be anything using the same host as the auth url.
579+
defaults.Regex = fmt.Sprintf(`^(https?://)?%s(/.*)?$`, strings.ReplaceAll(auth.Host, ".", `\.`))
580+
581+
tokenURL := auth.ResolveReference(&url.URL{Path: "/rest/oauth2/latest/token"})
582+
defaults.TokenURL = tokenURL.String()
583+
584+
// validate needs to return a 200 when logged in and a 401 when unauthenticated.
585+
// This endpoint returns the count of the number of PR's in the authenticated
586+
// user's inbox. Which will work perfectly for our use case.
587+
validate := auth.ResolveReference(&url.URL{Path: "/rest/api/latest/inbox/pull-requests/count"})
588+
defaults.ValidateURL = validate.String()
589+
590+
return defaults
591+
}
592+
593+
var staticDefaults = map[codersdk.EnhancedExternalAuthProvider]codersdk.ExternalAuthConfig{
543594
codersdk.EnhancedExternalAuthProviderAzureDevops: {
544595
AuthURL: "https://app.vssps.visualstudio.com/oauth2/authorize",
545596
TokenURL: "https://app.vssps.visualstudio.com/oauth2/token",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package externalauth
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/require"
7+
8+
"github.com/coder/coder/v2/codersdk"
9+
)
10+
11+
func Test_bitbucketServerConfigDefaults(t *testing.T) {
12+
bbType := string(codersdk.EnhancedExternalAuthProviderBitBucketServer)
13+
tests := []struct {
14+
name string
15+
config *codersdk.ExternalAuthConfig
16+
expected codersdk.ExternalAuthConfig
17+
}{
18+
{
19+
// Very few fields are statically defined for Bitbucket Server.
20+
name: "EmpyBitbucketServer",
21+
config: &codersdk.ExternalAuthConfig{
22+
Type: bbType,
23+
},
24+
expected: codersdk.ExternalAuthConfig{
25+
Type: bbType,
26+
ID: bbType,
27+
DisplayName: "Bitbucket Server",
28+
Scopes: []string{"PUBLIC_REPOS", "REPO_READ", "REPO_WRITE"},
29+
DisplayIcon: "/icon/bitbucket.svg",
30+
},
31+
},
32+
{
33+
// Only the AuthURL is required for defaults to work.
34+
name: "AuthURL",
35+
config: &codersdk.ExternalAuthConfig{
36+
Type: bbType,
37+
AuthURL: "https://bitbucket.example.com/login/oauth/authorize",
38+
},
39+
expected: codersdk.ExternalAuthConfig{
40+
Type: bbType,
41+
ID: bbType,
42+
AuthURL: "https://bitbucket.example.com/login/oauth/authorize",
43+
TokenURL: "https://bitbucket.example.com/rest/oauth2/latest/token",
44+
ValidateURL: "https://bitbucket.example.com/rest/api/latest/inbox/pull-requests/count",
45+
Scopes: []string{"PUBLIC_REPOS", "REPO_READ", "REPO_WRITE"},
46+
Regex: `^(https?://)?bitbucket\.example\.com(/.*)?$`,
47+
DisplayName: "Bitbucket Server",
48+
DisplayIcon: "/icon/bitbucket.svg",
49+
},
50+
},
51+
}
52+
for _, tt := range tests {
53+
t.Run(tt.name, func(t *testing.T) {
54+
configDefaults(tt.config)
55+
require.Equal(t, tt.expected, *tt.config)
56+
})
57+
}
58+
}

coderd/workspaceagents.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -2452,7 +2452,7 @@ func createExternalAuthResponse(typ, token string, extra pqtype.NullRawMessage)
24522452
Username: "oauth2",
24532453
Password: token,
24542454
}
2455-
case string(codersdk.EnhancedExternalAuthProviderBitBucket):
2455+
case string(codersdk.EnhancedExternalAuthProviderBitBucket), string(codersdk.EnhancedExternalAuthProviderBitBucketServer):
24562456
// https://support.atlassian.com/bitbucket-cloud/docs/use-oauth-on-bitbucket-cloud/#Cloning-a-repository-with-an-access-token
24572457
resp = agentsdk.ExternalAuthResponse{
24582458
Username: "x-token-auth",

codersdk/externalauth.go

+6-2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ func (e EnhancedExternalAuthProvider) Git() bool {
2222
case EnhancedExternalAuthProviderGitHub,
2323
EnhancedExternalAuthProviderGitLab,
2424
EnhancedExternalAuthProviderBitBucket,
25+
EnhancedExternalAuthProviderBitBucketServer,
2526
EnhancedExternalAuthProviderAzureDevops:
2627
return true
2728
default:
@@ -33,8 +34,11 @@ const (
3334
EnhancedExternalAuthProviderAzureDevops EnhancedExternalAuthProvider = "azure-devops"
3435
EnhancedExternalAuthProviderGitHub EnhancedExternalAuthProvider = "github"
3536
EnhancedExternalAuthProviderGitLab EnhancedExternalAuthProvider = "gitlab"
36-
EnhancedExternalAuthProviderBitBucket EnhancedExternalAuthProvider = "bitbucket"
37-
EnhancedExternalAuthProviderSlack EnhancedExternalAuthProvider = "slack"
37+
// EnhancedExternalAuthProviderBitBucket is the Bitbucket Cloud provider.
38+
// Not to be confused with the self-hosted 'EnhancedExternalAuthProviderBitBucketServer'
39+
EnhancedExternalAuthProviderBitBucket EnhancedExternalAuthProvider = "bitbucket"
40+
EnhancedExternalAuthProviderBitBucketServer EnhancedExternalAuthProvider = "bitbucket-server"
41+
EnhancedExternalAuthProviderSlack EnhancedExternalAuthProvider = "slack"
3842
)
3943

4044
type ExternalAuth struct {

site/src/api/typesGenerated.ts

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)