github.com/timstclair/heapster@v0.20.0-alpha1/Godeps/_workspace/src/golang.org/x/oauth2/internal/token.go (about) 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 internal contains support packages for oauth2 package. 6 package internal 7 8 import ( 9 "encoding/json" 10 "fmt" 11 "io" 12 "io/ioutil" 13 "mime" 14 "net/http" 15 "net/url" 16 "strconv" 17 "strings" 18 "time" 19 20 "golang.org/x/net/context" 21 ) 22 23 // Token represents the crendentials used to authorize 24 // the requests to access protected resources on the OAuth 2.0 25 // provider's backend. 26 // 27 // This type is a mirror of oauth2.Token and exists to break 28 // an otherwise-circular dependency. Other internal packages 29 // should convert this Token into an oauth2.Token before use. 30 type Token struct { 31 // AccessToken is the token that authorizes and authenticates 32 // the requests. 33 AccessToken string 34 35 // TokenType is the type of token. 36 // The Type method returns either this or "Bearer", the default. 37 TokenType string 38 39 // RefreshToken is a token that's used by the application 40 // (as opposed to the user) to refresh the access token 41 // if it expires. 42 RefreshToken string 43 44 // Expiry is the optional expiration time of the access token. 45 // 46 // If zero, TokenSource implementations will reuse the same 47 // token forever and RefreshToken or equivalent 48 // mechanisms for that TokenSource will not be used. 49 Expiry time.Time 50 51 // Raw optionally contains extra metadata from the server 52 // when updating a token. 53 Raw interface{} 54 } 55 56 // tokenJSON is the struct representing the HTTP response from OAuth2 57 // providers returning a token in JSON form. 58 type tokenJSON struct { 59 AccessToken string `json:"access_token"` 60 TokenType string `json:"token_type"` 61 RefreshToken string `json:"refresh_token"` 62 ExpiresIn expirationTime `json:"expires_in"` // at least PayPal returns string, while most return number 63 Expires expirationTime `json:"expires"` // broken Facebook spelling of expires_in 64 } 65 66 func (e *tokenJSON) expiry() (t time.Time) { 67 if v := e.ExpiresIn; v != 0 { 68 return time.Now().Add(time.Duration(v) * time.Second) 69 } 70 if v := e.Expires; v != 0 { 71 return time.Now().Add(time.Duration(v) * time.Second) 72 } 73 return 74 } 75 76 type expirationTime int32 77 78 func (e *expirationTime) UnmarshalJSON(b []byte) error { 79 var n json.Number 80 err := json.Unmarshal(b, &n) 81 if err != nil { 82 return err 83 } 84 i, err := n.Int64() 85 if err != nil { 86 return err 87 } 88 *e = expirationTime(i) 89 return nil 90 } 91 92 var brokenAuthHeaderProviders = []string{ 93 "https://accounts.google.com/", 94 "https://api.dropbox.com/", 95 "https://api.instagram.com/", 96 "https://api.netatmo.net/", 97 "https://api.odnoklassniki.ru/", 98 "https://api.pushbullet.com/", 99 "https://api.soundcloud.com/", 100 "https://api.twitch.tv/", 101 "https://app.box.com/", 102 "https://connect.stripe.com/", 103 "https://login.microsoftonline.com/", 104 "https://login.salesforce.com/", 105 "https://oauth.sandbox.trainingpeaks.com/", 106 "https://oauth.trainingpeaks.com/", 107 "https://oauth.vk.com/", 108 "https://slack.com/", 109 "https://test-sandbox.auth.corp.google.com", 110 "https://test.salesforce.com/", 111 "https://user.gini.net/", 112 "https://www.douban.com/", 113 "https://www.googleapis.com/", 114 "https://www.linkedin.com/", 115 "https://www.strava.com/oauth/", 116 } 117 118 func RegisterBrokenAuthHeaderProvider(tokenURL string) { 119 brokenAuthHeaderProviders = append(brokenAuthHeaderProviders, tokenURL) 120 } 121 122 // providerAuthHeaderWorks reports whether the OAuth2 server identified by the tokenURL 123 // implements the OAuth2 spec correctly 124 // See https://code.google.com/p/goauth2/issues/detail?id=31 for background. 125 // In summary: 126 // - Reddit only accepts client secret in the Authorization header 127 // - Dropbox accepts either it in URL param or Auth header, but not both. 128 // - Google only accepts URL param (not spec compliant?), not Auth header 129 // - Stripe only accepts client secret in Auth header with Bearer method, not Basic 130 func providerAuthHeaderWorks(tokenURL string) bool { 131 for _, s := range brokenAuthHeaderProviders { 132 if strings.HasPrefix(tokenURL, s) { 133 // Some sites fail to implement the OAuth2 spec fully. 134 return false 135 } 136 } 137 138 // Assume the provider implements the spec properly 139 // otherwise. We can add more exceptions as they're 140 // discovered. We will _not_ be adding configurable hooks 141 // to this package to let users select server bugs. 142 return true 143 } 144 145 func RetrieveToken(ctx context.Context, ClientID, ClientSecret, TokenURL string, v url.Values) (*Token, error) { 146 hc, err := ContextClient(ctx) 147 if err != nil { 148 return nil, err 149 } 150 v.Set("client_id", ClientID) 151 bustedAuth := !providerAuthHeaderWorks(TokenURL) 152 if bustedAuth && ClientSecret != "" { 153 v.Set("client_secret", ClientSecret) 154 } 155 req, err := http.NewRequest("POST", TokenURL, strings.NewReader(v.Encode())) 156 if err != nil { 157 return nil, err 158 } 159 req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 160 if !bustedAuth { 161 req.SetBasicAuth(ClientID, ClientSecret) 162 } 163 r, err := hc.Do(req) 164 if err != nil { 165 return nil, err 166 } 167 defer r.Body.Close() 168 body, err := ioutil.ReadAll(io.LimitReader(r.Body, 1<<20)) 169 if err != nil { 170 return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err) 171 } 172 if code := r.StatusCode; code < 200 || code > 299 { 173 return nil, fmt.Errorf("oauth2: cannot fetch token: %v\nResponse: %s", r.Status, body) 174 } 175 176 var token *Token 177 content, _, _ := mime.ParseMediaType(r.Header.Get("Content-Type")) 178 switch content { 179 case "application/x-www-form-urlencoded", "text/plain": 180 vals, err := url.ParseQuery(string(body)) 181 if err != nil { 182 return nil, err 183 } 184 token = &Token{ 185 AccessToken: vals.Get("access_token"), 186 TokenType: vals.Get("token_type"), 187 RefreshToken: vals.Get("refresh_token"), 188 Raw: vals, 189 } 190 e := vals.Get("expires_in") 191 if e == "" { 192 // TODO(jbd): Facebook's OAuth2 implementation is broken and 193 // returns expires_in field in expires. Remove the fallback to expires, 194 // when Facebook fixes their implementation. 195 e = vals.Get("expires") 196 } 197 expires, _ := strconv.Atoi(e) 198 if expires != 0 { 199 token.Expiry = time.Now().Add(time.Duration(expires) * time.Second) 200 } 201 default: 202 var tj tokenJSON 203 if err = json.Unmarshal(body, &tj); err != nil { 204 return nil, err 205 } 206 token = &Token{ 207 AccessToken: tj.AccessToken, 208 TokenType: tj.TokenType, 209 RefreshToken: tj.RefreshToken, 210 Expiry: tj.expiry(), 211 Raw: make(map[string]interface{}), 212 } 213 json.Unmarshal(body, &token.Raw) // no error checks for optional fields 214 } 215 // Don't overwrite `RefreshToken` with an empty value 216 // if this was a token refreshing request. 217 if token.RefreshToken == "" { 218 token.RefreshToken = v.Get("refresh_token") 219 } 220 return token, nil 221 }