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.
 
 
 
 
 

127 lines
3.4 KiB

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