mirror of https://github.com/dexidp/dex.git
3 changed files with 265 additions and 0 deletions
@ -0,0 +1,148 @@
|
||||
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 |
||||
} |
||||
@ -0,0 +1,71 @@
|
||||
package connector |
||||
|
||||
import ( |
||||
"github.com/coreos/go-oidc/oidc" |
||||
"net/http" |
||||
"testing" |
||||
) |
||||
|
||||
var facebookUser1 = `{ |
||||
"id":"testUser1", |
||||
"name":"testUser1Fname testUser1Lname", |
||||
"email": "testUser1@facebook.com" |
||||
}` |
||||
|
||||
var facebookUser2 = `{ |
||||
"id":"testUser2", |
||||
"name":"testUser2Fname testUser2Lname", |
||||
"email": "testUser2@facebook.com" |
||||
}` |
||||
|
||||
var facebookExampleError = `{ |
||||
"error": { |
||||
"message": "Invalid OAuth access token signature.", |
||||
"type": "OAuthException", |
||||
"code": 190, |
||||
"fbtrace_id": "Ee/6W0EfrWP" |
||||
} |
||||
}` |
||||
|
||||
func TestFacebookIdentity(t *testing.T) { |
||||
tests := []oauth2IdentityTest{ |
||||
{ |
||||
urlResps: map[string]response{ |
||||
facebookGraphAPIURL: {http.StatusOK, facebookUser1}, |
||||
}, |
||||
want: oidc.Identity{ |
||||
Name: "testUser1Fname testUser1Lname", |
||||
ID: "testUser1", |
||||
Email: "testUser1@facebook.com", |
||||
}, |
||||
}, |
||||
{ |
||||
urlResps: map[string]response{ |
||||
facebookGraphAPIURL: {http.StatusOK, facebookUser2}, |
||||
}, |
||||
want: oidc.Identity{ |
||||
Name: "testUser2Fname testUser2Lname", |
||||
ID: "testUser2", |
||||
Email: "testUser2@facebook.com", |
||||
}, |
||||
}, |
||||
{ |
||||
urlResps: map[string]response{ |
||||
facebookGraphAPIURL: {http.StatusUnauthorized, facebookExampleError}, |
||||
}, |
||||
wantErr: facebookErr{ |
||||
ErrorMessage: ErrorMessage{ |
||||
Code: 190, |
||||
Type: "OAuthException", |
||||
Message: "Invalid OAuth access token signature.", |
||||
FbTraceId: "Ee/6W0EfrWP", |
||||
}, |
||||
}, |
||||
}, |
||||
} |
||||
conn, err := newFacebookConnector("fakeFacebookAppID", "fakeFacebookAppSecret", "http://example.com/auth/facebook/callback") |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
runOAuth2IdentityTests(t, conn, tests) |
||||
} |
||||
Loading…
Reference in new issue