1// Copyright 2010 Gary Burd 2// 3// Licensed under the Apache License, Version 2.0 (the "License"): you may 4// not use this file except in compliance with the License. You may obtain 5// a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12// License for the specific language governing permissions and limitations 13// under the License. 14 15// Package oauth is consumer interface for OAuth 1.0, OAuth 1.0a and RFC 5849. 16// 17// Redirection-based Authorization 18// 19// This section outlines how to use the oauth package in redirection-based 20// authorization (http://tools.ietf.org/html/rfc5849#section-2). 21// 22// Step 1: Create a Client using credentials and URIs provided by the server. 23// The Client can be initialized once at application startup and stored in a 24// package-level variable. 25// 26// Step 2: Request temporary credentials using the Client 27// RequestTemporaryCredentials method. The callbackURL parameter is the URL of 28// the callback handler in step 4. Save the returned credential secret so that 29// it can be later found using credential token as a key. The secret can be 30// stored in a database keyed by the token. Another option is to store the 31// token and secret in session storage or a cookie. 32// 33// Step 3: Redirect the user to URL returned from AuthorizationURL method. The 34// AuthorizationURL method uses the temporary credentials from step 2 and other 35// parameters as specified by the server. 36// 37// Step 4: The server redirects back to the callback URL specified in step 2 38// with the temporary token and a verifier. Use the temporary token to find the 39// temporary secret saved in step 2. Using the temporary token, temporary 40// secret and verifier, request token credentials using the client RequestToken 41// method. Save the returned credentials for later use in the application. 42// 43// Signing Requests 44// 45// The Client type has two low-level methods for signing requests, SignForm and 46// SetAuthorizationHeader. 47// 48// The SignForm method adds an OAuth signature to a form. The application makes 49// an authenticated request by encoding the modified form to the query string 50// or request body. 51// 52// The SetAuthorizationHeader method adds an OAuth siganture to a request 53// header. The SetAuthorizationHeader method is the only way to correctly sign 54// a request if the application sets the URL Opaque field when making a 55// request. 56// 57// The Get, Put, Post and Delete methods sign and invoke a request using the 58// supplied net/http Client. These methods are easy to use, but not as flexible 59// as constructing a request using one of the low-level methods. 60// 61// Context With HTTP Client 62// 63// A context-enabled method can include a custom HTTP client in the 64// context and execute an HTTP request using the included HTTP client. 65// 66// hc := &http.Client{Timeout: 2 * time.Second} 67// ctx := context.WithValue(context.Background(), oauth.HTTPClient, hc) 68// c := oauth.Client{ /* Any settings */ } 69// resp, err := c.GetContext(ctx, &oauth.Credentials{}, rawurl, nil) 70package oauth // import "github.com/garyburd/go-oauth/oauth" 71 72import ( 73 "bytes" 74 "crypto" 75 "crypto/hmac" 76 "crypto/rand" 77 "crypto/rsa" 78 "crypto/sha1" 79 "encoding/base64" 80 "encoding/binary" 81 "errors" 82 "fmt" 83 "io" 84 "io/ioutil" 85 "net/http" 86 "net/url" 87 "sort" 88 "strconv" 89 "strings" 90 "sync/atomic" 91 "time" 92 93 "golang.org/x/net/context" 94) 95 96// noscape[b] is true if b should not be escaped per section 3.6 of the RFC. 97var noEscape = [256]bool{ 98 'A': true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, 99 'a': true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, 100 '0': true, true, true, true, true, true, true, true, true, true, 101 '-': true, 102 '.': true, 103 '_': true, 104 '~': true, 105} 106 107// encode encodes string per section 3.6 of the RFC. If double is true, then 108// the encoding is applied twice. 109func encode(s string, double bool) []byte { 110 // Compute size of result. 111 m := 3 112 if double { 113 m = 5 114 } 115 n := 0 116 for i := 0; i < len(s); i++ { 117 if noEscape[s[i]] { 118 n++ 119 } else { 120 n += m 121 } 122 } 123 124 p := make([]byte, n) 125 126 // Encode it. 127 j := 0 128 for i := 0; i < len(s); i++ { 129 b := s[i] 130 if noEscape[b] { 131 p[j] = b 132 j++ 133 } else if double { 134 p[j] = '%' 135 p[j+1] = '2' 136 p[j+2] = '5' 137 p[j+3] = "0123456789ABCDEF"[b>>4] 138 p[j+4] = "0123456789ABCDEF"[b&15] 139 j += 5 140 } else { 141 p[j] = '%' 142 p[j+1] = "0123456789ABCDEF"[b>>4] 143 p[j+2] = "0123456789ABCDEF"[b&15] 144 j += 3 145 } 146 } 147 return p 148} 149 150type keyValue struct{ key, value []byte } 151 152type byKeyValue []keyValue 153 154func (p byKeyValue) Len() int { return len(p) } 155func (p byKeyValue) Swap(i, j int) { p[i], p[j] = p[j], p[i] } 156func (p byKeyValue) Less(i, j int) bool { 157 sgn := bytes.Compare(p[i].key, p[j].key) 158 if sgn == 0 { 159 sgn = bytes.Compare(p[i].value, p[j].value) 160 } 161 return sgn < 0 162} 163 164func (p byKeyValue) appendValues(values url.Values) byKeyValue { 165 for k, vs := range values { 166 k := encode(k, true) 167 for _, v := range vs { 168 v := encode(v, true) 169 p = append(p, keyValue{k, v}) 170 } 171 } 172 return p 173} 174 175// writeBaseString writes method, url, and params to w using the OAuth signature 176// base string computation described in section 3.4.1 of the RFC. 177func writeBaseString(w io.Writer, method string, u *url.URL, form url.Values, oauthParams map[string]string) { 178 // Method 179 w.Write(encode(strings.ToUpper(method), false)) 180 w.Write([]byte{'&'}) 181 182 // URL 183 scheme := strings.ToLower(u.Scheme) 184 host := strings.ToLower(u.Host) 185 186 uNoQuery := *u 187 uNoQuery.RawQuery = "" 188 path := uNoQuery.RequestURI() 189 190 switch { 191 case scheme == "http" && strings.HasSuffix(host, ":80"): 192 host = host[:len(host)-len(":80")] 193 case scheme == "https" && strings.HasSuffix(host, ":443"): 194 host = host[:len(host)-len(":443")] 195 } 196 197 w.Write(encode(scheme, false)) 198 w.Write(encode("://", false)) 199 w.Write(encode(host, false)) 200 w.Write(encode(path, false)) 201 w.Write([]byte{'&'}) 202 203 // Create sorted slice of encoded parameters. Parameter keys and values are 204 // double encoded in a single step. This is safe because double encoding 205 // does not change the sort order. 206 queryParams := u.Query() 207 p := make(byKeyValue, 0, len(form)+len(queryParams)+len(oauthParams)) 208 p = p.appendValues(form) 209 p = p.appendValues(queryParams) 210 for k, v := range oauthParams { 211 p = append(p, keyValue{encode(k, true), encode(v, true)}) 212 } 213 sort.Sort(p) 214 215 // Write the parameters. 216 encodedAmp := encode("&", false) 217 encodedEqual := encode("=", false) 218 sep := false 219 for _, kv := range p { 220 if sep { 221 w.Write(encodedAmp) 222 } else { 223 sep = true 224 } 225 w.Write(kv.key) 226 w.Write(encodedEqual) 227 w.Write(kv.value) 228 } 229} 230 231var nonceCounter uint64 232 233func init() { 234 if err := binary.Read(rand.Reader, binary.BigEndian, &nonceCounter); err != nil { 235 // fallback to time if rand reader is broken 236 nonceCounter = uint64(time.Now().UnixNano()) 237 } 238} 239 240// nonce returns a unique string. 241func nonce() string { 242 return strconv.FormatUint(atomic.AddUint64(&nonceCounter, 1), 16) 243} 244 245// SignatureMethod identifies a signature method. 246type SignatureMethod int 247 248func (sm SignatureMethod) String() string { 249 switch sm { 250 case RSASHA1: 251 return "RSA-SHA1" 252 case HMACSHA1: 253 return "HMAC-SHA1" 254 case PLAINTEXT: 255 return "PLAINTEXT" 256 default: 257 return "unknown" 258 } 259} 260 261const ( 262 HMACSHA1 SignatureMethod = iota // HMAC-SHA1 263 RSASHA1 // RSA-SHA1 264 PLAINTEXT // Plain text 265) 266 267// Credentials represents client, temporary and token credentials. 268type Credentials struct { 269 Token string // Also known as consumer key or access token. 270 Secret string // Also known as consumer secret or access token secret. 271} 272 273// Client represents an OAuth client. 274type Client struct { 275 // Credentials specifies the client key and secret. 276 // Also known as the consumer key and secret 277 Credentials Credentials 278 279 // TemporaryCredentialRequestURI is the endpoint used by the client to 280 // obtain a set of temporary credentials. Also known as the request token 281 // URL. 282 TemporaryCredentialRequestURI string 283 284 // ResourceOwnerAuthorizationURI is the endpoint to which the resource 285 // owner is redirected to grant authorization. Also known as authorization 286 // URL. 287 ResourceOwnerAuthorizationURI string 288 289 // TokenRequestURI is the endpoint used by the client to request a set of 290 // token credentials using a set of temporary credentials. Also known as 291 // access token URL. 292 TokenRequestURI string 293 294 // RenewCredentialRequestURI is the endpoint the client uses to 295 // request a new set of token credentials using the old set of credentials. 296 RenewCredentialRequestURI string 297 298 // TemporaryCredentialsMethod is the HTTP method used by the client to 299 // obtain a set of temporary credentials. If this field is the empty 300 // string, then POST is used. 301 TemporaryCredentialsMethod string 302 303 // TokenCredentailsMethod is the HTTP method used by the client to request 304 // a set of token credentials. If this field is the empty string, then POST 305 // is used. 306 TokenCredentailsMethod string 307 308 // Header specifies optional extra headers for requests. 309 Header http.Header 310 311 // SignatureMethod specifies the method for signing a request. 312 SignatureMethod SignatureMethod 313 314 // PrivateKey is the private key to use for RSA-SHA1 signatures. This field 315 // must be set for RSA-SHA1 signatures and ignored for other signature 316 // methods. 317 PrivateKey *rsa.PrivateKey 318} 319 320type request struct { 321 credentials *Credentials 322 method string 323 u *url.URL 324 form url.Values 325 verifier string 326 sessionHandle string 327 callbackURL string 328} 329 330var testHook = func(map[string]string) {} 331 332// oauthParams returns the OAuth request parameters for the given credentials, 333// method, URL and application params. See 334// http://tools.ietf.org/html/rfc5849#section-3.4 for more information about 335// signatures. 336func (c *Client) oauthParams(r *request) (map[string]string, error) { 337 oauthParams := map[string]string{ 338 "oauth_consumer_key": c.Credentials.Token, 339 "oauth_signature_method": c.SignatureMethod.String(), 340 "oauth_version": "1.0", 341 } 342 343 if c.SignatureMethod != PLAINTEXT { 344 oauthParams["oauth_timestamp"] = strconv.FormatInt(time.Now().Unix(), 10) 345 oauthParams["oauth_nonce"] = nonce() 346 } 347 348 if r.credentials != nil { 349 oauthParams["oauth_token"] = r.credentials.Token 350 } 351 352 if r.verifier != "" { 353 oauthParams["oauth_verifier"] = r.verifier 354 } 355 356 if r.sessionHandle != "" { 357 oauthParams["oauth_session_handle"] = r.sessionHandle 358 } 359 360 if r.callbackURL != "" { 361 oauthParams["oauth_callback"] = r.callbackURL 362 } 363 364 testHook(oauthParams) 365 366 var signature string 367 368 switch c.SignatureMethod { 369 case HMACSHA1: 370 key := encode(c.Credentials.Secret, false) 371 key = append(key, '&') 372 if r.credentials != nil { 373 key = append(key, encode(r.credentials.Secret, false)...) 374 } 375 h := hmac.New(sha1.New, key) 376 writeBaseString(h, r.method, r.u, r.form, oauthParams) 377 signature = base64.StdEncoding.EncodeToString(h.Sum(key[:0])) 378 case RSASHA1: 379 if c.PrivateKey == nil { 380 return nil, errors.New("oauth: private key not set") 381 } 382 h := sha1.New() 383 writeBaseString(h, r.method, r.u, r.form, oauthParams) 384 rawSignature, err := rsa.SignPKCS1v15(rand.Reader, c.PrivateKey, crypto.SHA1, h.Sum(nil)) 385 if err != nil { 386 return nil, err 387 } 388 signature = base64.StdEncoding.EncodeToString(rawSignature) 389 case PLAINTEXT: 390 rawSignature := encode(c.Credentials.Secret, false) 391 rawSignature = append(rawSignature, '&') 392 if r.credentials != nil { 393 rawSignature = append(rawSignature, encode(r.credentials.Secret, false)...) 394 } 395 signature = string(rawSignature) 396 default: 397 return nil, errors.New("oauth: unknown signature method") 398 } 399 400 oauthParams["oauth_signature"] = signature 401 return oauthParams, nil 402} 403 404// SignForm adds an OAuth signature to form. The urlStr argument must not 405// include a query string. 406// 407// See http://tools.ietf.org/html/rfc5849#section-3.5.2 for 408// information about transmitting OAuth parameters in a request body and 409// http://tools.ietf.org/html/rfc5849#section-3.5.2 for information about 410// transmitting OAuth parameters in a query string. 411func (c *Client) SignForm(credentials *Credentials, method, urlStr string, form url.Values) error { 412 u, err := url.Parse(urlStr) 413 switch { 414 case err != nil: 415 return err 416 case u.RawQuery != "": 417 return errors.New("oauth: urlStr argument to SignForm must not include a query string") 418 } 419 p, err := c.oauthParams(&request{credentials: credentials, method: method, u: u, form: form}) 420 if err != nil { 421 return err 422 } 423 for k, v := range p { 424 form.Set(k, v) 425 } 426 return nil 427} 428 429// SignParam is deprecated. Use SignForm instead. 430func (c *Client) SignParam(credentials *Credentials, method, urlStr string, params url.Values) { 431 u, _ := url.Parse(urlStr) 432 u.RawQuery = "" 433 p, _ := c.oauthParams(&request{credentials: credentials, method: method, u: u, form: params}) 434 for k, v := range p { 435 params.Set(k, v) 436 } 437} 438 439var oauthKeys = []string{ 440 "oauth_consumer_key", 441 "oauth_nonce", 442 "oauth_signature", 443 "oauth_signature_method", 444 "oauth_timestamp", 445 "oauth_token", 446 "oauth_version", 447 "oauth_callback", 448 "oauth_verifier", 449 "oauth_session_handle", 450} 451 452func (c *Client) authorizationHeader(r *request) (string, error) { 453 p, err := c.oauthParams(r) 454 if err != nil { 455 return "", err 456 } 457 var h []byte 458 // Append parameters in a fixed order to support testing. 459 for _, k := range oauthKeys { 460 if v, ok := p[k]; ok { 461 if h == nil { 462 h = []byte(`OAuth `) 463 } else { 464 h = append(h, ", "...) 465 } 466 h = append(h, k...) 467 h = append(h, `="`...) 468 h = append(h, encode(v, false)...) 469 h = append(h, '"') 470 } 471 } 472 return string(h), nil 473} 474 475// AuthorizationHeader returns the HTTP authorization header value for given 476// method, URL and parameters. 477// 478// AuthorizationHeader is deprecated. Use SetAuthorizationHeader instead. 479func (c *Client) AuthorizationHeader(credentials *Credentials, method string, u *url.URL, params url.Values) string { 480 // Signing a request can return an error. This method is deprecated because 481 // this method does not return an error. 482 v, _ := c.authorizationHeader(&request{credentials: credentials, method: method, u: u, form: params}) 483 return v 484} 485 486// SetAuthorizationHeader adds an OAuth signature to a request header. 487// 488// See http://tools.ietf.org/html/rfc5849#section-3.5.1 for information about 489// transmitting OAuth parameters in an HTTP request header. 490func (c *Client) SetAuthorizationHeader(header http.Header, credentials *Credentials, method string, u *url.URL, form url.Values) error { 491 v, err := c.authorizationHeader(&request{credentials: credentials, method: method, u: u, form: form}) 492 if err != nil { 493 return err 494 } 495 header.Set("Authorization", v) 496 return nil 497} 498 499func (c *Client) do(ctx context.Context, urlStr string, r *request) (*http.Response, error) { 500 var body io.Reader 501 if r.method != http.MethodGet { 502 body = strings.NewReader(r.form.Encode()) 503 } 504 req, err := http.NewRequest(r.method, urlStr, body) 505 if err != nil { 506 return nil, err 507 } 508 if req.URL.RawQuery != "" { 509 return nil, errors.New("oauth: url must not contain a query string") 510 } 511 for k, v := range c.Header { 512 req.Header[k] = v 513 } 514 r.u = req.URL 515 auth, err := c.authorizationHeader(r) 516 if err != nil { 517 return nil, err 518 } 519 req.Header.Set("Authorization", auth) 520 if r.method == http.MethodGet { 521 req.URL.RawQuery = r.form.Encode() 522 } else { 523 req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 524 } 525 req = requestWithContext(ctx, req) 526 client := contextClient(ctx) 527 return client.Do(req) 528} 529 530// Get issues a GET to the specified URL with form added as a query string. 531func (c *Client) Get(client *http.Client, credentials *Credentials, urlStr string, form url.Values) (*http.Response, error) { 532 ctx := context.WithValue(context.Background(), HTTPClient, client) 533 return c.GetContext(ctx, credentials, urlStr, form) 534} 535 536// GetContext uses Context to perform Get. 537func (c *Client) GetContext(ctx context.Context, credentials *Credentials, urlStr string, form url.Values) (*http.Response, error) { 538 return c.do(ctx, urlStr, &request{method: http.MethodGet, credentials: credentials, form: form}) 539} 540 541// Post issues a POST with the specified form. 542func (c *Client) Post(client *http.Client, credentials *Credentials, urlStr string, form url.Values) (*http.Response, error) { 543 ctx := context.WithValue(context.Background(), HTTPClient, client) 544 return c.PostContext(ctx, credentials, urlStr, form) 545} 546 547// PostContext uses Context to perform Post. 548func (c *Client) PostContext(ctx context.Context, credentials *Credentials, urlStr string, form url.Values) (*http.Response, error) { 549 return c.do(ctx, urlStr, &request{method: http.MethodPost, credentials: credentials, form: form}) 550} 551 552// Delete issues a DELETE with the specified form. 553func (c *Client) Delete(client *http.Client, credentials *Credentials, urlStr string, form url.Values) (*http.Response, error) { 554 ctx := context.WithValue(context.Background(), HTTPClient, client) 555 return c.DeleteContext(ctx, credentials, urlStr, form) 556} 557 558// DeleteContext uses Context to perform Delete. 559func (c *Client) DeleteContext(ctx context.Context, credentials *Credentials, urlStr string, form url.Values) (*http.Response, error) { 560 return c.do(ctx, urlStr, &request{method: http.MethodDelete, credentials: credentials, form: form}) 561} 562 563// Put issues a PUT with the specified form. 564func (c *Client) Put(client *http.Client, credentials *Credentials, urlStr string, form url.Values) (*http.Response, error) { 565 ctx := context.WithValue(context.Background(), HTTPClient, client) 566 return c.PutContext(ctx, credentials, urlStr, form) 567} 568 569// PutContext uses Context to perform Put. 570func (c *Client) PutContext(ctx context.Context, credentials *Credentials, urlStr string, form url.Values) (*http.Response, error) { 571 return c.do(ctx, urlStr, &request{method: http.MethodPut, credentials: credentials, form: form}) 572} 573 574func (c *Client) requestCredentials(ctx context.Context, u string, r *request) (*Credentials, url.Values, error) { 575 if r.method == "" { 576 r.method = http.MethodPost 577 } 578 resp, err := c.do(ctx, u, r) 579 if err != nil { 580 return nil, nil, err 581 } 582 p, err := ioutil.ReadAll(resp.Body) 583 resp.Body.Close() 584 if err != nil { 585 return nil, nil, RequestCredentialsError{StatusCode: resp.StatusCode, Header: resp.Header, 586 Body: p, msg: err.Error()} 587 } 588 if resp.StatusCode != 200 && resp.StatusCode != 201 { 589 return nil, nil, RequestCredentialsError{StatusCode: resp.StatusCode, Header: resp.Header, 590 Body: p, msg: fmt.Sprintf("OAuth server status %d, %s", resp.StatusCode, string(p))} 591 } 592 m, err := url.ParseQuery(string(p)) 593 if err != nil { 594 return nil, nil, RequestCredentialsError{StatusCode: resp.StatusCode, Header: resp.Header, 595 Body: p, msg: err.Error()} 596 } 597 tokens := m["oauth_token"] 598 if len(tokens) == 0 || tokens[0] == "" { 599 return nil, nil, RequestCredentialsError{StatusCode: resp.StatusCode, Header: resp.Header, 600 Body: p, msg: "oauth: token missing from server result"} 601 } 602 secrets := m["oauth_token_secret"] 603 if len(secrets) == 0 { // allow "" as a valid secret. 604 return nil, nil, RequestCredentialsError{StatusCode: resp.StatusCode, Header: resp.Header, 605 Body: p, msg: "oauth: secret missing from server result"} 606 } 607 return &Credentials{Token: tokens[0], Secret: secrets[0]}, m, nil 608} 609 610// RequestTemporaryCredentials requests temporary credentials from the server. 611// See http://tools.ietf.org/html/rfc5849#section-2.1 for information about 612// temporary credentials. 613func (c *Client) RequestTemporaryCredentials(client *http.Client, callbackURL string, additionalParams url.Values) (*Credentials, error) { 614 ctx := context.WithValue(context.Background(), HTTPClient, client) 615 return c.RequestTemporaryCredentialsContext(ctx, callbackURL, additionalParams) 616} 617 618// RequestTemporaryCredentialsContext uses Context to perform RequestTemporaryCredentials. 619func (c *Client) RequestTemporaryCredentialsContext(ctx context.Context, callbackURL string, additionalParams url.Values) (*Credentials, error) { 620 credentials, _, err := c.requestCredentials(ctx, c.TemporaryCredentialRequestURI, 621 &request{method: c.TemporaryCredentialsMethod, form: additionalParams, callbackURL: callbackURL}) 622 return credentials, err 623} 624 625// RequestToken requests token credentials from the server. See 626// http://tools.ietf.org/html/rfc5849#section-2.3 for information about token 627// credentials. 628func (c *Client) RequestToken(client *http.Client, temporaryCredentials *Credentials, verifier string) (*Credentials, url.Values, error) { 629 ctx := context.WithValue(context.Background(), HTTPClient, client) 630 return c.RequestTokenContext(ctx, temporaryCredentials, verifier) 631} 632 633// RequestTokenContext uses Context to perform RequestToken. 634func (c *Client) RequestTokenContext(ctx context.Context, temporaryCredentials *Credentials, verifier string) (*Credentials, url.Values, error) { 635 return c.requestCredentials(ctx, c.TokenRequestURI, 636 &request{credentials: temporaryCredentials, method: c.TokenCredentailsMethod, verifier: verifier}) 637} 638 639// RenewRequestCredentials requests new token credentials from the server. 640// See http://wiki.oauth.net/w/page/12238549/ScalableOAuth#AccessTokenRenewal 641// for information about access token renewal. 642func (c *Client) RenewRequestCredentials(client *http.Client, credentials *Credentials, sessionHandle string) (*Credentials, url.Values, error) { 643 ctx := context.WithValue(context.Background(), HTTPClient, client) 644 return c.RenewRequestCredentialsContext(ctx, credentials, sessionHandle) 645} 646 647// RenewRequestCredentialsContext uses Context to perform RenewRequestCredentials. 648func (c *Client) RenewRequestCredentialsContext(ctx context.Context, credentials *Credentials, sessionHandle string) (*Credentials, url.Values, error) { 649 return c.requestCredentials(ctx, c.RenewCredentialRequestURI, &request{credentials: credentials, sessionHandle: sessionHandle}) 650} 651 652// RequestTokenXAuth requests token credentials from the server using the xAuth protocol. 653// See https://dev.twitter.com/oauth/xauth for information on xAuth. 654func (c *Client) RequestTokenXAuth(client *http.Client, temporaryCredentials *Credentials, user, password string) (*Credentials, url.Values, error) { 655 ctx := context.WithValue(context.Background(), HTTPClient, client) 656 return c.RequestTokenXAuthContext(ctx, temporaryCredentials, user, password) 657} 658 659// RequestTokenXAuthContext uses Context to perform RequestTokenXAuth. 660func (c *Client) RequestTokenXAuthContext(ctx context.Context, temporaryCredentials *Credentials, user, password string) (*Credentials, url.Values, error) { 661 form := make(url.Values) 662 form.Set("x_auth_mode", "client_auth") 663 form.Set("x_auth_username", user) 664 form.Set("x_auth_password", password) 665 return c.requestCredentials(ctx, c.TokenRequestURI, 666 &request{credentials: temporaryCredentials, method: c.TokenCredentailsMethod, form: form}) 667} 668 669// AuthorizationURL returns the URL for resource owner authorization. See 670// http://tools.ietf.org/html/rfc5849#section-2.2 for information about 671// resource owner authorization. 672func (c *Client) AuthorizationURL(temporaryCredentials *Credentials, additionalParams url.Values) string { 673 params := make(url.Values) 674 for k, vs := range additionalParams { 675 params[k] = vs 676 } 677 params.Set("oauth_token", temporaryCredentials.Token) 678 return c.ResourceOwnerAuthorizationURI + "?" + params.Encode() 679} 680 681// HTTPClient is the context key to use with context's 682// WithValue function to associate an *http.Client value with a context. 683var HTTPClient contextKey 684 685type contextKey struct{} 686 687func contextClient(ctx context.Context) *http.Client { 688 if ctx != nil { 689 if hc, ok := ctx.Value(HTTPClient).(*http.Client); ok && hc != nil { 690 return hc 691 } 692 } 693 return http.DefaultClient 694} 695 696// RequestCredentialsError is an error containing 697// response information when requesting credentials. 698type RequestCredentialsError struct { 699 StatusCode int 700 Header http.Header 701 Body []byte 702 msg string 703} 704 705func (e RequestCredentialsError) Error() string { 706 return e.msg 707} 708