mirror of https://github.com/dexidp/dex.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
161 lines
3.9 KiB
161 lines
3.9 KiB
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 |
|
}
|
|
|