From bde7f9eee1f70a6c1632b14274450853e3523b4c Mon Sep 17 00:00:00 2001 From: Mohammad Fouad Date: Thu, 1 Oct 2026 10:00:53 +0300 Subject: [PATCH] fix(bitbucket): auth failure due to Bitbucket API changes (#3477) * fix: #3428 bybitbucket auth failure by changing token n validation flow to use Authentication header instead of query param Co-authored-by: aviralgarg05 Signed-off-by: Mohammad Hassan Signed-off-by: Jan Larwig * fix: added changelog entry and added tests to verify bitbucket auth header fix Signed-off-by: Mohammad Hassan * fix(bitbucket): aded support for limiting login for workspace members and handled deprecated team api and added test cases Signed-off-by: Mohammad Hassan * docs(bitbucket): applied missing docs changes Signed-off-by: Jan Larwig --------- Signed-off-by: Mohammad Hassan Signed-off-by: Jan Larwig Co-authored-by: aviralgarg05 Co-authored-by: Jan Larwig --- CHANGELOG.md | 4 + contrib/oauth2-proxy_autocomplete.sh | 2 +- docs/docs/configuration/alpha_config.md | 3 +- .../docs/configuration/providers/bitbucket.md | 13 +- .../configuration/alpha_config.md | 3 +- .../configuration/providers/bitbucket.md | 13 +- pkg/apis/options/legacy_options.go | 5 +- pkg/apis/options/providers.go | 4 +- providers/bitbucket.go | 56 +++++--- providers/bitbucket_test.go | 130 +++++++++++++++++- 10 files changed, 197 insertions(+), 36 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f7b5e335..c974da0d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,9 @@ ## Release Highlights ## Important Notes +The Bitbucket provider `--bitbucket-team` flag got deprecated and we added `--bitbucket-workspace` flag to restrict logins to members of a specific workspace instead of a team. The `--bitbucket-team` flag is still supported and will act like workspace but will be removed in a future release. Please update your configuration to use the new `--bitbucket-workspace` flag. For more information, refer to [Bitbucket teams API deprecation](https://developer.atlassian.com/cloud/bitbucket/bitbucket-api-teams-deprecation/). + +Additionally refer to OAuth client configuration for Bitbucket provider in the [documentation](https://oauth2-proxy.github.io/oauth2-proxy/configuration/providers/bitbucket/). for changes in the scopes (Account>Read) is now required if you restrict by workspace. ## Breaking Changes @@ -10,6 +13,7 @@ - [#3546](https://github.com/oauth2-proxy/oauth2-proxy/pull/3546) fix: strip the port from the request host when matching cookie domains @kirilju - [#3547](https://github.com/oauth2-proxy/oauth2-proxy/pull/3547) fix: refresh additional claims for OIDC and MS Entra ID providers and properly populate additional claims during login (@Apollo3zehn) +- [#3477](https://github.com/oauth2-proxy/oauth2-proxy/pull/3477) fix(bitbucket): auth failure due to Bitbucket OAuth 2.0 [changes on May 4th 2026](https://developer.atlassian.com/cloud/bitbucket/changelog/#CHANGE-3052) @mfouad # V7.15.4 diff --git a/contrib/oauth2-proxy_autocomplete.sh b/contrib/oauth2-proxy_autocomplete.sh index 0dd8d304..347b00bf 100644 --- a/contrib/oauth2-proxy_autocomplete.sh +++ b/contrib/oauth2-proxy_autocomplete.sh @@ -24,7 +24,7 @@ _oauth2_proxy() { COMPREPLY=( $(compgen -W 'X-Real-IP X-Forwarded-For X-ProxyUser-IP' -- ${cur}) ) return 0 ;; - --@(http-address|https-address|redirect-url|upstream|basic-auth-password|skip-auth-regex|flush-interval|extra-jwt-issuers|email-domain|whitelist-domain|trusted-ip|keycloak-group|azure-tenant|bitbucket-team|bitbucket-repository|github-org|github-team|github-repo|github-token|gitlab-group|github-user|google-group|google-admin-email|google-service-account-json|client-id|client_secret|banner|footer|proxy-prefix|ping-path|ready-path|cookie-name|cookie-secret|cookie-domain|cookie-path|cookie-expire|cookie-refresh|cookie-samesite|redist-sentinel-master-name|redist-sentinel-connection-urls|redist-cluster-connection-urls|logging-max-size|logging-max-age|logging-max-backups|standard-logging-format|request-logging-format|exclude-logging-paths|auth-logging-format|oidc-issuer-url|oidc-jwks-url|login-url|redeem-url|profile-url|resource|validate-url|scope|approval-prompt|signature-key|acr-values|jwt-key|pubjwk-url|force-json-errors)) + --@(http-address|https-address|redirect-url|upstream|basic-auth-password|skip-auth-regex|flush-interval|extra-jwt-issuers|email-domain|whitelist-domain|trusted-ip|keycloak-group|azure-tenant|bitbucket-workspace|bitbucket-repository|github-org|github-team|github-repo|github-token|gitlab-group|github-user|google-group|google-admin-email|google-service-account-json|client-id|client_secret|banner|footer|proxy-prefix|ping-path|ready-path|cookie-name|cookie-secret|cookie-domain|cookie-path|cookie-expire|cookie-refresh|cookie-samesite|redist-sentinel-master-name|redist-sentinel-connection-urls|redist-cluster-connection-urls|logging-max-size|logging-max-age|logging-max-backups|standard-logging-format|request-logging-format|exclude-logging-paths|auth-logging-format|oidc-issuer-url|oidc-jwks-url|login-url|redeem-url|profile-url|resource|validate-url|scope|approval-prompt|signature-key|acr-values|jwt-key|pubjwk-url|force-json-errors)) return 0 ;; esac diff --git a/docs/docs/configuration/alpha_config.md b/docs/docs/configuration/alpha_config.md index 928e5fee..824dc324 100644 --- a/docs/docs/configuration/alpha_config.md +++ b/docs/docs/configuration/alpha_config.md @@ -342,7 +342,8 @@ They may change between releases without notice. | Field | Type | Description | | ----- | ---- | ----------- | -| `team` | _string_ | Team sets restrict logins to members of this team | +| `team` | _string_ | Team sets restrict logins to members of this team - Bitbucket has deprecated teams, it will act as workspace instead | +| `workspace` | _string_ | Workspace sets restrict logins to members of this workspace, use workspace slug | | `repository` | _string_ | Repository sets restrict logins to user with access to this repository | ### ClaimSource diff --git a/docs/docs/configuration/providers/bitbucket.md b/docs/docs/configuration/providers/bitbucket.md index e31de752..db68999f 100644 --- a/docs/docs/configuration/providers/bitbucket.md +++ b/docs/docs/configuration/providers/bitbucket.md @@ -8,7 +8,7 @@ title: BitBucket hostname that oauth2-proxy is running on. * In Permissions section select: * Account -> Email - * Team membership -> Read + * Account -> Read [Required for workspace membership check] * Repositories -> Read 2. Note the Client ID and Client Secret. @@ -20,6 +20,11 @@ To use the provider, pass the following options: --client-secret= ``` -The default configuration allows everyone with Bitbucket account to authenticate. To restrict the access to the team -members use additional configuration option: `--bitbucket-team=`. To restrict the access to only these users -who have access to one selected repository use `--bitbucket-repository=`. +The default configuration allows everyone with Bitbucket account to authenticate. + +To restrict the access to members of a specific workspace, use the additional configuration option: `--bitbucket-workspace=`. + +To restrict the access to users who have write access to one selected repository (contributors) use `--bitbucket-repository=`. Note that repository full name format `owner/repo` is required, for example `--bitbucket-repository=myworkspace/myrepo`. + +**Deprecated**: To restrict the access to members of a specific team, use the additional configuration option: `--bitbucket-team=`. Note that this option is deprecated and will be removed in a future release. Please use `--bitbucket-workspace` instead. For more info, see [Bitbucket teams API deprecation](https://developer.atlassian.com/cloud/bitbucket/bitbucket-api-teams-deprecation/). + diff --git a/docs/versioned_docs/version-7.15.x/configuration/alpha_config.md b/docs/versioned_docs/version-7.15.x/configuration/alpha_config.md index 680741ba..94e4e5f1 100644 --- a/docs/versioned_docs/version-7.15.x/configuration/alpha_config.md +++ b/docs/versioned_docs/version-7.15.x/configuration/alpha_config.md @@ -331,7 +331,8 @@ They may change between releases without notice. | Field | Type | Description | | ----- | ---- | ----------- | -| `team` | _string_ | Team sets restrict logins to members of this team | +| `team` | _string_ | Team sets restrict logins to members of this team - Bitbucket has deprecated the teams, it will act as workspace instead | +| `workspace` | _string_ | Workspace sets restrict logins to members of this workspace, use workspace slug | | `repository` | _string_ | Repository sets restrict logins to user with access to this repository | ### ClaimSource diff --git a/docs/versioned_docs/version-7.15.x/configuration/providers/bitbucket.md b/docs/versioned_docs/version-7.15.x/configuration/providers/bitbucket.md index e31de752..db68999f 100644 --- a/docs/versioned_docs/version-7.15.x/configuration/providers/bitbucket.md +++ b/docs/versioned_docs/version-7.15.x/configuration/providers/bitbucket.md @@ -8,7 +8,7 @@ title: BitBucket hostname that oauth2-proxy is running on. * In Permissions section select: * Account -> Email - * Team membership -> Read + * Account -> Read [Required for workspace membership check] * Repositories -> Read 2. Note the Client ID and Client Secret. @@ -20,6 +20,11 @@ To use the provider, pass the following options: --client-secret= ``` -The default configuration allows everyone with Bitbucket account to authenticate. To restrict the access to the team -members use additional configuration option: `--bitbucket-team=`. To restrict the access to only these users -who have access to one selected repository use `--bitbucket-repository=`. +The default configuration allows everyone with Bitbucket account to authenticate. + +To restrict the access to members of a specific workspace, use the additional configuration option: `--bitbucket-workspace=`. + +To restrict the access to users who have write access to one selected repository (contributors) use `--bitbucket-repository=`. Note that repository full name format `owner/repo` is required, for example `--bitbucket-repository=myworkspace/myrepo`. + +**Deprecated**: To restrict the access to members of a specific team, use the additional configuration option: `--bitbucket-team=`. Note that this option is deprecated and will be removed in a future release. Please use `--bitbucket-workspace` instead. For more info, see [Bitbucket teams API deprecation](https://developer.atlassian.com/cloud/bitbucket/bitbucket-api-teams-deprecation/). + diff --git a/pkg/apis/options/legacy_options.go b/pkg/apis/options/legacy_options.go index e53fd480..ac5746dc 100644 --- a/pkg/apis/options/legacy_options.go +++ b/pkg/apis/options/legacy_options.go @@ -512,6 +512,7 @@ type LegacyProvider struct { EntraIDAllowedTenants []string `flag:"entra-id-allowed-tenant" cfg:"entra_id_allowed_tenants"` EntraIDFederatedTokenAuth bool `flag:"entra-id-federated-token-auth" cfg:"entra_id_federated_token_auth"` BitbucketTeam string `flag:"bitbucket-team" cfg:"bitbucket_team"` + BitbucketWorkspace string `flag:"bitbucket-workspace" cfg:"bitbucket_workspace"` BitbucketRepository string `flag:"bitbucket-repository" cfg:"bitbucket_repository"` GitHubOrg string `flag:"github-org" cfg:"github_org"` GitHubTeam string `flag:"github-team" cfg:"github_team"` @@ -580,7 +581,8 @@ func legacyProviderFlagSet() *pflag.FlagSet { flagSet.String("azure-graph-group-field", "", "configures the group field to be used when building the groups list(`id` or `displayName`. Default is `id`) from Microsoft Graph(available only for v2.0 oidc url). Based on this value, the `allowed-group` config values should be adjusted accordingly. If using `id` as group field, `allowed-group` should contains groups IDs, if using `displayName` as group field, `allowed-group` should contains groups name") flagSet.StringSlice("entra-id-allowed-tenant", []string{}, "list of tenants allowed for MS Entra ID multi-tenant application") flagSet.Bool("entra-id-federated-token-auth", false, "enable oAuth client authentication with federated token projected by Azure Workload Identity plugin, instead of client secret.") - flagSet.String("bitbucket-team", "", "restrict logins to members of this team") + flagSet.String("bitbucket-team", "", "[deprecated, use bitbucket-workspace instead] restrict logins to members of this team") + flagSet.String("bitbucket-workspace", "", "restrict logins to members of this workspace, use workspace slug (eg. `myworkspace`) instead of workspace name (eg. `My Workspace`)") flagSet.String("bitbucket-repository", "", "restrict logins to user with access to this repository") flagSet.String("github-org", "", "restrict logins to members of this organisation") flagSet.String("github-team", "", "restrict logins to members of this team") @@ -777,6 +779,7 @@ func (l *LegacyProvider) convert() (Providers, error) { case "bitbucket": provider.BitbucketConfig = BitbucketOptions{ Team: l.BitbucketTeam, + Workspace: l.BitbucketWorkspace, Repository: l.BitbucketRepository, } case "google": diff --git a/pkg/apis/options/providers.go b/pkg/apis/options/providers.go index 6f115f8a..cdc5601a 100644 --- a/pkg/apis/options/providers.go +++ b/pkg/apis/options/providers.go @@ -236,8 +236,10 @@ type ADFSOptions struct { } type BitbucketOptions struct { - // Team sets restrict logins to members of this team + // Team sets restrict logins to members of this team - Bitbucket has deprecated teams, it will act as workspace instead Team string `yaml:"team,omitempty"` + // Workspace sets restrict logins to members of this workspace, use workspace slug + Workspace string `yaml:"workspace,omitempty"` // Repository sets restrict logins to user with access to this repository Repository string `yaml:"repository,omitempty"` } diff --git a/providers/bitbucket.go b/providers/bitbucket.go index 75cd2926..67d71354 100644 --- a/providers/bitbucket.go +++ b/providers/bitbucket.go @@ -14,7 +14,7 @@ import ( // BitbucketProvider represents an Bitbucket based Identity Provider type BitbucketProvider struct { *ProviderData - Team string + Workspace string Repository string } @@ -67,7 +67,10 @@ func NewBitbucketProvider(p *ProviderData, opts options.BitbucketOptions) *Bitbu provider := &BitbucketProvider{ProviderData: p} if opts.Team != "" { - provider.setTeam(opts.Team) + provider.setWorkspace(opts.Team) + } + if opts.Workspace != "" { + provider.setWorkspace(opts.Workspace) } if opts.Repository != "" { provider.setRepository(opts.Repository) @@ -75,11 +78,11 @@ func NewBitbucketProvider(p *ProviderData, opts options.BitbucketOptions) *Bitbu return provider } -// setTeam defines the Bitbucket team the user must be part of -func (p *BitbucketProvider) setTeam(team string) { - p.Team = team - if !strings.Contains(p.Scope, "team") { - p.Scope += " team" +// setWorkspace defines the Bitbucket workspace the user must be part of +func (p *BitbucketProvider) setWorkspace(workspace string) { + p.Workspace = workspace + if !strings.Contains(p.Scope, "account") { + p.Scope += " account" } } @@ -91,6 +94,11 @@ func (p *BitbucketProvider) setRepository(repository string) { } } +// ValidateSession validates the AccessToken using a Bearer token header +func (p *BitbucketProvider) ValidateSession(ctx context.Context, s *sessions.SessionState) bool { + return validateToken(ctx, p, s.AccessToken, makeOIDCHeader(s.AccessToken)) +} + // GetEmailAddress returns the email of the authenticated user func (p *BitbucketProvider) GetEmailAddress(ctx context.Context, s *sessions.SessionState) (string, error) { @@ -100,9 +108,11 @@ func (p *BitbucketProvider) GetEmailAddress(ctx context.Context, s *sessions.Ses Primary bool `json:"is_primary"` } } - var teams struct { + var workspaces struct { Values []struct { - Name string `json:"username"` + Workspace struct { + Slug string `json:"slug"` + } `json:"workspace"` } } var repositories struct { @@ -111,9 +121,10 @@ func (p *BitbucketProvider) GetEmailAddress(ctx context.Context, s *sessions.Ses } } - requestURL := p.ValidateURL.String() + "?access_token=" + s.AccessToken + requestURL := p.ValidateURL.String() err := requests.New(requestURL). WithContext(ctx). + WithHeaders(makeOIDCHeader(s.AccessToken)). Do(). UnmarshalInto(&emails) if err != nil { @@ -121,24 +132,29 @@ func (p *BitbucketProvider) GetEmailAddress(ctx context.Context, s *sessions.Ses return "", err } - if p.Team != "" { + if p.Workspace != "" { teamURL := &url.URL{} *teamURL = *p.ValidateURL - teamURL.Path = "/2.0/teams" + // /teams api was deprecated in Oct 20, use workspaces instead + // https://developer.atlassian.com/cloud/bitbucket/bitbucket-api-teams-deprecation/ + // https://developer.atlassian.com/cloud/bitbucket/rest/api-group-workspaces/#api-workspaces-get + teamURL.Path = "2.0/user/workspaces" - requestURL := teamURL.String() + "?role=member&access_token=" + s.AccessToken + requestURL := teamURL.String() err := requests.New(requestURL). WithContext(ctx). + WithHeaders(makeOIDCHeader(s.AccessToken)). Do(). - UnmarshalInto(&teams) + UnmarshalInto(&workspaces) + logger.Printf("workspaces: %+v", workspaces) if err != nil { logger.Errorf("failed requesting teams membership: %v", err) return "", err } var found = false - for _, team := range teams.Values { - if p.Team == team.Name { + for _, workspace := range workspaces.Values { + if p.Workspace == workspace.Workspace.Slug { found = true break } @@ -152,14 +168,16 @@ func (p *BitbucketProvider) GetEmailAddress(ctx context.Context, s *sessions.Ses if p.Repository != "" { repositoriesURL := &url.URL{} *repositoriesURL = *p.ValidateURL - repositoriesURL.Path = "/2.0/repositories/" + strings.Split(p.Repository, "/")[0] + // split the repository name to get the workspace name, which is the first part of the repository name + var repoWorkspace = strings.Split(p.Repository, "/")[0] + repositoriesURL.Path = "/2.0/repositories/" + repoWorkspace requestURL := repositoriesURL.String() + "?role=contributor" + - "&q=full_name=" + url.QueryEscape("\""+p.Repository+"\"") + - "&access_token=" + s.AccessToken + "&q=full_name=" + url.QueryEscape("\""+p.Repository+"\"") err := requests.New(requestURL). WithContext(ctx). + WithHeaders(makeOIDCHeader(s.AccessToken)). Do(). UnmarshalInto(&repositories) if err != nil { diff --git a/providers/bitbucket_test.go b/providers/bitbucket_test.go index 3502edc7..a551f512 100644 --- a/providers/bitbucket_test.go +++ b/providers/bitbucket_test.go @@ -6,6 +6,7 @@ import ( "net/http" "net/http/httptest" "net/url" + "strings" "testing" "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options" @@ -25,6 +26,7 @@ func testBitbucketProvider(hostname, team string, repository string) *BitbucketP Scope: ""}, options.BitbucketOptions{ Team: team, + Workspace: team, Repository: repository, }, ) @@ -41,7 +43,6 @@ func testBitbucketProvider(hostname, team string, repository string) *BitbucketP func testBitbucketBackend(payload string) *httptest.Server { paths := map[string]bool{ "/2.0/user/emails": true, - "/2.0/teams": true, } return httptest.NewServer(http.HandlerFunc( @@ -50,7 +51,7 @@ func testBitbucketBackend(payload string) *httptest.Server { if !paths[url.Path] { log.Printf("%s not in %+v\n", url.Path, paths) w.WriteHeader(404) - } else if !IsAuthorizedInURL(r.URL) { + } else if !IsAuthorizedInHeader(r.Header) { w.WriteHeader(403) } else { w.WriteHeader(200) @@ -59,6 +60,34 @@ func testBitbucketBackend(payload string) *httptest.Server { })) } +func testBitbucketBackendWithWorkspace(emailPayload, workspacePayload string) *httptest.Server { + return testBitbucketBackendFull(emailPayload, workspacePayload, "") +} + +func testBitbucketBackendFull(emailPayload, workspacePayload, repoPayload string) *httptest.Server { + return httptest.NewServer(http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + if !IsAuthorizedInHeader(r.Header) { + w.WriteHeader(403) + return + } + switch { + case r.URL.Path == "/2.0/user/emails": + w.WriteHeader(200) + w.Write([]byte(emailPayload)) + case r.URL.Path == "/2.0/user/workspaces": + w.WriteHeader(200) + w.Write([]byte(workspacePayload)) + case strings.HasPrefix(r.URL.Path, "/2.0/repositories/"): + w.WriteHeader(200) + w.Write([]byte(repoPayload)) + default: + log.Printf("%s not handled\n", r.URL.Path) + w.WriteHeader(404) + } + })) +} + func TestNewBitbucketProvider(t *testing.T) { g := NewWithT(t) @@ -75,7 +104,7 @@ func TestNewBitbucketProvider(t *testing.T) { func TestBitbucketProviderScopeAdjustForTeam(t *testing.T) { p := testBitbucketProvider("", "test-team", "") assert.NotEqual(t, nil, p) - assert.Equal(t, "email team", p.Data().Scope) + assert.Equal(t, "email account", p.Data().Scope) } func TestBitbucketProviderScopeAdjustForRepository(t *testing.T) { @@ -126,7 +155,9 @@ func TestBitbucketProviderGetEmailAddress(t *testing.T) { } func TestBitbucketProviderGetEmailAddressAndGroup(t *testing.T) { - b := testBitbucketBackend("{\"values\": [ { \"email\": \"michael.bland@gsa.gov\", \"is_primary\": true, \"username\": \"bioinformatics\" } ] }") + emailPayload := `{"values": [ { "email": "michael.bland@gsa.gov", "is_primary": true } ] }` + workspacePayload := `{"values": [ { "type": "workspace_access", "workspace": { "slug": "bioinformatics" } } ] }` + b := testBitbucketBackendWithWorkspace(emailPayload, workspacePayload) defer b.Close() bURL, _ := url.Parse(b.URL) @@ -138,6 +169,97 @@ func TestBitbucketProviderGetEmailAddressAndGroup(t *testing.T) { assert.Equal(t, "michael.bland@gsa.gov", email) } +func TestBitbucketProviderGetEmailAddressMultipleWorkspaces(t *testing.T) { + emailPayload := `{"values": [ { "email": "michael.bland@gsa.gov", "is_primary": true } ] }` + // target workspace is the last entry to exercise the full loop + workspacePayload := `{"values": [ + { "type": "workspace_access", "workspace": { "slug": "other-team-1" } }, + { "type": "workspace_access", "workspace": { "slug": "other-team-2" } }, + { "type": "workspace_access", "workspace": { "slug": "bioinformatics" } } + ]}` + b := testBitbucketBackendWithWorkspace(emailPayload, workspacePayload) + defer b.Close() + + bURL, _ := url.Parse(b.URL) + p := testBitbucketProvider(bURL.Host, "bioinformatics", "") + + session := CreateAuthorizedSession() + email, err := p.GetEmailAddress(context.Background(), session) + assert.Equal(t, nil, err) + assert.Equal(t, "michael.bland@gsa.gov", email) +} + +func TestBitbucketProviderWorkspaceNotInList(t *testing.T) { + emailPayload := `{"values": [ { "email": "michael.bland@gsa.gov", "is_primary": true } ] }` + workspacePayload := `{"values": [ + { "type": "workspace_access", "workspace": { "slug": "other-team-1" } }, + { "type": "workspace_access", "workspace": { "slug": "other-team-2" } } + ]}` + b := testBitbucketBackendWithWorkspace(emailPayload, workspacePayload) + defer b.Close() + + bURL, _ := url.Parse(b.URL) + p := testBitbucketProvider(bURL.Host, "bioinformatics", "") + + session := CreateAuthorizedSession() + email, err := p.GetEmailAddress(context.Background(), session) + assert.Equal(t, nil, err) + assert.Equal(t, "", email) +} + +func TestBitbucketProviderGetEmailAddressAndRepository(t *testing.T) { + emailPayload := `{"values": [ { "email": "michael.bland@gsa.gov", "is_primary": true } ] }` + repoPayload := `{"values": [ { "full_name": "bioinformatics/myrepo" } ] }` + b := testBitbucketBackendFull(emailPayload, "", repoPayload) + defer b.Close() + + bURL, _ := url.Parse(b.URL) + p := testBitbucketProvider(bURL.Host, "", "bioinformatics/myrepo") + + session := CreateAuthorizedSession() + email, err := p.GetEmailAddress(context.Background(), session) + assert.Equal(t, nil, err) + assert.Equal(t, "michael.bland@gsa.gov", email) +} + +func TestBitbucketProviderGetEmailAddressMultipleRepositories(t *testing.T) { + emailPayload := `{"values": [ { "email": "michael.bland@gsa.gov", "is_primary": true } ] }` + // target repo is the last entry to exercise the full loop + repoPayload := `{"values": [ + { "full_name": "bioinformatics/other-repo-1" }, + { "full_name": "bioinformatics/other-repo-2" }, + { "full_name": "bioinformatics/myrepo" } + ]}` + b := testBitbucketBackendFull(emailPayload, "", repoPayload) + defer b.Close() + + bURL, _ := url.Parse(b.URL) + p := testBitbucketProvider(bURL.Host, "", "bioinformatics/myrepo") + + session := CreateAuthorizedSession() + email, err := p.GetEmailAddress(context.Background(), session) + assert.Equal(t, nil, err) + assert.Equal(t, "michael.bland@gsa.gov", email) +} + +func TestBitbucketProviderRepositoryNotInList(t *testing.T) { + emailPayload := `{"values": [ { "email": "michael.bland@gsa.gov", "is_primary": true } ] }` + repoPayload := `{"values": [ + { "full_name": "bioinformatics/other-repo-1" }, + { "full_name": "bioinformatics/other-repo-2" } + ]}` + b := testBitbucketBackendFull(emailPayload, "", repoPayload) + defer b.Close() + + bURL, _ := url.Parse(b.URL) + p := testBitbucketProvider(bURL.Host, "", "bioinformatics/myrepo") + + session := CreateAuthorizedSession() + email, err := p.GetEmailAddress(context.Background(), session) + assert.Equal(t, nil, err) + assert.Equal(t, "", email) +} + // Note that trying to trigger the "failed building request" case is not // practical, since the only way it can fail is if the URL fails to parse. func TestBitbucketProviderGetEmailAddressFailedRequest(t *testing.T) {