token.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. // Copyright 2014 The Go Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. package internal
  5. import (
  6. "context"
  7. "encoding/json"
  8. "errors"
  9. "fmt"
  10. "io"
  11. "io/ioutil"
  12. "math"
  13. "mime"
  14. "net/http"
  15. "net/url"
  16. "strconv"
  17. "strings"
  18. "sync"
  19. "sync/atomic"
  20. "time"
  21. )
  22. // Token represents the credentials used to authorize
  23. // the requests to access protected resources on the OAuth 2.0
  24. // provider's backend.
  25. //
  26. // This type is a mirror of oauth2.Token and exists to break
  27. // an otherwise-circular dependency. Other internal packages
  28. // should convert this Token into an oauth2.Token before use.
  29. type Token struct {
  30. // AccessToken is the token that authorizes and authenticates
  31. // the requests.
  32. AccessToken string
  33. // TokenType is the type of token.
  34. // The Type method returns either this or "Bearer", the default.
  35. TokenType string
  36. // RefreshToken is a token that's used by the application
  37. // (as opposed to the user) to refresh the access token
  38. // if it expires.
  39. RefreshToken string
  40. // Expiry is the optional expiration time of the access token.
  41. //
  42. // If zero, TokenSource implementations will reuse the same
  43. // token forever and RefreshToken or equivalent
  44. // mechanisms for that TokenSource will not be used.
  45. Expiry time.Time
  46. // Raw optionally contains extra metadata from the server
  47. // when updating a token.
  48. Raw interface{}
  49. }
  50. // tokenJSON is the struct representing the HTTP response from OAuth2
  51. // providers returning a token or error in JSON form.
  52. // https://datatracker.ietf.org/doc/html/rfc6749#section-5.1
  53. type tokenJSON struct {
  54. AccessToken string `json:"access_token"`
  55. TokenType string `json:"token_type"`
  56. RefreshToken string `json:"refresh_token"`
  57. ExpiresIn expirationTime `json:"expires_in"` // at least PayPal returns string, while most return number
  58. // error fields
  59. // https://datatracker.ietf.org/doc/html/rfc6749#section-5.2
  60. ErrorCode string `json:"error"`
  61. ErrorDescription string `json:"error_description"`
  62. ErrorURI string `json:"error_uri"`
  63. }
  64. func (e *tokenJSON) expiry() (t time.Time) {
  65. if v := e.ExpiresIn; v != 0 {
  66. return time.Now().Add(time.Duration(v) * time.Second)
  67. }
  68. return
  69. }
  70. type expirationTime int32
  71. func (e *expirationTime) UnmarshalJSON(b []byte) error {
  72. if len(b) == 0 || string(b) == "null" {
  73. return nil
  74. }
  75. var n json.Number
  76. err := json.Unmarshal(b, &n)
  77. if err != nil {
  78. return err
  79. }
  80. i, err := n.Int64()
  81. if err != nil {
  82. return err
  83. }
  84. if i > math.MaxInt32 {
  85. i = math.MaxInt32
  86. }
  87. *e = expirationTime(i)
  88. return nil
  89. }
  90. // RegisterBrokenAuthHeaderProvider previously did something. It is now a no-op.
  91. //
  92. // Deprecated: this function no longer does anything. Caller code that
  93. // wants to avoid potential extra HTTP requests made during
  94. // auto-probing of the provider's auth style should set
  95. // Endpoint.AuthStyle.
  96. func RegisterBrokenAuthHeaderProvider(tokenURL string) {}
  97. // AuthStyle is a copy of the golang.org/x/oauth2 package's AuthStyle type.
  98. type AuthStyle int
  99. const (
  100. AuthStyleUnknown AuthStyle = 0
  101. AuthStyleInParams AuthStyle = 1
  102. AuthStyleInHeader AuthStyle = 2
  103. )
  104. // LazyAuthStyleCache is a backwards compatibility compromise to let Configs
  105. // have a lazily-initialized AuthStyleCache.
  106. //
  107. // The two users of this, oauth2.Config and oauth2/clientcredentials.Config,
  108. // both would ideally just embed an unexported AuthStyleCache but because both
  109. // were historically allowed to be copied by value we can't retroactively add an
  110. // uncopyable Mutex to them.
  111. //
  112. // We could use an atomic.Pointer, but that was added recently enough (in Go
  113. // 1.18) that we'd break Go 1.17 users where the tests as of 2023-08-03
  114. // still pass. By using an atomic.Value, it supports both Go 1.17 and
  115. // copying by value, even if that's not ideal.
  116. type LazyAuthStyleCache struct {
  117. v atomic.Value // of *AuthStyleCache
  118. }
  119. func (lc *LazyAuthStyleCache) Get() *AuthStyleCache {
  120. if c, ok := lc.v.Load().(*AuthStyleCache); ok {
  121. return c
  122. }
  123. c := new(AuthStyleCache)
  124. if !lc.v.CompareAndSwap(nil, c) {
  125. c = lc.v.Load().(*AuthStyleCache)
  126. }
  127. return c
  128. }
  129. // AuthStyleCache is the set of tokenURLs we've successfully used via
  130. // RetrieveToken and which style auth we ended up using.
  131. // It's called a cache, but it doesn't (yet?) shrink. It's expected that
  132. // the set of OAuth2 servers a program contacts over time is fixed and
  133. // small.
  134. type AuthStyleCache struct {
  135. mu sync.Mutex
  136. m map[string]AuthStyle // keyed by tokenURL
  137. }
  138. // lookupAuthStyle reports which auth style we last used with tokenURL
  139. // when calling RetrieveToken and whether we have ever done so.
  140. func (c *AuthStyleCache) lookupAuthStyle(tokenURL string) (style AuthStyle, ok bool) {
  141. c.mu.Lock()
  142. defer c.mu.Unlock()
  143. style, ok = c.m[tokenURL]
  144. return
  145. }
  146. // setAuthStyle adds an entry to authStyleCache, documented above.
  147. func (c *AuthStyleCache) setAuthStyle(tokenURL string, v AuthStyle) {
  148. c.mu.Lock()
  149. defer c.mu.Unlock()
  150. if c.m == nil {
  151. c.m = make(map[string]AuthStyle)
  152. }
  153. c.m[tokenURL] = v
  154. }
  155. // newTokenRequest returns a new *http.Request to retrieve a new token
  156. // from tokenURL using the provided clientID, clientSecret, and POST
  157. // body parameters.
  158. //
  159. // inParams is whether the clientID & clientSecret should be encoded
  160. // as the POST body. An 'inParams' value of true means to send it in
  161. // the POST body (along with any values in v); false means to send it
  162. // in the Authorization header.
  163. func newTokenRequest(tokenURL, clientID, clientSecret string, v url.Values, authStyle AuthStyle) (*http.Request, error) {
  164. if authStyle == AuthStyleInParams {
  165. v = cloneURLValues(v)
  166. if clientID != "" {
  167. v.Set("client_id", clientID)
  168. }
  169. if clientSecret != "" {
  170. v.Set("client_secret", clientSecret)
  171. }
  172. }
  173. req, err := http.NewRequest("POST", tokenURL, strings.NewReader(v.Encode()))
  174. if err != nil {
  175. return nil, err
  176. }
  177. req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
  178. if authStyle == AuthStyleInHeader {
  179. req.SetBasicAuth(url.QueryEscape(clientID), url.QueryEscape(clientSecret))
  180. }
  181. return req, nil
  182. }
  183. func cloneURLValues(v url.Values) url.Values {
  184. v2 := make(url.Values, len(v))
  185. for k, vv := range v {
  186. v2[k] = append([]string(nil), vv...)
  187. }
  188. return v2
  189. }
  190. func RetrieveToken(ctx context.Context, clientID, clientSecret, tokenURL string, v url.Values, authStyle AuthStyle, styleCache *AuthStyleCache) (*Token, error) {
  191. needsAuthStyleProbe := authStyle == 0
  192. if needsAuthStyleProbe {
  193. if style, ok := styleCache.lookupAuthStyle(tokenURL); ok {
  194. authStyle = style
  195. needsAuthStyleProbe = false
  196. } else {
  197. authStyle = AuthStyleInHeader // the first way we'll try
  198. }
  199. }
  200. req, err := newTokenRequest(tokenURL, clientID, clientSecret, v, authStyle)
  201. if err != nil {
  202. return nil, err
  203. }
  204. token, err := doTokenRoundTrip(ctx, req)
  205. if err != nil && needsAuthStyleProbe {
  206. // If we get an error, assume the server wants the
  207. // clientID & clientSecret in a different form.
  208. // See https://code.google.com/p/goauth2/issues/detail?id=31 for background.
  209. // In summary:
  210. // - Reddit only accepts client secret in the Authorization header
  211. // - Dropbox accepts either it in URL param or Auth header, but not both.
  212. // - Google only accepts URL param (not spec compliant?), not Auth header
  213. // - Stripe only accepts client secret in Auth header with Bearer method, not Basic
  214. //
  215. // We used to maintain a big table in this code of all the sites and which way
  216. // they went, but maintaining it didn't scale & got annoying.
  217. // So just try both ways.
  218. authStyle = AuthStyleInParams // the second way we'll try
  219. req, _ = newTokenRequest(tokenURL, clientID, clientSecret, v, authStyle)
  220. token, err = doTokenRoundTrip(ctx, req)
  221. }
  222. if needsAuthStyleProbe && err == nil {
  223. styleCache.setAuthStyle(tokenURL, authStyle)
  224. }
  225. // Don't overwrite `RefreshToken` with an empty value
  226. // if this was a token refreshing request.
  227. if token != nil && token.RefreshToken == "" {
  228. token.RefreshToken = v.Get("refresh_token")
  229. }
  230. return token, err
  231. }
  232. func doTokenRoundTrip(ctx context.Context, req *http.Request) (*Token, error) {
  233. r, err := ContextClient(ctx).Do(req.WithContext(ctx))
  234. if err != nil {
  235. return nil, err
  236. }
  237. body, err := ioutil.ReadAll(io.LimitReader(r.Body, 1<<20))
  238. r.Body.Close()
  239. if err != nil {
  240. return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err)
  241. }
  242. failureStatus := r.StatusCode < 200 || r.StatusCode > 299
  243. retrieveError := &RetrieveError{
  244. Response: r,
  245. Body: body,
  246. // attempt to populate error detail below
  247. }
  248. var token *Token
  249. content, _, _ := mime.ParseMediaType(r.Header.Get("Content-Type"))
  250. switch content {
  251. case "application/x-www-form-urlencoded", "text/plain":
  252. // some endpoints return a query string
  253. vals, err := url.ParseQuery(string(body))
  254. if err != nil {
  255. if failureStatus {
  256. return nil, retrieveError
  257. }
  258. return nil, fmt.Errorf("oauth2: cannot parse response: %v", err)
  259. }
  260. retrieveError.ErrorCode = vals.Get("error")
  261. retrieveError.ErrorDescription = vals.Get("error_description")
  262. retrieveError.ErrorURI = vals.Get("error_uri")
  263. token = &Token{
  264. AccessToken: vals.Get("access_token"),
  265. TokenType: vals.Get("token_type"),
  266. RefreshToken: vals.Get("refresh_token"),
  267. Raw: vals,
  268. }
  269. e := vals.Get("expires_in")
  270. expires, _ := strconv.Atoi(e)
  271. if expires != 0 {
  272. token.Expiry = time.Now().Add(time.Duration(expires) * time.Second)
  273. }
  274. default:
  275. var tj tokenJSON
  276. if err = json.Unmarshal(body, &tj); err != nil {
  277. if failureStatus {
  278. return nil, retrieveError
  279. }
  280. return nil, fmt.Errorf("oauth2: cannot parse json: %v", err)
  281. }
  282. retrieveError.ErrorCode = tj.ErrorCode
  283. retrieveError.ErrorDescription = tj.ErrorDescription
  284. retrieveError.ErrorURI = tj.ErrorURI
  285. token = &Token{
  286. AccessToken: tj.AccessToken,
  287. TokenType: tj.TokenType,
  288. RefreshToken: tj.RefreshToken,
  289. Expiry: tj.expiry(),
  290. Raw: make(map[string]interface{}),
  291. }
  292. json.Unmarshal(body, &token.Raw) // no error checks for optional fields
  293. }
  294. // according to spec, servers should respond status 400 in error case
  295. // https://www.rfc-editor.org/rfc/rfc6749#section-5.2
  296. // but some unorthodox servers respond 200 in error case
  297. if failureStatus || retrieveError.ErrorCode != "" {
  298. return nil, retrieveError
  299. }
  300. if token.AccessToken == "" {
  301. return nil, errors.New("oauth2: server response missing access_token")
  302. }
  303. return token, nil
  304. }
  305. // mirrors oauth2.RetrieveError
  306. type RetrieveError struct {
  307. Response *http.Response
  308. Body []byte
  309. ErrorCode string
  310. ErrorDescription string
  311. ErrorURI string
  312. }
  313. func (r *RetrieveError) Error() string {
  314. if r.ErrorCode != "" {
  315. s := fmt.Sprintf("oauth2: %q", r.ErrorCode)
  316. if r.ErrorDescription != "" {
  317. s += fmt.Sprintf(" %q", r.ErrorDescription)
  318. }
  319. if r.ErrorURI != "" {
  320. s += fmt.Sprintf(" %q", r.ErrorURI)
  321. }
  322. return s
  323. }
  324. return fmt.Sprintf("oauth2: cannot fetch token: %v\nResponse: %s", r.Response.Status, r.Body)
  325. }