本站源代码
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.

217 lines
5.5KB

  1. // Package facebook implements the OAuth2 protocol for authenticating users through Facebook.
  2. // This package can be used as a reference implementation of an OAuth2 provider for Goth.
  3. package facebook
  4. import (
  5. "bytes"
  6. "crypto/hmac"
  7. "crypto/sha256"
  8. "encoding/hex"
  9. "encoding/json"
  10. "errors"
  11. "fmt"
  12. "io"
  13. "io/ioutil"
  14. "net/http"
  15. "net/url"
  16. "strings"
  17. "github.com/markbates/goth"
  18. "golang.org/x/oauth2"
  19. )
  20. const (
  21. authURL string = "https://www.facebook.com/dialog/oauth"
  22. tokenURL string = "https://graph.facebook.com/oauth/access_token"
  23. endpointProfile string = "https://graph.facebook.com/me?fields="
  24. )
  25. // New creates a new Facebook provider, and sets up important connection details.
  26. // You should always call `facebook.New` to get a new Provider. Never try to create
  27. // one manually.
  28. func New(clientKey, secret, callbackURL string, scopes ...string) *Provider {
  29. p := &Provider{
  30. ClientKey: clientKey,
  31. Secret: secret,
  32. CallbackURL: callbackURL,
  33. providerName: "facebook",
  34. }
  35. p.config = newConfig(p, scopes)
  36. p.Fields = "email,first_name,last_name,link,about,id,name,picture,location"
  37. return p
  38. }
  39. // Provider is the implementation of `goth.Provider` for accessing Facebook.
  40. type Provider struct {
  41. ClientKey string
  42. Secret string
  43. CallbackURL string
  44. HTTPClient *http.Client
  45. Fields string
  46. config *oauth2.Config
  47. providerName string
  48. }
  49. // Name is the name used to retrieve this provider later.
  50. func (p *Provider) Name() string {
  51. return p.providerName
  52. }
  53. // SetName is to update the name of the provider (needed in case of multiple providers of 1 type)
  54. func (p *Provider) SetName(name string) {
  55. p.providerName = name
  56. }
  57. // SetCustomFields sets the fields used to return information
  58. // for a user.
  59. //
  60. // A list of available field values can be found at
  61. // https://developers.facebook.com/docs/graph-api/reference/user
  62. func (p *Provider) SetCustomFields(fields []string) *Provider {
  63. p.Fields = strings.Join(fields, ",")
  64. return p
  65. }
  66. func (p *Provider) Client() *http.Client {
  67. return goth.HTTPClientWithFallBack(p.HTTPClient)
  68. }
  69. // Debug is a no-op for the facebook package.
  70. func (p *Provider) Debug(debug bool) {}
  71. // BeginAuth asks Facebook for an authentication end-point.
  72. func (p *Provider) BeginAuth(state string) (goth.Session, error) {
  73. authUrl := p.config.AuthCodeURL(state)
  74. session := &Session{
  75. AuthURL: authUrl,
  76. }
  77. return session, nil
  78. }
  79. // FetchUser will go to Facebook and access basic information about the user.
  80. func (p *Provider) FetchUser(session goth.Session) (goth.User, error) {
  81. sess := session.(*Session)
  82. user := goth.User{
  83. AccessToken: sess.AccessToken,
  84. Provider: p.Name(),
  85. ExpiresAt: sess.ExpiresAt,
  86. }
  87. if user.AccessToken == "" {
  88. // data is not yet retrieved since accessToken is still empty
  89. return user, fmt.Errorf("%s cannot get user information without accessToken", p.providerName)
  90. }
  91. // always add appsecretProof to make calls more protected
  92. // https://github.com/markbates/goth/issues/96
  93. // https://developers.facebook.com/docs/graph-api/securing-requests
  94. hash := hmac.New(sha256.New, []byte(p.Secret))
  95. hash.Write([]byte(sess.AccessToken))
  96. appsecretProof := hex.EncodeToString(hash.Sum(nil))
  97. reqUrl := fmt.Sprint(
  98. endpointProfile,
  99. p.Fields,
  100. "&access_token=",
  101. url.QueryEscape(sess.AccessToken),
  102. "&appsecret_proof=",
  103. appsecretProof,
  104. )
  105. response, err := p.Client().Get(reqUrl)
  106. if err != nil {
  107. return user, err
  108. }
  109. defer response.Body.Close()
  110. if response.StatusCode != http.StatusOK {
  111. return user, fmt.Errorf("%s responded with a %d trying to fetch user information", p.providerName, response.StatusCode)
  112. }
  113. bits, err := ioutil.ReadAll(response.Body)
  114. if err != nil {
  115. return user, err
  116. }
  117. err = json.NewDecoder(bytes.NewReader(bits)).Decode(&user.RawData)
  118. if err != nil {
  119. return user, err
  120. }
  121. err = userFromReader(bytes.NewReader(bits), &user)
  122. return user, err
  123. }
  124. func userFromReader(reader io.Reader, user *goth.User) error {
  125. u := struct {
  126. ID string `json:"id"`
  127. Email string `json:"email"`
  128. About string `json:"about"`
  129. Name string `json:"name"`
  130. FirstName string `json:"first_name"`
  131. LastName string `json:"last_name"`
  132. Link string `json:"link"`
  133. Picture struct {
  134. Data struct {
  135. URL string `json:"url"`
  136. } `json:"data"`
  137. } `json:"picture"`
  138. Location struct {
  139. Name string `json:"name"`
  140. } `json:"location"`
  141. }{}
  142. err := json.NewDecoder(reader).Decode(&u)
  143. if err != nil {
  144. return err
  145. }
  146. user.Name = u.Name
  147. user.FirstName = u.FirstName
  148. user.LastName = u.LastName
  149. user.NickName = u.Name
  150. user.Email = u.Email
  151. user.Description = u.About
  152. user.AvatarURL = u.Picture.Data.URL
  153. user.UserID = u.ID
  154. user.Location = u.Location.Name
  155. return err
  156. }
  157. func newConfig(provider *Provider, scopes []string) *oauth2.Config {
  158. c := &oauth2.Config{
  159. ClientID: provider.ClientKey,
  160. ClientSecret: provider.Secret,
  161. RedirectURL: provider.CallbackURL,
  162. Endpoint: oauth2.Endpoint{
  163. AuthURL: authURL,
  164. TokenURL: tokenURL,
  165. },
  166. Scopes: []string{
  167. "email",
  168. },
  169. }
  170. defaultScopes := map[string]struct{}{
  171. "email": {},
  172. }
  173. for _, scope := range scopes {
  174. if _, exists := defaultScopes[scope]; !exists {
  175. c.Scopes = append(c.Scopes, scope)
  176. }
  177. }
  178. return c
  179. }
  180. //RefreshToken refresh token is not provided by facebook
  181. func (p *Provider) RefreshToken(refreshToken string) (*oauth2.Token, error) {
  182. return nil, errors.New("Refresh token is not provided by facebook")
  183. }
  184. //RefreshTokenAvailable refresh token is not provided by facebook
  185. func (p *Provider) RefreshTokenAvailable() bool {
  186. return false
  187. }
上海开阖软件有限公司 沪ICP备12045867号-1