From afe73aa09952f6b3fceaf9e9349601dcf1a679bc Mon Sep 17 00:00:00 2001 From: Andres Felipe De Castro Date: Tue, 20 Feb 2018 00:40:27 +0200 Subject: [PATCH] #60 | Add Facebook Oauth provider --- oauth2/facebook.go | 76 +++++++++++++++++++++++++++++++++++++++++ oauth2/facebook_test.go | 68 ++++++++++++++++++++++++++++++++++++ oauth2/provider_test.go | 7 +++- 3 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 oauth2/facebook.go create mode 100644 oauth2/facebook_test.go diff --git a/oauth2/facebook.go b/oauth2/facebook.go new file mode 100644 index 0000000..20201fc --- /dev/null +++ b/oauth2/facebook.go @@ -0,0 +1,76 @@ +package oauth2 + +import ( + "github.com/tarent/loginsrv/model" + "fmt" + "net/http" + "io/ioutil" + "encoding/json" + "strings" +) + +var facebookAPI = "https://graph.facebook.com/v2.12" + +func init() { + RegisterProvider(providerfacebook) +} + +// facebookUser is used for parsing the facebook response +type facebookUser struct { + UserID string `json:"id,omitempty"` + Picture struct{ + Data struct{ + URL string `json:"url,omitempty"` + } `json:"data,omitempty"` + } `json:"picture,omitempty"` + Name string `json:"name,omitempty"` + Email string `json:"email,omitempty"` +} + +var providerfacebook = Provider{ + Name: "facebook", + AuthURL: "https://www.facebook.com/v2.12/dialog/oauth", + TokenURL: "https://graph.facebook.com/v2.12/oauth/access_token", + GetUserInfo: func(token TokenInfo) (model.UserInfo, string, error) { + fu := facebookUser{} + + url := fmt.Sprintf("%v/me?access_token=%v&fields=name,email,id,picture", facebookAPI, token.AccessToken) + + // For facebook return an application/json Content-type the Accept header should be set as 'application/json' + client := &http.Client{} + contentType := "application/json" + req, _ := http.NewRequest("GET", url, nil) + req.Header.Set("Accept", contentType) + resp, err := client.Do(req) + + if err != nil { + return model.UserInfo{}, "", err + } + + if !strings.Contains(resp.Header.Get("Content-Type"), contentType) { + return model.UserInfo{}, "", fmt.Errorf("wrong content-type on facebook get user info: %v", resp.Header.Get("Content-Type")) + } + + if resp.StatusCode != 200 { + return model.UserInfo{}, "", fmt.Errorf("got http status %v on facebook get user info", resp.StatusCode) + } + + b, err := ioutil.ReadAll(resp.Body) + if err != nil { + return model.UserInfo{}, "", fmt.Errorf("error reading facebook get user info: %v", err) + } + + err = json.Unmarshal(b, &fu) + if err != nil { + return model.UserInfo{}, "", fmt.Errorf("error parsing facebook get user info: %v", err) + } + + return model.UserInfo{ + Sub: fu.UserID, + Picture: fu.Picture.Data.URL, + Name: fu.Name, + Email: fu.Email, + Origin: "facebook", + }, string(b), nil + }, +} diff --git a/oauth2/facebook_test.go b/oauth2/facebook_test.go new file mode 100644 index 0000000..c52e6c9 --- /dev/null +++ b/oauth2/facebook_test.go @@ -0,0 +1,68 @@ +package oauth2 + +import ( + . "github.com/stretchr/testify/assert" + "net/http" + "net/http/httptest" + "testing" +) + +var facebookTestUserResponse = `{ + "id": "23456789012345678", + "name": "Facebook User", + "picture": { + "data": { + "height": 100, + "is_silhouette": false, + "url": "https://scontent.xx.fbcdn.net/v/t1.0-1/p100x100/example_facebook_image.jpg", + "width": 100 + } + }, + "email": "facebookuser@facebook.com" +}` + +func Test_Facebook_getUserInfo(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + Equal(t, "secret", r.FormValue("access_token")) + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.Write([]byte(facebookTestUserResponse)) + })) + defer server.Close() + + facebookAPI = server.URL + + u, rawJSON, err := providerfacebook.GetUserInfo(TokenInfo{AccessToken: "secret"}) + NoError(t, err) + Equal(t, "23456789012345678", u.Sub) + Equal(t, "facebookuser@facebook.com", u.Email) + Equal(t, "Facebook User", u.Name) + Equal(t, facebookTestUserResponse, rawJSON) +} + +func Test_Facebook_getUserInfo_WrongContentType(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + Equal(t, "secret", r.FormValue("access_token")) + w.Header().Set("Content-Type", "text/javascript; charset=utf-8") + w.Write([]byte(facebookTestUserResponse)) + })) + defer server.Close() + + facebookAPI = server.URL + + _, _, err := providerfacebook.GetUserInfo(TokenInfo{AccessToken: "secret"}) + Error(t, err) +} + +func Test_Facebook_getUserInfo_WrongStatus(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + Equal(t, "secret", r.FormValue("access_token")) + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(facebookTestUserResponse)) + })) + defer server.Close() + + facebookAPI = server.URL + + _, _, err := providerfacebook.GetUserInfo(TokenInfo{AccessToken: "secret"}) + Error(t, err) +} diff --git a/oauth2/provider_test.go b/oauth2/provider_test.go index 7be526c..b6c8384 100644 --- a/oauth2/provider_test.go +++ b/oauth2/provider_test.go @@ -18,9 +18,14 @@ func Test_ProviderRegistration(t *testing.T) { NotNil(t, bitbucket) True(t, exist) + facebook, exist := GetProvider("facebook") + NotNil(t, facebook) + True(t, exist) + list := ProviderList() - Equal(t, 3, len(list)) + Equal(t, 4, len(list)) Contains(t, list, "github") Contains(t, list, "google") Contains(t, list, "bitbucket") + Contains(t, list, "facebook") }