A clean, Markdown-based publishing platform made for writers. Write together, and build a community. https://writefreely.org
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.
 
 
 
 
 

134 lines
3.8 KiB

  1. package writefreely
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "github.com/writeas/web-core/log"
  7. "net/http"
  8. "net/url"
  9. "strings"
  10. )
  11. type giteaOauthClient struct {
  12. ClientID string
  13. ClientSecret string
  14. AuthLocation string
  15. ExchangeLocation string
  16. InspectLocation string
  17. CallbackLocation string
  18. Scope string
  19. MapUserID string
  20. MapUsername string
  21. MapDisplayName string
  22. MapEmail string
  23. HttpClient HttpClient
  24. }
  25. var _ oauthClient = giteaOauthClient{}
  26. const (
  27. giteaDisplayName = "Gitea"
  28. )
  29. func (c giteaOauthClient) GetProvider() string {
  30. return "gitea"
  31. }
  32. func (c giteaOauthClient) GetClientID() string {
  33. return c.ClientID
  34. }
  35. func (c giteaOauthClient) GetCallbackLocation() string {
  36. return c.CallbackLocation
  37. }
  38. func (c giteaOauthClient) buildLoginURL(state string) (string, error) {
  39. u, err := url.Parse(c.AuthLocation)
  40. if err != nil {
  41. return "", err
  42. }
  43. q := u.Query()
  44. q.Set("client_id", c.ClientID)
  45. q.Set("redirect_uri", c.CallbackLocation)
  46. q.Set("response_type", "code")
  47. q.Set("state", state)
  48. q.Set("scope", c.Scope)
  49. u.RawQuery = q.Encode()
  50. return u.String(), nil
  51. }
  52. func (c giteaOauthClient) exchangeOauthCode(ctx context.Context, code string) (*TokenResponse, error) {
  53. form := url.Values{}
  54. form.Add("grant_type", "authorization_code")
  55. form.Add("redirect_uri", c.CallbackLocation)
  56. form.Add("scope", c.Scope)
  57. form.Add("code", code)
  58. req, err := http.NewRequest("POST", c.ExchangeLocation, strings.NewReader(form.Encode()))
  59. if err != nil {
  60. return nil, err
  61. }
  62. req.WithContext(ctx)
  63. req.Header.Set("User-Agent", ServerUserAgent(""))
  64. req.Header.Set("Accept", "application/json")
  65. req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
  66. req.SetBasicAuth(c.ClientID, c.ClientSecret)
  67. resp, err := c.HttpClient.Do(req)
  68. if err != nil {
  69. return nil, err
  70. }
  71. if resp.StatusCode != http.StatusOK {
  72. return nil, errors.New("unable to exchange code for access token")
  73. }
  74. var tokenResponse TokenResponse
  75. if err := limitedJsonUnmarshal(resp.Body, tokenRequestMaxLen, &tokenResponse); err != nil {
  76. return nil, err
  77. }
  78. if tokenResponse.Error != "" {
  79. return nil, errors.New(tokenResponse.Error)
  80. }
  81. return &tokenResponse, nil
  82. }
  83. func (c giteaOauthClient) inspectOauthAccessToken(ctx context.Context, accessToken string) (*InspectResponse, error) {
  84. req, err := http.NewRequest("GET", c.InspectLocation, nil)
  85. if err != nil {
  86. return nil, err
  87. }
  88. req.WithContext(ctx)
  89. req.Header.Set("User-Agent", ServerUserAgent(""))
  90. req.Header.Set("Accept", "application/json")
  91. req.Header.Set("Authorization", "Bearer "+accessToken)
  92. resp, err := c.HttpClient.Do(req)
  93. if err != nil {
  94. return nil, err
  95. }
  96. if resp.StatusCode != http.StatusOK {
  97. return nil, errors.New("unable to inspect access token")
  98. }
  99. // since we don't know what the JSON from the server will look like, we create a
  100. // generic interface and then map manually to values set in the config
  101. var genericInterface map[string]interface{}
  102. if err := limitedJsonUnmarshal(resp.Body, infoRequestMaxLen, &genericInterface); err != nil {
  103. return nil, err
  104. }
  105. // map each relevant field in inspectResponse to the mapped field from the config
  106. var inspectResponse InspectResponse
  107. inspectResponse.UserID, _ = genericInterface[c.MapUserID].(string)
  108. // log.Info("Userid from Gitea: %s", inspectResponse.UserID)
  109. if inspectResponse.UserID == "" {
  110. log.Error("[CONFIGURATION ERROR] Gitea OAuth provider returned empty UserID value (`%s`).\n Do you need to configure a different `map_user_id` value for this provider?", c.MapUserID)
  111. return nil, fmt.Errorf("no UserID (`%s`) value returned", c.MapUserID)
  112. }
  113. inspectResponse.Username, _ = genericInterface[c.MapUsername].(string)
  114. inspectResponse.DisplayName, _ = genericInterface[c.MapDisplayName].(string)
  115. inspectResponse.Email, _ = genericInterface[c.MapEmail].(string)
  116. return &inspectResponse, nil
  117. }