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

181 lines
4.9KB

  1. // Package google implements the OAuth2 protocol for authenticating users
  2. // through Google.
  3. package google
  4. import (
  5. "encoding/json"
  6. "fmt"
  7. "io/ioutil"
  8. "net/http"
  9. "net/url"
  10. "strings"
  11. "github.com/markbates/goth"
  12. "golang.org/x/oauth2"
  13. )
  14. const endpointProfile string = "https://www.googleapis.com/oauth2/v2/userinfo"
  15. // New creates a new Google provider, and sets up important connection details.
  16. // You should always call `google.New` to get a new Provider. Never try to create
  17. // one manually.
  18. func New(clientKey, secret, callbackURL string, scopes ...string) *Provider {
  19. p := &Provider{
  20. ClientKey: clientKey,
  21. Secret: secret,
  22. CallbackURL: callbackURL,
  23. providerName: "google",
  24. }
  25. p.config = newConfig(p, scopes)
  26. return p
  27. }
  28. // Provider is the implementation of `goth.Provider` for accessing Google.
  29. type Provider struct {
  30. ClientKey string
  31. Secret string
  32. CallbackURL string
  33. HTTPClient *http.Client
  34. config *oauth2.Config
  35. prompt oauth2.AuthCodeOption
  36. providerName string
  37. }
  38. // Name is the name used to retrieve this provider later.
  39. func (p *Provider) Name() string {
  40. return p.providerName
  41. }
  42. // SetName is to update the name of the provider (needed in case of multiple providers of 1 type)
  43. func (p *Provider) SetName(name string) {
  44. p.providerName = name
  45. }
  46. // Client returns an HTTP client to be used in all fetch operations.
  47. func (p *Provider) Client() *http.Client {
  48. return goth.HTTPClientWithFallBack(p.HTTPClient)
  49. }
  50. // Debug is a no-op for the google package.
  51. func (p *Provider) Debug(debug bool) {}
  52. // BeginAuth asks Google for an authentication endpoint.
  53. func (p *Provider) BeginAuth(state string) (goth.Session, error) {
  54. var opts []oauth2.AuthCodeOption
  55. if p.prompt != nil {
  56. opts = append(opts, p.prompt)
  57. }
  58. url := p.config.AuthCodeURL(state, opts...)
  59. session := &Session{
  60. AuthURL: url,
  61. }
  62. return session, nil
  63. }
  64. type googleUser struct {
  65. ID string `json:"id"`
  66. Email string `json:"email"`
  67. Name string `json:"name"`
  68. FirstName string `json:"given_name"`
  69. LastName string `json:"family_name"`
  70. Link string `json:"link"`
  71. Picture string `json:"picture"`
  72. }
  73. // FetchUser will go to Google and access basic information about the user.
  74. func (p *Provider) FetchUser(session goth.Session) (goth.User, error) {
  75. sess := session.(*Session)
  76. user := goth.User{
  77. AccessToken: sess.AccessToken,
  78. Provider: p.Name(),
  79. RefreshToken: sess.RefreshToken,
  80. ExpiresAt: sess.ExpiresAt,
  81. }
  82. if user.AccessToken == "" {
  83. // Data is not yet retrieved, since accessToken is still empty.
  84. return user, fmt.Errorf("%s cannot get user information without accessToken", p.providerName)
  85. }
  86. response, err := p.Client().Get(endpointProfile + "?access_token=" + url.QueryEscape(sess.AccessToken))
  87. if err != nil {
  88. return user, err
  89. }
  90. defer response.Body.Close()
  91. if response.StatusCode != http.StatusOK {
  92. return user, fmt.Errorf("%s responded with a %d trying to fetch user information", p.providerName, response.StatusCode)
  93. }
  94. responseBytes, err := ioutil.ReadAll(response.Body)
  95. if err != nil {
  96. return user, err
  97. }
  98. var u googleUser
  99. if err := json.Unmarshal(responseBytes, &u); err != nil {
  100. return user, err
  101. }
  102. // Extract the user data we got from Google into our goth.User.
  103. user.Name = u.Name
  104. user.FirstName = u.FirstName
  105. user.LastName = u.LastName
  106. user.NickName = u.Name
  107. user.Email = u.Email
  108. user.AvatarURL = u.Picture
  109. user.UserID = u.ID
  110. // Google provides other useful fields such as 'hd'; get them from RawData
  111. if err := json.Unmarshal(responseBytes, &user.RawData); err != nil {
  112. return user, err
  113. }
  114. return user, nil
  115. }
  116. func newConfig(provider *Provider, scopes []string) *oauth2.Config {
  117. c := &oauth2.Config{
  118. ClientID: provider.ClientKey,
  119. ClientSecret: provider.Secret,
  120. RedirectURL: provider.CallbackURL,
  121. Endpoint: Endpoint,
  122. Scopes: []string{},
  123. }
  124. if len(scopes) > 0 {
  125. for _, scope := range scopes {
  126. c.Scopes = append(c.Scopes, scope)
  127. }
  128. } else {
  129. c.Scopes = []string{"email"}
  130. }
  131. return c
  132. }
  133. //RefreshTokenAvailable refresh token is provided by auth provider or not
  134. func (p *Provider) RefreshTokenAvailable() bool {
  135. return true
  136. }
  137. //RefreshToken get new access token based on the refresh token
  138. func (p *Provider) RefreshToken(refreshToken string) (*oauth2.Token, error) {
  139. token := &oauth2.Token{RefreshToken: refreshToken}
  140. ts := p.config.TokenSource(goth.ContextForClient(p.Client()), token)
  141. newToken, err := ts.Token()
  142. if err != nil {
  143. return nil, err
  144. }
  145. return newToken, err
  146. }
  147. // SetPrompt sets the prompt values for the google OAuth call. Use this to
  148. // force users to choose and account every time by passing "select_account",
  149. // for example.
  150. // See https://developers.google.com/identity/protocols/OpenIDConnect#authenticationuriparameters
  151. func (p *Provider) SetPrompt(prompt ...string) {
  152. if len(prompt) == 0 {
  153. return
  154. }
  155. p.prompt = oauth2.SetAuthURLParam("prompt", strings.Join(prompt, " "))
  156. }
上海开阖软件有限公司 沪ICP备12045867号-1