dex/connector/connector_bitbucket.go
Eric Chiang 3a23f6bc33 connector: add bitbucket connector
Add bitbucket implementation of oauth2 connector.
2015-12-08 10:19:29 -08:00

162 lines
3.9 KiB
Go

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
}