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.

206 lines
5.1 KiB

  1. // Package bitbucket implements the OAuth2 protocol for authenticating users through Bitbucket.
  2. package bitbucket
  3. import (
  4. "bytes"
  5. "encoding/json"
  6. "io"
  7. "io/ioutil"
  8. "net/http"
  9. "net/url"
  10. "github.com/markbates/goth"
  11. "golang.org/x/oauth2"
  12. "fmt"
  13. )
  14. const (
  15. authURL string = "https://bitbucket.org/site/oauth2/authorize"
  16. tokenURL string = "https://bitbucket.org/site/oauth2/access_token"
  17. endpointProfile string = "https://api.bitbucket.org/2.0/user"
  18. endpointEmail string = "https://api.bitbucket.org/2.0/user/emails"
  19. )
  20. // New creates a new Bitbucket provider, and sets up important connection details.
  21. // You should always call `bitbucket.New` to get a new Provider. Never try to create
  22. // one manually.
  23. func New(clientKey, secret, callbackURL string, scopes ...string) *Provider {
  24. p := &Provider{
  25. ClientKey: clientKey,
  26. Secret: secret,
  27. CallbackURL: callbackURL,
  28. providerName: "bitbucket",
  29. }
  30. p.config = newConfig(p, scopes)
  31. return p
  32. }
  33. // Provider is the implementation of `goth.Provider` for accessing Bitbucket.
  34. type Provider struct {
  35. ClientKey string
  36. Secret string
  37. CallbackURL string
  38. HTTPClient *http.Client
  39. config *oauth2.Config
  40. providerName string
  41. }
  42. // Name is the name used to retrieve this provider later.
  43. func (p *Provider) Name() string {
  44. return p.providerName
  45. }
  46. // SetName is to update the name of the provider (needed in case of multiple providers of 1 type)
  47. func (p *Provider) SetName(name string) {
  48. p.providerName = name
  49. }
  50. func (p *Provider) Client() *http.Client {
  51. return goth.HTTPClientWithFallBack(p.HTTPClient)
  52. }
  53. // Debug is a no-op for the bitbucket package.
  54. func (p *Provider) Debug(debug bool) {}
  55. // BeginAuth asks Bitbucket for an authentication end-point.
  56. func (p *Provider) BeginAuth(state string) (goth.Session, error) {
  57. url := p.config.AuthCodeURL(state)
  58. session := &Session{
  59. AuthURL: url,
  60. }
  61. return session, nil
  62. }
  63. // FetchUser will go to Bitbucket and access basic information about the user.
  64. func (p *Provider) FetchUser(session goth.Session) (goth.User, error) {
  65. sess := session.(*Session)
  66. user := goth.User{
  67. AccessToken: sess.AccessToken,
  68. Provider: p.Name(),
  69. RefreshToken: sess.RefreshToken,
  70. ExpiresAt: sess.ExpiresAt,
  71. }
  72. if user.AccessToken == "" {
  73. // data is not yet retrieved since accessToken is still empty
  74. return user, fmt.Errorf("%s cannot get user information without accessToken", p.providerName)
  75. }
  76. response, err := goth.HTTPClientWithFallBack(p.Client()).Get(endpointProfile + "?access_token=" + url.QueryEscape(sess.AccessToken))
  77. if err != nil {
  78. return user, err
  79. }
  80. defer response.Body.Close()
  81. if response.StatusCode != http.StatusOK {
  82. return user, fmt.Errorf("%s responded with a %d trying to fetch user information", p.providerName, response.StatusCode)
  83. }
  84. bits, err := ioutil.ReadAll(response.Body)
  85. if err != nil {
  86. return user, err
  87. }
  88. err = json.NewDecoder(bytes.NewReader(bits)).Decode(&user.RawData)
  89. if err != nil {
  90. return user, err
  91. }
  92. err = userFromReader(bytes.NewReader(bits), &user)
  93. response, err = goth.HTTPClientWithFallBack(p.Client()).Get(endpointEmail + "?access_token=" + url.QueryEscape(sess.AccessToken))
  94. if err != nil {
  95. return user, err
  96. }
  97. defer response.Body.Close()
  98. bits, err = ioutil.ReadAll(response.Body)
  99. if err != nil {
  100. return user, err
  101. }
  102. err = emailFromReader(bytes.NewReader(bits), &user)
  103. return user, err
  104. }
  105. func userFromReader(reader io.Reader, user *goth.User) error {
  106. u := struct {
  107. ID string `json:"uuid"`
  108. Links struct {
  109. Avatar struct {
  110. URL string `json:"href"`
  111. } `json:"avatar"`
  112. } `json:"links"`
  113. Email string `json:"email"`
  114. Username string `json:"username"`
  115. Name string `json:"display_name"`
  116. Location string `json:"location"`
  117. }{}
  118. err := json.NewDecoder(reader).Decode(&u)
  119. if err != nil {
  120. return err
  121. }
  122. user.Name = u.Name
  123. user.NickName = u.Username
  124. user.AvatarURL = u.Links.Avatar.URL
  125. user.UserID = u.ID
  126. user.Location = u.Location
  127. return err
  128. }
  129. func emailFromReader(reader io.Reader, user *goth.User) error {
  130. e := struct {
  131. Values []struct {
  132. Email string `json:"email"`
  133. } `json:"values"`
  134. }{}
  135. err := json.NewDecoder(reader).Decode(&e)
  136. if err != nil {
  137. return err
  138. }
  139. if len(e.Values) > 0 {
  140. user.Email = e.Values[0].Email
  141. }
  142. return err
  143. }
  144. func newConfig(provider *Provider, scopes []string) *oauth2.Config {
  145. c := &oauth2.Config{
  146. ClientID: provider.ClientKey,
  147. ClientSecret: provider.Secret,
  148. RedirectURL: provider.CallbackURL,
  149. Endpoint: oauth2.Endpoint{
  150. AuthURL: authURL,
  151. TokenURL: tokenURL,
  152. },
  153. Scopes: []string{},
  154. }
  155. for _, scope := range scopes {
  156. c.Scopes = append(c.Scopes, scope)
  157. }
  158. return c
  159. }
  160. //RefreshTokenAvailable refresh token is provided by auth provider or not
  161. func (p *Provider) RefreshTokenAvailable() bool {
  162. return true
  163. }
  164. //RefreshToken get new access token based on the refresh token
  165. func (p *Provider) RefreshToken(refreshToken string) (*oauth2.Token, error) {
  166. token := &oauth2.Token{RefreshToken: refreshToken}
  167. ts := p.config.TokenSource(goth.ContextForClient(p.Client()), token)
  168. newToken, err := ts.Token()
  169. if err != nil {
  170. return nil, err
  171. }
  172. return newToken, err
  173. }