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

187 lines
5.0KB

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