mirror of https://github.com/dexidp/dex.git
2 changed files with 220 additions and 0 deletions
@ -0,0 +1,161 @@
|
||||
package connector |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"fmt" |
||||
"html/template" |
||||
"net/http" |
||||
"net/url" |
||||
"path" |
||||
|
||||
chttp "github.com/coreos/go-oidc/http" |
||||
"github.com/coreos/go-oidc/oauth2" |
||||
"github.com/coreos/go-oidc/oidc" |
||||
) |
||||
|
||||
const ( |
||||
BitbucketConnectorType = "bitbucket" |
||||
bitbucketAuthURL = "https://bitbucket.org/site/oauth2/authorize" |
||||
bitbucketTokenURL = "https://bitbucket.org/site/oauth2/access_token" |
||||
bitbucketAPIUserURL = "https://bitbucket.org/api/2.0/user" |
||||
bitbucketAPIEmailURL = "https://api.bitbucket.org/2.0/user/emails" |
||||
) |
||||
|
||||
func init() { |
||||
RegisterConnectorConfigType(BitbucketConnectorType, func() ConnectorConfig { return &BitbucketConnectorConfig{} }) |
||||
} |
||||
|
||||
type BitbucketConnectorConfig struct { |
||||
ID string `json:"id"` |
||||
ClientID string `json:"clientID"` |
||||
ClientSecret string `json:"clientSecret"` |
||||
} |
||||
|
||||
func (cfg *BitbucketConnectorConfig) ConnectorID() string { |
||||
return cfg.ID |
||||
} |
||||
|
||||
func (cfg *BitbucketConnectorConfig) ConnectorType() string { |
||||
return BitbucketConnectorType |
||||
} |
||||
|
||||
func (cfg *BitbucketConnectorConfig) Connector(ns url.URL, lf oidc.LoginFunc, tpls *template.Template) (Connector, error) { |
||||
ns.Path = path.Join(ns.Path, httpPathCallback) |
||||
oauth2Conn, err := newBitbucketConnector(cfg.ClientID, cfg.ClientSecret, ns.String()) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return &OAuth2Connector{ |
||||
id: cfg.ID, |
||||
loginFunc: lf, |
||||
cbURL: ns, |
||||
conn: oauth2Conn, |
||||
}, nil |
||||
} |
||||
|
||||
type bitbucketOAuth2Connector struct { |
||||
clientID string |
||||
clientSecret string |
||||
client *oauth2.Client |
||||
} |
||||
|
||||
func newBitbucketConnector(clientID, clientSecret, cbURL string) (oauth2Connector, error) { |
||||
config := oauth2.Config{ |
||||
Credentials: oauth2.ClientCredentials{ID: clientID, Secret: clientSecret}, |
||||
AuthURL: bitbucketAuthURL, |
||||
TokenURL: bitbucketTokenURL, |
||||
AuthMethod: oauth2.AuthMethodClientSecretPost, |
||||
RedirectURL: cbURL, |
||||
} |
||||
|
||||
cli, err := oauth2.NewClient(http.DefaultClient, config) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
return &bitbucketOAuth2Connector{ |
||||
clientID: clientID, |
||||
clientSecret: clientSecret, |
||||
client: cli, |
||||
}, nil |
||||
} |
||||
|
||||
func (c *bitbucketOAuth2Connector) Client() *oauth2.Client { |
||||
return c.client |
||||
} |
||||
|
||||
func (c *bitbucketOAuth2Connector) Identity(cli chttp.Client) (oidc.Identity, error) { |
||||
var user struct { |
||||
UUID string `json:"uuid"` |
||||
Username string `json:"username"` |
||||
DisplayName string `json:"display_name"` |
||||
} |
||||
if err := getAndDecode(cli, bitbucketAPIUserURL, &user); err != nil { |
||||
return oidc.Identity{}, fmt.Errorf("getting user info: %v", err) |
||||
} |
||||
|
||||
name := user.DisplayName |
||||
if name == "" { |
||||
name = user.Username |
||||
} |
||||
|
||||
var emails struct { |
||||
Values []struct { |
||||
Email string `json:"email"` |
||||
Confirmed bool `json:"is_confirmed"` |
||||
Primary bool `json:"is_primary"` |
||||
} `json:"values"` |
||||
} |
||||
if err := getAndDecode(cli, bitbucketAPIEmailURL, &emails); err != nil { |
||||
return oidc.Identity{}, fmt.Errorf("getting user email: %v", err) |
||||
} |
||||
email := "" |
||||
for _, val := range emails.Values { |
||||
if !val.Confirmed { |
||||
continue |
||||
} |
||||
if email == "" || val.Primary { |
||||
email = val.Email |
||||
} |
||||
if val.Primary { |
||||
break |
||||
} |
||||
} |
||||
|
||||
return oidc.Identity{ |
||||
ID: user.UUID, |
||||
Name: name, |
||||
Email: email, |
||||
}, nil |
||||
} |
||||
|
||||
func getAndDecode(cli chttp.Client, url string, v interface{}) error { |
||||
req, err := http.NewRequest("GET", url, nil) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
resp, err := cli.Do(req) |
||||
if err != nil { |
||||
return fmt.Errorf("get: %v", err) |
||||
} |
||||
defer resp.Body.Close() |
||||
switch { |
||||
case resp.StatusCode >= 400 && resp.StatusCode < 500: |
||||
return oauth2.NewError(oauth2.ErrorAccessDenied) |
||||
case resp.StatusCode == http.StatusOK: |
||||
default: |
||||
return fmt.Errorf("unexpected status from providor %s", resp.Status) |
||||
} |
||||
if err := json.NewDecoder(resp.Body).Decode(v); err != nil { |
||||
return fmt.Errorf("decode body: %v", err) |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func (c *bitbucketOAuth2Connector) Healthy() error { |
||||
return nil |
||||
} |
||||
|
||||
func (c *bitbucketOAuth2Connector) TrustedEmailProvider() bool { |
||||
return false |
||||
} |
||||
@ -0,0 +1,59 @@
|
||||
package connector |
||||
|
||||
import ( |
||||
"net/http" |
||||
"testing" |
||||
|
||||
"github.com/coreos/go-oidc/oidc" |
||||
) |
||||
|
||||
var bitbucketExampleUser1 = `{ |
||||
"display_name": "tutorials account", |
||||
"username": "tutorials", |
||||
"uuid": "{c788b2da-b7a2-404c-9e26-d3f077557007}" |
||||
}` |
||||
|
||||
var bitbucketExampleUser2 = `{ |
||||
"username": "tutorials", |
||||
"uuid": "{c788b2da-b7a2-404c-9e26-d3f077557007}" |
||||
}` |
||||
|
||||
var bitbucketExampleEmail = `{ |
||||
"values": [ |
||||
{"email": "tutorials1@bitbucket.org","is_confirmed": false,"is_primary": false}, |
||||
{"email": "tutorials2@bitbucket.org","is_confirmed": true,"is_primary": false}, |
||||
{"email": "tutorials3@bitbucket.org","is_confirmed": true,"is_primary": true} |
||||
] |
||||
}` |
||||
|
||||
func TestBitBucketIdentity(t *testing.T) { |
||||
tests := []oauth2IdentityTest{ |
||||
{ |
||||
urlResps: map[string]response{ |
||||
bitbucketAPIUserURL: {http.StatusOK, bitbucketExampleUser1}, |
||||
bitbucketAPIEmailURL: {http.StatusOK, bitbucketExampleEmail}, |
||||
}, |
||||
want: oidc.Identity{ |
||||
Name: "tutorials account", |
||||
ID: "{c788b2da-b7a2-404c-9e26-d3f077557007}", |
||||
Email: "tutorials3@bitbucket.org", |
||||
}, |
||||
}, |
||||
{ |
||||
urlResps: map[string]response{ |
||||
bitbucketAPIUserURL: {http.StatusOK, bitbucketExampleUser2}, |
||||
bitbucketAPIEmailURL: {http.StatusOK, bitbucketExampleEmail}, |
||||
}, |
||||
want: oidc.Identity{ |
||||
Name: "tutorials", |
||||
ID: "{c788b2da-b7a2-404c-9e26-d3f077557007}", |
||||
Email: "tutorials3@bitbucket.org", |
||||
}, |
||||
}, |
||||
} |
||||
conn, err := newBitbucketConnector("fakeclientid", "fakeclientsecret", "http://example.com/auth/bitbucket/callback") |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
runOAuth2IdentityTests(t, conn, tests) |
||||
} |
||||
Loading…
Reference in new issue