123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294 |
- package internal
- import (
- "context"
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "io/ioutil"
- "math"
- "mime"
- "net/http"
- "net/url"
- "strconv"
- "strings"
- "sync"
- "time"
- "golang.org/x/net/context/ctxhttp"
- )
- type Token struct {
-
-
- AccessToken string
-
-
- TokenType string
-
-
-
- RefreshToken string
-
-
-
-
-
- Expiry time.Time
-
-
- Raw interface{}
- }
- type tokenJSON struct {
- AccessToken string `json:"access_token"`
- TokenType string `json:"token_type"`
- RefreshToken string `json:"refresh_token"`
- ExpiresIn expirationTime `json:"expires_in"`
- }
- func (e *tokenJSON) expiry() (t time.Time) {
- if v := e.ExpiresIn; v != 0 {
- return time.Now().Add(time.Duration(v) * time.Second)
- }
- return
- }
- type expirationTime int32
- func (e *expirationTime) UnmarshalJSON(b []byte) error {
- if len(b) == 0 || string(b) == "null" {
- return nil
- }
- var n json.Number
- err := json.Unmarshal(b, &n)
- if err != nil {
- return err
- }
- i, err := n.Int64()
- if err != nil {
- return err
- }
- if i > math.MaxInt32 {
- i = math.MaxInt32
- }
- *e = expirationTime(i)
- return nil
- }
- func RegisterBrokenAuthHeaderProvider(tokenURL string) {}
- type AuthStyle int
- const (
- AuthStyleUnknown AuthStyle = 0
- AuthStyleInParams AuthStyle = 1
- AuthStyleInHeader AuthStyle = 2
- )
- var authStyleCache struct {
- sync.Mutex
- m map[string]AuthStyle
- }
- func ResetAuthCache() {
- authStyleCache.Lock()
- defer authStyleCache.Unlock()
- authStyleCache.m = nil
- }
- func lookupAuthStyle(tokenURL string) (style AuthStyle, ok bool) {
- authStyleCache.Lock()
- defer authStyleCache.Unlock()
- style, ok = authStyleCache.m[tokenURL]
- return
- }
- func setAuthStyle(tokenURL string, v AuthStyle) {
- authStyleCache.Lock()
- defer authStyleCache.Unlock()
- if authStyleCache.m == nil {
- authStyleCache.m = make(map[string]AuthStyle)
- }
- authStyleCache.m[tokenURL] = v
- }
- func newTokenRequest(tokenURL, clientID, clientSecret string, v url.Values, authStyle AuthStyle) (*http.Request, error) {
- if authStyle == AuthStyleInParams {
- v = cloneURLValues(v)
- if clientID != "" {
- v.Set("client_id", clientID)
- }
- if clientSecret != "" {
- v.Set("client_secret", clientSecret)
- }
- }
- req, err := http.NewRequest("POST", tokenURL, strings.NewReader(v.Encode()))
- if err != nil {
- return nil, err
- }
- req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- if authStyle == AuthStyleInHeader {
- req.SetBasicAuth(url.QueryEscape(clientID), url.QueryEscape(clientSecret))
- }
- return req, nil
- }
- func cloneURLValues(v url.Values) url.Values {
- v2 := make(url.Values, len(v))
- for k, vv := range v {
- v2[k] = append([]string(nil), vv...)
- }
- return v2
- }
- func RetrieveToken(ctx context.Context, clientID, clientSecret, tokenURL string, v url.Values, authStyle AuthStyle) (*Token, error) {
- needsAuthStyleProbe := authStyle == 0
- if needsAuthStyleProbe {
- if style, ok := lookupAuthStyle(tokenURL); ok {
- authStyle = style
- needsAuthStyleProbe = false
- } else {
- authStyle = AuthStyleInHeader
- }
- }
- req, err := newTokenRequest(tokenURL, clientID, clientSecret, v, authStyle)
- if err != nil {
- return nil, err
- }
- token, err := doTokenRoundTrip(ctx, req)
- if err != nil && needsAuthStyleProbe {
-
-
-
-
-
-
-
-
-
-
-
-
- authStyle = AuthStyleInParams
- req, _ = newTokenRequest(tokenURL, clientID, clientSecret, v, authStyle)
- token, err = doTokenRoundTrip(ctx, req)
- }
- if needsAuthStyleProbe && err == nil {
- setAuthStyle(tokenURL, authStyle)
- }
-
-
- if token != nil && token.RefreshToken == "" {
- token.RefreshToken = v.Get("refresh_token")
- }
- return token, err
- }
- func doTokenRoundTrip(ctx context.Context, req *http.Request) (*Token, error) {
- r, err := ctxhttp.Do(ctx, ContextClient(ctx), req)
- if err != nil {
- return nil, err
- }
- body, err := ioutil.ReadAll(io.LimitReader(r.Body, 1<<20))
- r.Body.Close()
- if err != nil {
- return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err)
- }
- if code := r.StatusCode; code < 200 || code > 299 {
- return nil, &RetrieveError{
- Response: r,
- Body: body,
- }
- }
- var token *Token
- content, _, _ := mime.ParseMediaType(r.Header.Get("Content-Type"))
- switch content {
- case "application/x-www-form-urlencoded", "text/plain":
- vals, err := url.ParseQuery(string(body))
- if err != nil {
- return nil, err
- }
- token = &Token{
- AccessToken: vals.Get("access_token"),
- TokenType: vals.Get("token_type"),
- RefreshToken: vals.Get("refresh_token"),
- Raw: vals,
- }
- e := vals.Get("expires_in")
- expires, _ := strconv.Atoi(e)
- if expires != 0 {
- token.Expiry = time.Now().Add(time.Duration(expires) * time.Second)
- }
- default:
- var tj tokenJSON
- if err = json.Unmarshal(body, &tj); err != nil {
- return nil, err
- }
- token = &Token{
- AccessToken: tj.AccessToken,
- TokenType: tj.TokenType,
- RefreshToken: tj.RefreshToken,
- Expiry: tj.expiry(),
- Raw: make(map[string]interface{}),
- }
- json.Unmarshal(body, &token.Raw)
- }
- if token.AccessToken == "" {
- return nil, errors.New("oauth2: server response missing access_token")
- }
- return token, nil
- }
- type RetrieveError struct {
- Response *http.Response
- Body []byte
- }
- func (r *RetrieveError) Error() string {
- return fmt.Sprintf("oauth2: cannot fetch token: %v\nResponse: %s", r.Response.Status, r.Body)
- }
|