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