Unverified Commit e1acb6d5 authored by Stephan Renatus's avatar Stephan Renatus Committed by GitHub

Merge pull request #1307 from edtan/upstream-add-bitbucket-connector

Add Bitbucket connector
parents a1b6ba9b 50afa921
# Authentication through Bitbucket Cloud
## Overview
One of the login options for dex uses the Bitbucket OAuth2 flow to identify the end user through their Bitbucket account.
When a client redeems a refresh token through dex, dex will re-query Bitbucket to update user information in the ID Token. To do this, __dex stores a readonly Bitbucket access token in its backing datastore.__ Users that reject dex's access through Bitbucket will also revoke all dex clients which authenticated them through Bitbucket.
## Configuration
Register a new OAuth consumer with [Bitbucket](https://confluence.atlassian.com/bitbucket/oauth-on-bitbucket-cloud-238027431.html) ensuring the callback URL is `(dex issuer)/callback`. For example if dex is listening at the non-root path `https://auth.example.com/dex` the callback would be `https://auth.example.com/dex/callback`.
The following is an example of a configuration for `examples/config-dev.yaml`:
```yaml
connectors:
- type: bitbucket-cloud
# Required field for connector id.
id: bitbucket-cloud
# Required field for connector name.
name: Bitbucket Cloud
config:
# Credentials can be string literals or pulled from the environment.
clientID: $BITBUCKET_CLIENT_ID
clientSecret: $BITBUCKET_CLIENT_SECRET
redirectURI: http://127.0.0.1:5556/dex/callback
# Optional teams whitelist, communicated through the "groups" scope.
# If `teams` is omitted, all of the user's Bitbucket teams are returned when the groups scope is present.
# If `teams` is provided, this acts as a whitelist - only the user's Bitbucket teams that are in the configured `teams` below will go into the groups claim. Conversely, if the user is not in any of the configured `teams`, the user will not be authenticated.
teams:
- my-team
```
......@@ -73,6 +73,7 @@ Dex implements the following connectors:
| [LinkedIn](Documentation/connectors/linkedin.md) | yes | no | beta | |
| [Microsoft](Documentation/connectors/microsoft.md) | yes | yes | beta | |
| [AuthProxy](Documentation/connectors/authproxy.md) | no | no | alpha | Authentication proxies such as Apache2 mod_auth, etc. |
| [Bitbucket Cloud](Documentation/connectors/bitbucket.md) | yes | yes | alpha | |
Stable, beta, and alpha are defined as:
......
This diff is collapsed.
package bitbucketcloud
import (
"context"
"crypto/tls"
"encoding/json"
"net/http"
"net/http/httptest"
"net/url"
"reflect"
"testing"
"github.com/dexidp/dex/connector"
)
func TestUserGroups(t *testing.T) {
teamsResponse := userTeamsResponse{
pagedResponse: pagedResponse{
Size: 3,
Page: 1,
PageLen: 10,
},
Values: []team{
{Name: "team-1"},
{Name: "team-2"},
{Name: "team-3"},
},
}
s := newTestServer(map[string]interface{}{
"/teams?role=member": teamsResponse,
})
connector := bitbucketConnector{apiURL: s.URL}
groups, err := connector.userTeams(context.Background(), newClient())
expectNil(t, err)
expectEquals(t, groups, []string{
"team-1",
"team-2",
"team-3",
})
s.Close()
}
func TestUserWithoutTeams(t *testing.T) {
s := newTestServer(map[string]interface{}{
"/teams?role=member": userTeamsResponse{},
})
connector := bitbucketConnector{apiURL: s.URL}
groups, err := connector.userTeams(context.Background(), newClient())
expectNil(t, err)
expectEquals(t, len(groups), 0)
s.Close()
}
func TestUsernameIncludedInFederatedIdentity(t *testing.T) {
s := newTestServer(map[string]interface{}{
"/user": user{Username: "some-login"},
"/user/emails": userEmailResponse{
pagedResponse: pagedResponse{
Size: 1,
Page: 1,
PageLen: 10,
},
Values: []userEmail{{
Email: "some@email.com",
IsConfirmed: true,
IsPrimary: true,
}},
},
"/site/oauth2/access_token": map[string]interface{}{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9",
"expires_in": "30",
},
})
hostURL, err := url.Parse(s.URL)
expectNil(t, err)
req, err := http.NewRequest("GET", hostURL.String(), nil)
expectNil(t, err)
bitbucketConnector := bitbucketConnector{apiURL: s.URL, hostName: hostURL.Host, httpClient: newClient()}
identity, err := bitbucketConnector.HandleCallback(connector.Scopes{}, req)
expectNil(t, err)
expectEquals(t, identity.Username, "some-login")
s.Close()
}
func newTestServer(responses map[string]interface{}) *httptest.Server {
return httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-Type", "application/json")
json.NewEncoder(w).Encode(responses[r.URL.String()])
}))
}
func newClient() *http.Client {
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
return &http.Client{Transport: tr}
}
func expectNil(t *testing.T, a interface{}) {
if a != nil {
t.Fatalf("Expected %+v to equal nil", a)
}
}
func expectEquals(t *testing.T, a interface{}, b interface{}) {
if !reflect.DeepEqual(a, b) {
t.Fatalf("Expected %+v to equal %+v", a, b)
}
}
hash: 12d0ad2fc0df4ab221e45c1ba7821708b908033c82741e250cc46dcd445b67eb
updated: 2018-09-18T23:51:30.787348994+02:00
updated: 2018-09-30T14:07:57.901347233-04:00
imports:
- name: github.com/beevik/etree
version: 4cd0dd976db869f817248477718071a28e978df0
......@@ -124,6 +124,7 @@ imports:
- name: golang.org/x/oauth2
version: 08c8d727d2392d18286f9f88ad775ad98f09ab33
subpackages:
- bitbucket
- github
- internal
- name: golang.org/x/sys
......
......@@ -24,6 +24,7 @@ import (
"github.com/dexidp/dex/connector"
"github.com/dexidp/dex/connector/authproxy"
"github.com/dexidp/dex/connector/bitbucketcloud"
"github.com/dexidp/dex/connector/github"
"github.com/dexidp/dex/connector/gitlab"
"github.com/dexidp/dex/connector/ldap"
......@@ -429,16 +430,17 @@ type ConnectorConfig interface {
// ConnectorsConfig variable provides an easy way to return a config struct
// depending on the connector type.
var ConnectorsConfig = map[string]func() ConnectorConfig{
"mockCallback": func() ConnectorConfig { return new(mock.CallbackConfig) },
"mockPassword": func() ConnectorConfig { return new(mock.PasswordConfig) },
"ldap": func() ConnectorConfig { return new(ldap.Config) },
"github": func() ConnectorConfig { return new(github.Config) },
"gitlab": func() ConnectorConfig { return new(gitlab.Config) },
"oidc": func() ConnectorConfig { return new(oidc.Config) },
"saml": func() ConnectorConfig { return new(saml.Config) },
"authproxy": func() ConnectorConfig { return new(authproxy.Config) },
"linkedin": func() ConnectorConfig { return new(linkedin.Config) },
"microsoft": func() ConnectorConfig { return new(microsoft.Config) },
"mockCallback": func() ConnectorConfig { return new(mock.CallbackConfig) },
"mockPassword": func() ConnectorConfig { return new(mock.PasswordConfig) },
"ldap": func() ConnectorConfig { return new(ldap.Config) },
"github": func() ConnectorConfig { return new(github.Config) },
"gitlab": func() ConnectorConfig { return new(gitlab.Config) },
"oidc": func() ConnectorConfig { return new(oidc.Config) },
"saml": func() ConnectorConfig { return new(saml.Config) },
"authproxy": func() ConnectorConfig { return new(authproxy.Config) },
"linkedin": func() ConnectorConfig { return new(linkedin.Config) },
"microsoft": func() ConnectorConfig { return new(microsoft.Config) },
"bitbucket-cloud": func() ConnectorConfig { return new(bitbucketcloud.Config) },
// Keep around for backwards compatibility.
"samlExperimental": func() ConnectorConfig { return new(saml.Config) },
}
......
// Copyright 2015 The oauth2 Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package bitbucket provides constants for using OAuth2 to access Bitbucket.
package bitbucket
import (
"golang.org/x/oauth2"
)
// Endpoint is Bitbucket's OAuth 2.0 endpoint.
var Endpoint = oauth2.Endpoint{
AuthURL: "https://bitbucket.org/site/oauth2/authorize",
TokenURL: "https://bitbucket.org/site/oauth2/access_token",
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment