dex/connector/connector_facebook.go
ajanthan 464b7fb1af Connector: Connector for Facebook
This implements dex connector for facebook

Fixes #391
2016-10-07 16:19:44 -07:00

148 lines
3.8 KiB
Go

package connector
import (
"encoding/json"
"fmt"
chttp "github.com/coreos/go-oidc/http"
"github.com/coreos/go-oidc/oauth2"
"github.com/coreos/go-oidc/oidc"
"html/template"
"net/http"
"net/url"
"path"
)
const (
FacebookConnectorType = "facebook"
facebookConnectorAuthURL = "https://www.facebook.com/dialog/oauth"
facebookTokenURL = "https://graph.facebook.com/v2.3/oauth/access_token"
facebookGraphAPIURL = "https://graph.facebook.com/me?fields=id,name,email"
)
type FacebookConnectorConfig struct {
ID string `json:"id"`
ClientID string `json:"clientID"`
ClientSecret string `json:"clientSecret"`
}
func init() {
RegisterConnectorConfigType(FacebookConnectorType, func() ConnectorConfig { return &FacebookConnectorConfig{} })
}
func (cfg *FacebookConnectorConfig) ConnectorID() string {
return cfg.ID
}
func (cfg *FacebookConnectorConfig) ConnectorType() string {
return FacebookConnectorType
}
func (cfg *FacebookConnectorConfig) Connector(ns url.URL, lf oidc.LoginFunc, tpls *template.Template) (Connector, error) {
ns.Path = path.Join(ns.Path, httpPathCallback)
oauth2Conn, err := newFacebookConnector(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 facebookOAuth2Connector struct {
clientID string
clientSecret string
client *oauth2.Client
}
func newFacebookConnector(clientID, clientSecret, cbURL string) (oauth2Connector, error) {
config := oauth2.Config{
Credentials: oauth2.ClientCredentials{ID: clientID, Secret: clientSecret},
AuthURL: facebookConnectorAuthURL,
TokenURL: facebookTokenURL,
AuthMethod: oauth2.AuthMethodClientSecretPost,
RedirectURL: cbURL,
Scope: []string{"email"},
}
cli, err := oauth2.NewClient(http.DefaultClient, config)
if err != nil {
return nil, err
}
return &facebookOAuth2Connector{
clientID: clientID,
clientSecret: clientSecret,
client: cli,
}, nil
}
func (c *facebookOAuth2Connector) Client() *oauth2.Client {
return c.client
}
func (c *facebookOAuth2Connector) Healthy() error {
return nil
}
func (c *facebookOAuth2Connector) TrustedEmailProvider() bool {
return false
}
type ErrorMessage struct {
Message string `json:"message"`
Type string `json:"type"`
Code int `json:"code"`
ErrorSubCode int `json:"error_subcode"`
ErrorUserTitle string `json:"error_user_title"`
ErrorUserMsg string `json:"error_user_msg"`
FbTraceId string `json:"fbtrace_id"`
}
type facebookErr struct {
ErrorMessage ErrorMessage `json:"error"`
}
func (err facebookErr) Error() string {
return fmt.Sprintf("facebook: %s", err.ErrorMessage.Message)
}
func (c *facebookOAuth2Connector) Identity(cli chttp.Client) (oidc.Identity, error) {
var user struct {
ID string `json:"id"`
Email string `json:"email"`
Name string `json:"name"`
}
req, err := http.NewRequest("GET", facebookGraphAPIURL, nil)
if err != nil {
return oidc.Identity{}, err
}
resp, err := cli.Do(req)
if err != nil {
return oidc.Identity{}, fmt.Errorf("get: %v", err)
}
defer resp.Body.Close()
switch {
case resp.StatusCode >= 400 && resp.StatusCode < 600:
var authErr facebookErr
if err := json.NewDecoder(resp.Body).Decode(&authErr); err != nil {
return oidc.Identity{}, oauth2.NewError(oauth2.ErrorAccessDenied)
}
return oidc.Identity{}, authErr
case resp.StatusCode == http.StatusOK:
default:
return oidc.Identity{}, fmt.Errorf("unexpected status from providor %s", resp.Status)
}
if err := json.NewDecoder(resp.Body).Decode(&user); err != nil {
return oidc.Identity{}, fmt.Errorf("decode body: %v", err)
}
return oidc.Identity{
ID: user.ID,
Name: user.Name,
Email: user.Email,
}, nil
}