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 "fmt" 9 "net/http" 10 "net/url" 11 "strconv" 12 "strings" 13 "time" 14 15 "golang.org/x/net/context" 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// expired reports whether the token is expired. 122// t must be non-nil. 123func (t *Token) expired() bool { 124 if t.Expiry.IsZero() { 125 return false 126 } 127 return t.Expiry.Round(0).Add(-expiryDelta).Before(time.Now()) 128} 129 130// Valid reports whether t is non-nil, has an AccessToken, and is not expired. 131func (t *Token) Valid() bool { 132 return t != nil && t.AccessToken != "" && !t.expired() 133} 134 135// tokenFromInternal maps an *internal.Token struct into 136// a *Token struct. 137func tokenFromInternal(t *internal.Token) *Token { 138 if t == nil { 139 return nil 140 } 141 return &Token{ 142 AccessToken: t.AccessToken, 143 TokenType: t.TokenType, 144 RefreshToken: t.RefreshToken, 145 Expiry: t.Expiry, 146 raw: t.Raw, 147 } 148} 149 150// retrieveToken takes a *Config and uses that to retrieve an *internal.Token. 151// This token is then mapped from *internal.Token into an *oauth2.Token which is returned along 152// with an error.. 153func retrieveToken(ctx context.Context, c *Config, v url.Values) (*Token, error) { 154 tk, err := internal.RetrieveToken(ctx, c.ClientID, c.ClientSecret, c.Endpoint.TokenURL, v) 155 if err != nil { 156 if rErr, ok := err.(*internal.RetrieveError); ok { 157 return nil, (*RetrieveError)(rErr) 158 } 159 return nil, err 160 } 161 return tokenFromInternal(tk), nil 162} 163 164// RetrieveError is the error returned when the token endpoint returns a 165// non-2XX HTTP status code. 166type RetrieveError struct { 167 Response *http.Response 168 // Body is the body that was consumed by reading Response.Body. 169 // It may be truncated. 170 Body []byte 171} 172 173func (r *RetrieveError) Error() string { 174 return fmt.Sprintf("oauth2: cannot fetch token: %v\nResponse: %s", r.Response.Status, r.Body) 175} 176