token.go (5916B)
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 5 package oauth2 6 7 import ( 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 // defaultExpiryDelta 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. 22 const defaultExpiryDelta = 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. 31 type 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 // expiryDelta is used to calculate when a token is considered 57 // expired, by subtracting from Expiry. If zero, defaultExpiryDelta 58 // is used. 59 expiryDelta time.Duration 60 } 61 62 // Type returns t.TokenType if non-empty, else "Bearer". 63 func (t *Token) Type() string { 64 if strings.EqualFold(t.TokenType, "bearer") { 65 return "Bearer" 66 } 67 if strings.EqualFold(t.TokenType, "mac") { 68 return "MAC" 69 } 70 if strings.EqualFold(t.TokenType, "basic") { 71 return "Basic" 72 } 73 if t.TokenType != "" { 74 return t.TokenType 75 } 76 return "Bearer" 77 } 78 79 // SetAuthHeader sets the Authorization header to r using the access 80 // token in t. 81 // 82 // This method is unnecessary when using Transport or an HTTP Client 83 // returned by this package. 84 func (t *Token) SetAuthHeader(r *http.Request) { 85 r.Header.Set("Authorization", t.Type()+" "+t.AccessToken) 86 } 87 88 // WithExtra returns a new Token that's a clone of t, but using the 89 // provided raw extra map. This is only intended for use by packages 90 // implementing derivative OAuth2 flows. 91 func (t *Token) WithExtra(extra interface{}) *Token { 92 t2 := new(Token) 93 *t2 = *t 94 t2.raw = extra 95 return t2 96 } 97 98 // Extra returns an extra field. 99 // Extra fields are key-value pairs returned by the server as a 100 // part of the token retrieval response. 101 func (t *Token) Extra(key string) interface{} { 102 if raw, ok := t.raw.(map[string]interface{}); ok { 103 return raw[key] 104 } 105 106 vals, ok := t.raw.(url.Values) 107 if !ok { 108 return nil 109 } 110 111 v := vals.Get(key) 112 switch s := strings.TrimSpace(v); strings.Count(s, ".") { 113 case 0: // Contains no "."; try to parse as int 114 if i, err := strconv.ParseInt(s, 10, 64); err == nil { 115 return i 116 } 117 case 1: // Contains a single "."; try to parse as float 118 if f, err := strconv.ParseFloat(s, 64); err == nil { 119 return f 120 } 121 } 122 123 return v 124 } 125 126 // timeNow is time.Now but pulled out as a variable for tests. 127 var timeNow = time.Now 128 129 // expired reports whether the token is expired. 130 // t must be non-nil. 131 func (t *Token) expired() bool { 132 if t.Expiry.IsZero() { 133 return false 134 } 135 136 expiryDelta := defaultExpiryDelta 137 if t.expiryDelta != 0 { 138 expiryDelta = t.expiryDelta 139 } 140 return t.Expiry.Round(0).Add(-expiryDelta).Before(timeNow()) 141 } 142 143 // Valid reports whether t is non-nil, has an AccessToken, and is not expired. 144 func (t *Token) Valid() bool { 145 return t != nil && t.AccessToken != "" && !t.expired() 146 } 147 148 // tokenFromInternal maps an *internal.Token struct into 149 // a *Token struct. 150 func tokenFromInternal(t *internal.Token) *Token { 151 if t == nil { 152 return nil 153 } 154 return &Token{ 155 AccessToken: t.AccessToken, 156 TokenType: t.TokenType, 157 RefreshToken: t.RefreshToken, 158 Expiry: t.Expiry, 159 raw: t.Raw, 160 } 161 } 162 163 // retrieveToken takes a *Config and uses that to retrieve an *internal.Token. 164 // This token is then mapped from *internal.Token into an *oauth2.Token which is returned along 165 // with an error.. 166 func retrieveToken(ctx context.Context, c *Config, v url.Values) (*Token, error) { 167 tk, err := internal.RetrieveToken(ctx, c.ClientID, c.ClientSecret, c.Endpoint.TokenURL, v, internal.AuthStyle(c.Endpoint.AuthStyle)) 168 if err != nil { 169 if rErr, ok := err.(*internal.RetrieveError); ok { 170 return nil, (*RetrieveError)(rErr) 171 } 172 return nil, err 173 } 174 return tokenFromInternal(tk), nil 175 } 176 177 // RetrieveError is the error returned when the token endpoint returns a 178 // non-2XX HTTP status code or populates RFC 6749's 'error' parameter. 179 // https://datatracker.ietf.org/doc/html/rfc6749#section-5.2 180 type RetrieveError struct { 181 Response *http.Response 182 // Body is the body that was consumed by reading Response.Body. 183 // It may be truncated. 184 Body []byte 185 // ErrorCode is RFC 6749's 'error' parameter. 186 ErrorCode string 187 // ErrorDescription is RFC 6749's 'error_description' parameter. 188 ErrorDescription string 189 // ErrorURI is RFC 6749's 'error_uri' parameter. 190 ErrorURI string 191 } 192 193 func (r *RetrieveError) Error() string { 194 if r.ErrorCode != "" { 195 s := fmt.Sprintf("oauth2: %q", r.ErrorCode) 196 if r.ErrorDescription != "" { 197 s += fmt.Sprintf(" %q", r.ErrorDescription) 198 } 199 if r.ErrorURI != "" { 200 s += fmt.Sprintf(" %q", r.ErrorURI) 201 } 202 return s 203 } 204 return fmt.Sprintf("oauth2: cannot fetch token: %v\nResponse: %s", r.Response.Status, r.Body) 205 }