From a0763477c54f30741e7976499a53c9fbe897455b Mon Sep 17 00:00:00 2001 From: Jehiah Czebotar Date: Thu, 23 Jun 2016 08:29:44 -0400 Subject: [PATCH] Facebook Authentication Provider * will not re-prompt if the email permission is denied, or if you previously authorized the same FB app without the email scope. --- README.md | 6 ++++ api/api.go | 19 ++++++++++ providers/facebook.go | 80 ++++++++++++++++++++++++++++++++++++++++++ providers/providers.go | 2 ++ 4 files changed, 107 insertions(+) create mode 100644 providers/facebook.go diff --git a/README.md b/README.md index b21bf6c..f33711f 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ Valid providers are : * [Google](#google-auth-provider) *default* * [Azure](#azure-auth-provider) +* [Facebook](#facebook-auth-provider) * [GitHub](#github-auth-provider) * [GitLab](#gitlab-auth-provider) * [LinkedIn](#linkedin-auth-provider) @@ -87,6 +88,11 @@ Note: The user is checked against the group members list on initial authenticati The Azure AD auth provider uses `openid` as it default scope. It uses `https://graph.windows.net` as a default protected resource. It call to `https://graph.windows.net/me` to get the email address of the user that logs in. +### Facebook Auth Provider + +1. Create a new FB App from +2. Under FB Login, set your Valid OAuth redirect URIs to `https://internal.yourcompany.com/oauth2/callback` + ### GitHub Auth Provider 1. Create a new project: https://github.com/settings/developers diff --git a/api/api.go b/api/api.go index 245bfb1..e8378ff 100644 --- a/api/api.go +++ b/api/api.go @@ -1,6 +1,7 @@ package api import ( + "encoding/json" "fmt" "io/ioutil" "log" @@ -31,6 +32,24 @@ func Request(req *http.Request) (*simplejson.Json, error) { return data, nil } +func RequestJson(req *http.Request, v interface{}) error { + resp, err := http.DefaultClient.Do(req) + if err != nil { + log.Printf("%s %s %s", req.Method, req.URL, err) + return err + } + body, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + log.Printf("%d %s %s %s", resp.StatusCode, req.Method, req.URL, body) + if err != nil { + return err + } + if resp.StatusCode != 200 { + return fmt.Errorf("got %d %s", resp.StatusCode, body) + } + return json.Unmarshal(body, v) +} + func RequestUnparsedResponse(url string, header http.Header) (resp *http.Response, err error) { req, err := http.NewRequest("GET", url, nil) if err != nil { diff --git a/providers/facebook.go b/providers/facebook.go new file mode 100644 index 0000000..7abb9fe --- /dev/null +++ b/providers/facebook.go @@ -0,0 +1,80 @@ +package providers + +import ( + "errors" + "fmt" + "net/http" + "net/url" + + "github.com/bitly/oauth2_proxy/api" +) + +type FacebookProvider struct { + *ProviderData +} + +func NewFacebookProvider(p *ProviderData) *FacebookProvider { + p.ProviderName = "Facebook" + if p.LoginURL.String() == "" { + p.LoginURL = &url.URL{Scheme: "https", + Host: "www.facebook.com", + Path: "/v2.5/dialog/oauth", + // ?granted_scopes=true + } + } + if p.RedeemURL.String() == "" { + p.RedeemURL = &url.URL{Scheme: "https", + Host: "graph.facebook.com", + Path: "/v2.5/oauth/access_token", + } + } + if p.ProfileURL.String() == "" { + p.ProfileURL = &url.URL{Scheme: "https", + Host: "graph.facebook.com", + Path: "/v2.5/me", + } + } + if p.ValidateURL.String() == "" { + p.ValidateURL = p.ProfileURL + } + if p.Scope == "" { + p.Scope = "public_profile email" + } + return &FacebookProvider{ProviderData: p} +} + +func getFacebookHeader(access_token string) http.Header { + header := make(http.Header) + header.Set("Accept", "application/json") + header.Set("x-li-format", "json") + header.Set("Authorization", fmt.Sprintf("Bearer %s", access_token)) + return header +} + +func (p *FacebookProvider) GetEmailAddress(s *SessionState) (string, error) { + if s.AccessToken == "" { + return "", errors.New("missing access token") + } + req, err := http.NewRequest("GET", p.ProfileURL.String()+"?fields=name,email", nil) + if err != nil { + return "", err + } + req.Header = getFacebookHeader(s.AccessToken) + + type result struct { + Email string + } + var r result + err = api.RequestJson(req, &r) + if err != nil { + return "", err + } + if r.Email == "" { + return "", errors.New("no email") + } + return r.Email, nil +} + +func (p *FacebookProvider) ValidateSessionState(s *SessionState) bool { + return validateToken(p, s.AccessToken, getFacebookHeader(s.AccessToken)) +} diff --git a/providers/providers.go b/providers/providers.go index 010e633..fb2e5fc 100644 --- a/providers/providers.go +++ b/providers/providers.go @@ -22,6 +22,8 @@ func New(provider string, p *ProviderData) Provider { return NewMyUsaProvider(p) case "linkedin": return NewLinkedInProvider(p) + case "facebook": + return NewFacebookProvider(p) case "github": return NewGitHubProvider(p) case "azure":