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 5package oauth2 6 7import ( 8 "context" 9 "fmt" 10 "net/http" 11 "net/url" 12 "strconv" 13 "strings" 14 "time" 15 16 "golang.org/x/oauth2/internal" 17) 18 19// expiryDelta determines how earlier a token should be considered 20// expired than its actual expiration time. It is used to avoid late 21// expirations due to client-server time mismatches. 22const expiryDelta = 10 * time.Second 23 24// Token represents the credentials used to authorize 25// the requests to access protected resources on the OAuth 2.0 26// provider's backend. 27// 28// Most users of this package should not access fields of Token 29// directly. They're exported mostly for use by related packages 30// implementing derivative OAuth2 flows. 31type Token struct { 32 // AccessToken is the token that authorizes and authenticates 33 // the requests. 34 AccessToken string `json:"access_token"` 35 36 // TokenType is the type of token. 37 // The Type method returns either this or "Bearer", the default. 38 TokenType string `json:"token_type,omitempty"` 39 40 // RefreshToken is a token that's used by the application 41 // (as opposed to the user) to refresh the access token 42 // if it expires. 43 RefreshToken string `json:"refresh_token,omitempty"` 44 45 // Expiry is the optional expiration time of the access token. 46 // 47 // If zero, TokenSource implementations will reuse the same 48 // token forever and RefreshToken or equivalent 49 // mechanisms for that TokenSource will not be used. 50 Expiry time.Time `json:"expiry,omitempty"` 51 52 // raw optionally contains extra metadata from the server 53 // when updating a token. 54 raw interface{} 55} 56 57// Type returns t.TokenType if non-empty, else "Bearer". 58func (t *Token) Type() string { 59 if strings.EqualFold(t.TokenType, "bearer") { 60 return "Bearer" 61 } 62 if strings.EqualFold(t.TokenType, "mac") { 63 return "MAC" 64 } 65 if strings.EqualFold(t.TokenType, "basic") { 66 return "Basic" 67 } 68 if t.TokenType != "" { 69 return t.TokenType 70 } 71 return "Bearer" 72} 73 74// SetAuthHeader sets the Authorization header to r using the access 75// token in t. 76// 77// This method is unnecessary when using Transport or an HTTP Client 78// returned by this package. 79func (t *Token) SetAuthHeader(r *http.Request) { 80 r.Header.Set("Authorization", t.Type()+" "+t.AccessToken) 81} 82 83// WithExtra returns a new Token that's a clone of t, but using the 84// provided raw extra map. This is only intended for use by packages 85// implementing derivative OAuth2 flows. 86func (t *Token) WithExtra(extra interface{}) *Token { 87 t2 := new(Token) 88 *t2 = *t 89 t2.raw = extra 90 return t2 91} 92 93// Extra returns an extra field. 94// Extra fields are key-value pairs returned by the server as a 95// part of the token retrieval response. 96func (t *Token) Extra(key string) interface{} { 97 if raw, ok := t.raw.(map[string]interface{}); ok { 98 return raw[key] 99 } 100 101 vals, ok := t.raw.(url.Values) 102 if !ok { 103 return nil 104 } 105 106 v := vals.Get(key) 107 switch s := strings.TrimSpace(v); strings.Count(s, ".") { 108 case 0: // Contains no "."; try to parse as int 109 if i, err := strconv.ParseInt(s, 10, 64); err == nil { 110 return i 111 } 112 case 1: // Contains a single "."; try to parse as float 113 if f, err := strconv.ParseFloat(s, 64); err == nil { 114 return f 115 } 116 } 117 118 return v 119} 120 121// timeNow is time.Now but pulled out as a variable for tests. 122var timeNow = time.Now 123 124// expired reports whether the token is expired. 125// t must be non-nil. 126func (t *Token) expired() bool { 127 if t.Expiry.IsZero() { 128 return false 129 } 130 return t.Expiry.Round(0).Add(-expiryDelta).Before(timeNow()) 131} 132 133// Valid reports whether t is non-nil, has an AccessToken, and is not expired. 134func (t *Token) Valid() bool { 135 return t != nil && t.AccessToken != "" && !t.expired() 136} 137 138// tokenFromInternal maps an *internal.Token struct into 139// a *Token struct. 140func tokenFromInternal(t *internal.Token) *Token { 141 if t == nil { 142 return nil 143 } 144 return &Token{ 145 AccessToken: t.AccessToken, 146 TokenType: t.TokenType, 147 RefreshToken: t.RefreshToken, 148 Expiry: t.Expiry, 149 raw: t.Raw, 150 } 151} 152 153// retrieveToken takes a *Config and uses that to retrieve an *internal.Token. 154// This token is then mapped from *internal.Token into an *oauth2.Token which is returned along 155// with an error.. 156func retrieveToken(ctx context.Context, c *Config, v url.Values) (*Token, error) { 157 tk, err := internal.RetrieveToken(ctx, c.ClientID, c.ClientSecret, c.Endpoint.TokenURL, v, internal.AuthStyle(c.Endpoint.AuthStyle)) 158 if err != nil { 159 if rErr, ok := err.(*internal.RetrieveError); ok { 160 return nil, (*RetrieveError)(rErr) 161 } 162 return nil, err 163 } 164 return tokenFromInternal(tk), nil 165} 166 167// RetrieveError is the error returned when the token endpoint returns a 168// non-2XX HTTP status code. 169type RetrieveError struct { 170 Response *http.Response 171 // Body is the body that was consumed by reading Response.Body. 172 // It may be truncated. 173 Body []byte 174} 175 176func (r *RetrieveError) Error() string { 177 return fmt.Sprintf("oauth2: cannot fetch token: %v\nResponse: %s", r.Response.Status, r.Body) 178} 179