|
|
- // OAuth 1.0 consumer implementation.
- // See http://www.oauth.net and RFC 5849
- //
- // There are typically three parties involved in an OAuth exchange:
- // (1) The "Service Provider" (e.g. Google, Twitter, NetFlix) who operates the
- // service where the data resides.
- // (2) The "End User" who owns that data, and wants to grant access to a third-party.
- // (3) That third-party who wants access to the data (after first being authorized by
- // the user). This third-party is referred to as the "Consumer" in OAuth
- // terminology.
- //
- // This library is designed to help implement the third-party consumer by handling the
- // low-level authentication tasks, and allowing for authenticated requests to the
- // service provider on behalf of the user.
- //
- // Caveats:
- // - Currently only supports HMAC and RSA signatures.
- // - Currently only supports SHA1 and SHA256 hashes.
- // - Currently only supports OAuth 1.0
- //
- // Overview of how to use this library:
- // (1) First create a new Consumer instance with the NewConsumer function
- // (2) Get a RequestToken, and "authorization url" from GetRequestTokenAndUrl()
- // (3) Save the RequestToken, you will need it again in step 6.
- // (4) Redirect the user to the "authorization url" from step 2, where they will
- // authorize your access to the service provider.
- // (5) Wait. You will be called back on the CallbackUrl that you provide, and you
- // will recieve a "verification code".
- // (6) Call AuthorizeToken() with the RequestToken from step 2 and the
- // "verification code" from step 5.
- // (7) You will get back an AccessToken. Save this for as long as you need access
- // to the user's data, and treat it like a password; it is a secret.
- // (8) You can now throw away the RequestToken from step 2, it is no longer
- // necessary.
- // (9) Call "MakeHttpClient" using the AccessToken from step 7 to get an
- // HTTP client which can access protected resources.
- package oauth
-
- import (
- "bytes"
- "crypto"
- "crypto/hmac"
- cryptoRand "crypto/rand"
- "crypto/rsa"
- "encoding/base64"
- "errors"
- "fmt"
- "io"
- "io/ioutil"
- "math/rand"
- "mime/multipart"
- "net/http"
- "net/url"
- "sort"
- "strconv"
- "strings"
- "sync"
- "time"
- )
-
- const (
- OAUTH_VERSION = "1.0"
- SIGNATURE_METHOD_HMAC = "HMAC-"
- SIGNATURE_METHOD_RSA = "RSA-"
-
- HTTP_AUTH_HEADER = "Authorization"
- OAUTH_HEADER = "OAuth "
- BODY_HASH_PARAM = "oauth_body_hash"
- CALLBACK_PARAM = "oauth_callback"
- CONSUMER_KEY_PARAM = "oauth_consumer_key"
- NONCE_PARAM = "oauth_nonce"
- SESSION_HANDLE_PARAM = "oauth_session_handle"
- SIGNATURE_METHOD_PARAM = "oauth_signature_method"
- SIGNATURE_PARAM = "oauth_signature"
- TIMESTAMP_PARAM = "oauth_timestamp"
- TOKEN_PARAM = "oauth_token"
- TOKEN_SECRET_PARAM = "oauth_token_secret"
- VERIFIER_PARAM = "oauth_verifier"
- VERSION_PARAM = "oauth_version"
- )
-
- var HASH_METHOD_MAP = map[crypto.Hash]string{
- crypto.SHA1: "SHA1",
- crypto.SHA256: "SHA256",
- }
-
- // TODO(mrjones) Do we definitely want separate "Request" and "Access" token classes?
- // They're identical structurally, but used for different purposes.
- type RequestToken struct {
- Token string
- Secret string
- }
-
- type AccessToken struct {
- Token string
- Secret string
- AdditionalData map[string]string
- }
-
- type DataLocation int
-
- const (
- LOC_BODY DataLocation = iota + 1
- LOC_URL
- LOC_MULTIPART
- LOC_JSON
- LOC_XML
- )
-
- // Information about how to contact the service provider (see #1 above).
- // You usually find all of these URLs by reading the documentation for the service
- // that you're trying to connect to.
- // Some common examples are:
- // (1) Google, standard APIs:
- // http://code.google.com/apis/accounts/docs/OAuth_ref.html
- // - RequestTokenUrl: https://www.google.com/accounts/OAuthGetRequestToken
- // - AuthorizeTokenUrl: https://www.google.com/accounts/OAuthAuthorizeToken
- // - AccessTokenUrl: https://www.google.com/accounts/OAuthGetAccessToken
- // Note: Some Google APIs (for example, Google Latitude) use different values for
- // one or more of those URLs.
- // (2) Twitter API:
- // http://dev.twitter.com/pages/auth
- // - RequestTokenUrl: http://api.twitter.com/oauth/request_token
- // - AuthorizeTokenUrl: https://api.twitter.com/oauth/authorize
- // - AccessTokenUrl: https://api.twitter.com/oauth/access_token
- // (3) NetFlix API:
- // http://developer.netflix.com/docs/Security
- // - RequestTokenUrl: http://api.netflix.com/oauth/request_token
- // - AuthroizeTokenUrl: https://api-user.netflix.com/oauth/login
- // - AccessTokenUrl: http://api.netflix.com/oauth/access_token
- // Set HttpMethod if the service provider requires a different HTTP method
- // to be used for OAuth token requests
- type ServiceProvider struct {
- RequestTokenUrl string
- AuthorizeTokenUrl string
- AccessTokenUrl string
- HttpMethod string
- BodyHash bool
- IgnoreTimestamp bool
-
- // Enables non spec-compliant behavior:
- // Allow parameters to be passed in the query string rather
- // than the body.
- // See https://github.com/mrjones/oauth/pull/63
- SignQueryParams bool
- }
-
- func (sp *ServiceProvider) httpMethod() string {
- if sp.HttpMethod != "" {
- return sp.HttpMethod
- }
-
- return "GET"
- }
-
- // lockedNonceGenerator wraps a non-reentrant random number generator with a
- // lock
- type lockedNonceGenerator struct {
- nonceGenerator nonceGenerator
- lock sync.Mutex
- }
-
- func newLockedNonceGenerator(c clock) *lockedNonceGenerator {
- return &lockedNonceGenerator{
- nonceGenerator: rand.New(rand.NewSource(c.Nanos())),
- }
- }
-
- func (n *lockedNonceGenerator) Int63() int64 {
- n.lock.Lock()
- r := n.nonceGenerator.Int63()
- n.lock.Unlock()
- return r
- }
-
- // Consumers are stateless, you can call the various methods (GetRequestTokenAndUrl,
- // AuthorizeToken, and Get) on various different instances of Consumers *as long as
- // they were set up in the same way.* It is up to you, as the caller to persist the
- // necessary state (RequestTokens and AccessTokens).
- type Consumer struct {
- // Some ServiceProviders require extra parameters to be passed for various reasons.
- // For example Google APIs require you to set a scope= parameter to specify how much
- // access is being granted. The proper values for scope= depend on the service:
- // For more, see: http://code.google.com/apis/accounts/docs/OAuth.html#prepScope
- AdditionalParams map[string]string
-
- // The rest of this class is configured via the NewConsumer function.
- consumerKey string
- serviceProvider ServiceProvider
-
- // Some APIs (e.g. Netflix) aren't quite standard OAuth, and require passing
- // additional parameters when authorizing the request token. For most APIs
- // this field can be ignored. For Netflix, do something like:
- // consumer.AdditionalAuthorizationUrlParams = map[string]string{
- // "application_name": "YourAppName",
- // "oauth_consumer_key": "YourConsumerKey",
- // }
- AdditionalAuthorizationUrlParams map[string]string
-
- debug bool
-
- // Defaults to http.Client{}, can be overridden (e.g. for testing) as necessary
- HttpClient HttpClient
-
- // Some APIs (e.g. Intuit/Quickbooks) require sending additional headers along with
- // requests. (like "Accept" to specify the response type as XML or JSON) Note that this
- // will only *add* headers, not set existing ones.
- AdditionalHeaders map[string][]string
-
- // Private seams for mocking dependencies when testing
- clock clock
- // Seeded generators are not reentrant
- nonceGenerator nonceGenerator
- signer signer
- }
-
- func newConsumer(consumerKey string, serviceProvider ServiceProvider, httpClient *http.Client) *Consumer {
- clock := &defaultClock{}
- if httpClient == nil {
- httpClient = &http.Client{}
- }
- return &Consumer{
- consumerKey: consumerKey,
- serviceProvider: serviceProvider,
- clock: clock,
- HttpClient: httpClient,
- nonceGenerator: newLockedNonceGenerator(clock),
-
- AdditionalParams: make(map[string]string),
- AdditionalAuthorizationUrlParams: make(map[string]string),
- }
- }
-
- // Creates a new Consumer instance, with a HMAC-SHA1 signer
- // - consumerKey and consumerSecret:
- // values you should obtain from the ServiceProvider when you register your
- // application.
- //
- // - serviceProvider:
- // see the documentation for ServiceProvider for how to create this.
- //
- func NewConsumer(consumerKey string, consumerSecret string,
- serviceProvider ServiceProvider) *Consumer {
- consumer := newConsumer(consumerKey, serviceProvider, nil)
-
- consumer.signer = &HMACSigner{
- consumerSecret: consumerSecret,
- hashFunc: crypto.SHA1,
- }
-
- return consumer
- }
-
- // Creates a new Consumer instance, with a HMAC-SHA1 signer
- // - consumerKey and consumerSecret:
- // values you should obtain from the ServiceProvider when you register your
- // application.
- //
- // - serviceProvider:
- // see the documentation for ServiceProvider for how to create this.
- //
- // - httpClient:
- // Provides a custom implementation of the httpClient used under the hood
- // to make the request. This is especially useful if you want to use
- // Google App Engine.
- //
- func NewCustomHttpClientConsumer(consumerKey string, consumerSecret string,
- serviceProvider ServiceProvider, httpClient *http.Client) *Consumer {
- consumer := newConsumer(consumerKey, serviceProvider, httpClient)
-
- consumer.signer = &HMACSigner{
- consumerSecret: consumerSecret,
- hashFunc: crypto.SHA1,
- }
-
- return consumer
- }
-
- // Creates a new Consumer instance, with a HMAC signer
- // - consumerKey and consumerSecret:
- // values you should obtain from the ServiceProvider when you register your
- // application.
- //
- // - hashFunc:
- // the crypto.Hash to use for signatures
- //
- // - serviceProvider:
- // see the documentation for ServiceProvider for how to create this.
- //
- // - httpClient:
- // Provides a custom implementation of the httpClient used under the hood
- // to make the request. This is especially useful if you want to use
- // Google App Engine. Can be nil for default.
- //
- func NewCustomConsumer(consumerKey string, consumerSecret string,
- hashFunc crypto.Hash, serviceProvider ServiceProvider,
- httpClient *http.Client) *Consumer {
- consumer := newConsumer(consumerKey, serviceProvider, httpClient)
-
- consumer.signer = &HMACSigner{
- consumerSecret: consumerSecret,
- hashFunc: hashFunc,
- }
-
- return consumer
- }
-
- // Creates a new Consumer instance, with a RSA-SHA1 signer
- // - consumerKey:
- // value you should obtain from the ServiceProvider when you register your
- // application.
- //
- // - privateKey:
- // the private key to use for signatures
- //
- // - serviceProvider:
- // see the documentation for ServiceProvider for how to create this.
- //
- func NewRSAConsumer(consumerKey string, privateKey *rsa.PrivateKey,
- serviceProvider ServiceProvider) *Consumer {
- consumer := newConsumer(consumerKey, serviceProvider, nil)
-
- consumer.signer = &RSASigner{
- privateKey: privateKey,
- hashFunc: crypto.SHA1,
- rand: cryptoRand.Reader,
- }
-
- return consumer
- }
-
- // Creates a new Consumer instance, with a RSA signer
- // - consumerKey:
- // value you should obtain from the ServiceProvider when you register your
- // application.
- //
- // - privateKey:
- // the private key to use for signatures
- //
- // - hashFunc:
- // the crypto.Hash to use for signatures
- //
- // - serviceProvider:
- // see the documentation for ServiceProvider for how to create this.
- //
- // - httpClient:
- // Provides a custom implementation of the httpClient used under the hood
- // to make the request. This is especially useful if you want to use
- // Google App Engine. Can be nil for default.
- //
- func NewCustomRSAConsumer(consumerKey string, privateKey *rsa.PrivateKey,
- hashFunc crypto.Hash, serviceProvider ServiceProvider,
- httpClient *http.Client) *Consumer {
- consumer := newConsumer(consumerKey, serviceProvider, httpClient)
-
- consumer.signer = &RSASigner{
- privateKey: privateKey,
- hashFunc: hashFunc,
- rand: cryptoRand.Reader,
- }
-
- return consumer
- }
-
- // Kicks off the OAuth authorization process.
- // - callbackUrl:
- // Authorizing a token *requires* redirecting to the service provider. This is the
- // URL which the service provider will redirect the user back to after that
- // authorization is completed. The service provider will pass back a verification
- // code which is necessary to complete the rest of the process (in AuthorizeToken).
- // Notes on callbackUrl:
- // - Some (all?) service providers allow for setting "oob" (for out-of-band) as a
- // callback url. If this is set the service provider will present the
- // verification code directly to the user, and you must provide a place for
- // them to copy-and-paste it into.
- // - Otherwise, the user will be redirected to callbackUrl in the browser, and
- // will append a "oauth_verifier=<verifier>" parameter.
- //
- // This function returns:
- // - rtoken:
- // A temporary RequestToken, used during the authorization process. You must save
- // this since it will be necessary later in the process when calling
- // AuthorizeToken().
- //
- // - url:
- // A URL that you should redirect the user to in order that they may authorize you
- // to the service provider.
- //
- // - err:
- // Set only if there was an error, nil otherwise.
- func (c *Consumer) GetRequestTokenAndUrl(callbackUrl string) (rtoken *RequestToken, loginUrl string, err error) {
- return c.GetRequestTokenAndUrlWithParams(callbackUrl, c.AdditionalParams)
- }
-
- func (c *Consumer) GetRequestTokenAndUrlWithParams(callbackUrl string, additionalParams map[string]string) (rtoken *RequestToken, loginUrl string, err error) {
- params := c.baseParams(c.consumerKey, additionalParams)
- if callbackUrl != "" {
- params.Add(CALLBACK_PARAM, callbackUrl)
- }
-
- req := &request{
- method: c.serviceProvider.httpMethod(),
- url: c.serviceProvider.RequestTokenUrl,
- oauthParams: params,
- }
- if _, err := c.signRequest(req, ""); err != nil { // We don't have a token secret for the key yet
- return nil, "", err
- }
-
- resp, err := c.getBody(c.serviceProvider.httpMethod(), c.serviceProvider.RequestTokenUrl, params)
- if err != nil {
- return nil, "", errors.New("getBody: " + err.Error())
- }
-
- requestToken, err := parseRequestToken(*resp)
- if err != nil {
- return nil, "", errors.New("parseRequestToken: " + err.Error())
- }
-
- loginParams := make(url.Values)
- for k, v := range c.AdditionalAuthorizationUrlParams {
- loginParams.Set(k, v)
- }
- loginParams.Set(TOKEN_PARAM, requestToken.Token)
-
- loginUrl = c.serviceProvider.AuthorizeTokenUrl + "?" + loginParams.Encode()
-
- return requestToken, loginUrl, nil
- }
-
- // After the user has authorized you to the service provider, use this method to turn
- // your temporary RequestToken into a permanent AccessToken. You must pass in two values:
- // - rtoken:
- // The RequestToken returned from GetRequestTokenAndUrl()
- //
- // - verificationCode:
- // The string which passed back from the server, either as the oauth_verifier
- // query param appended to callbackUrl *OR* a string manually entered by the user
- // if callbackUrl is "oob"
- //
- // It will return:
- // - atoken:
- // A permanent AccessToken which can be used to access the user's data (until it is
- // revoked by the user or the service provider).
- //
- // - err:
- // Set only if there was an error, nil otherwise.
- func (c *Consumer) AuthorizeToken(rtoken *RequestToken, verificationCode string) (atoken *AccessToken, err error) {
- return c.AuthorizeTokenWithParams(rtoken, verificationCode, c.AdditionalParams)
- }
-
- func (c *Consumer) AuthorizeTokenWithParams(rtoken *RequestToken, verificationCode string, additionalParams map[string]string) (atoken *AccessToken, err error) {
- params := map[string]string{
- VERIFIER_PARAM: verificationCode,
- TOKEN_PARAM: rtoken.Token,
- }
- return c.makeAccessTokenRequestWithParams(params, rtoken.Secret, additionalParams)
- }
-
- // Use the service provider to refresh the AccessToken for a given session.
- // Note that this is only supported for service providers that manage an
- // authorization session (e.g. Yahoo).
- //
- // Most providers do not return the SESSION_HANDLE_PARAM needed to refresh
- // the token.
- //
- // See http://oauth.googlecode.com/svn/spec/ext/session/1.0/drafts/1/spec.html
- // for more information.
- // - accessToken:
- // The AccessToken returned from AuthorizeToken()
- //
- // It will return:
- // - atoken:
- // An AccessToken which can be used to access the user's data (until it is
- // revoked by the user or the service provider).
- //
- // - err:
- // Set if accessToken does not contain the SESSION_HANDLE_PARAM needed to
- // refresh the token, or if an error occurred when making the request.
- func (c *Consumer) RefreshToken(accessToken *AccessToken) (atoken *AccessToken, err error) {
- params := make(map[string]string)
- sessionHandle, ok := accessToken.AdditionalData[SESSION_HANDLE_PARAM]
- if !ok {
- return nil, errors.New("Missing " + SESSION_HANDLE_PARAM + " in access token.")
- }
- params[SESSION_HANDLE_PARAM] = sessionHandle
- params[TOKEN_PARAM] = accessToken.Token
-
- return c.makeAccessTokenRequest(params, accessToken.Secret)
- }
-
- // Use the service provider to obtain an AccessToken for a given session
- // - params:
- // The access token request paramters.
- //
- // - secret:
- // Secret key to use when signing the access token request.
- //
- // It will return:
- // - atoken
- // An AccessToken which can be used to access the user's data (until it is
- // revoked by the user or the service provider).
- //
- // - err:
- // Set only if there was an error, nil otherwise.
- func (c *Consumer) makeAccessTokenRequest(params map[string]string, secret string) (atoken *AccessToken, err error) {
- return c.makeAccessTokenRequestWithParams(params, secret, c.AdditionalParams)
- }
-
- func (c *Consumer) makeAccessTokenRequestWithParams(params map[string]string, secret string, additionalParams map[string]string) (atoken *AccessToken, err error) {
- orderedParams := c.baseParams(c.consumerKey, additionalParams)
- for key, value := range params {
- orderedParams.Add(key, value)
- }
-
- req := &request{
- method: c.serviceProvider.httpMethod(),
- url: c.serviceProvider.AccessTokenUrl,
- oauthParams: orderedParams,
- }
- if _, err := c.signRequest(req, secret); err != nil {
- return nil, err
- }
-
- resp, err := c.getBody(c.serviceProvider.httpMethod(), c.serviceProvider.AccessTokenUrl, orderedParams)
- if err != nil {
- return nil, err
- }
-
- return parseAccessToken(*resp)
- }
-
- type RoundTripper struct {
- consumer *Consumer
- token *AccessToken
- }
-
- func (c *Consumer) MakeRoundTripper(token *AccessToken) (*RoundTripper, error) {
- return &RoundTripper{consumer: c, token: token}, nil
- }
-
- func (c *Consumer) MakeHttpClient(token *AccessToken) (*http.Client, error) {
- return &http.Client{
- Transport: &RoundTripper{consumer: c, token: token},
- }, nil
- }
-
- // ** DEPRECATED **
- // Please call Get on the http client returned by MakeHttpClient instead!
- //
- // Executes an HTTP Get, authorized via the AccessToken.
- // - url:
- // The base url, without any query params, which is being accessed
- //
- // - userParams:
- // Any key=value params to be included in the query string
- //
- // - token:
- // The AccessToken returned by AuthorizeToken()
- //
- // This method returns:
- // - resp:
- // The HTTP Response resulting from making this request.
- //
- // - err:
- // Set only if there was an error, nil otherwise.
- func (c *Consumer) Get(url string, userParams map[string]string, token *AccessToken) (resp *http.Response, err error) {
- return c.makeAuthorizedRequest("GET", url, LOC_URL, "", userParams, token)
- }
-
- func encodeUserParams(userParams map[string]string) string {
- data := url.Values{}
- for k, v := range userParams {
- data.Add(k, v)
- }
- return data.Encode()
- }
-
- // ** DEPRECATED **
- // Please call "Post" on the http client returned by MakeHttpClient instead
- func (c *Consumer) PostForm(url string, userParams map[string]string, token *AccessToken) (resp *http.Response, err error) {
- return c.PostWithBody(url, "", userParams, token)
- }
-
- // ** DEPRECATED **
- // Please call "Post" on the http client returned by MakeHttpClient instead
- func (c *Consumer) Post(url string, userParams map[string]string, token *AccessToken) (resp *http.Response, err error) {
- return c.PostWithBody(url, "", userParams, token)
- }
-
- // ** DEPRECATED **
- // Please call "Post" on the http client returned by MakeHttpClient instead
- func (c *Consumer) PostWithBody(url string, body string, userParams map[string]string, token *AccessToken) (resp *http.Response, err error) {
- return c.makeAuthorizedRequest("POST", url, LOC_BODY, body, userParams, token)
- }
-
- // ** DEPRECATED **
- // Please call "Do" on the http client returned by MakeHttpClient instead
- // (and set the "Content-Type" header explicitly in the http.Request)
- func (c *Consumer) PostJson(url string, body string, token *AccessToken) (resp *http.Response, err error) {
- return c.makeAuthorizedRequest("POST", url, LOC_JSON, body, nil, token)
- }
-
- // ** DEPRECATED **
- // Please call "Do" on the http client returned by MakeHttpClient instead
- // (and set the "Content-Type" header explicitly in the http.Request)
- func (c *Consumer) PostXML(url string, body string, token *AccessToken) (resp *http.Response, err error) {
- return c.makeAuthorizedRequest("POST", url, LOC_XML, body, nil, token)
- }
-
- // ** DEPRECATED **
- // Please call "Do" on the http client returned by MakeHttpClient instead
- // (and setup the multipart data explicitly in the http.Request)
- func (c *Consumer) PostMultipart(url, multipartName string, multipartData io.ReadCloser, userParams map[string]string, token *AccessToken) (resp *http.Response, err error) {
- return c.makeAuthorizedRequestReader("POST", url, LOC_MULTIPART, 0, multipartName, multipartData, userParams, token)
- }
-
- // ** DEPRECATED **
- // Please call "Delete" on the http client returned by MakeHttpClient instead
- func (c *Consumer) Delete(url string, userParams map[string]string, token *AccessToken) (resp *http.Response, err error) {
- return c.makeAuthorizedRequest("DELETE", url, LOC_URL, "", userParams, token)
- }
-
- // ** DEPRECATED **
- // Please call "Put" on the http client returned by MakeHttpClient instead
- func (c *Consumer) Put(url string, body string, userParams map[string]string, token *AccessToken) (resp *http.Response, err error) {
- return c.makeAuthorizedRequest("PUT", url, LOC_URL, body, userParams, token)
- }
-
- func (c *Consumer) Debug(enabled bool) {
- c.debug = enabled
- c.signer.Debug(enabled)
- }
-
- type pair struct {
- key string
- value string
- }
-
- type pairs []pair
-
- func (p pairs) Len() int { return len(p) }
- func (p pairs) Less(i, j int) bool { return p[i].key < p[j].key }
- func (p pairs) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
-
- // This function has basically turned into a backwards compatibility layer
- // between the old API (where clients explicitly called consumer.Get()
- // consumer.Post() etc), and the new API (which takes actual http.Requests)
- //
- // So, here we construct the appropriate HTTP request for the inputs.
- func (c *Consumer) makeAuthorizedRequestReader(method string, urlString string, dataLocation DataLocation, contentLength int, multipartName string, body io.ReadCloser, userParams map[string]string, token *AccessToken) (resp *http.Response, err error) {
- urlObject, err := url.Parse(urlString)
- if err != nil {
- return nil, err
- }
-
- request := &http.Request{
- Method: method,
- URL: urlObject,
- Header: http.Header{},
- Body: body,
- ContentLength: int64(contentLength),
- }
-
- vals := url.Values{}
- for k, v := range userParams {
- vals.Add(k, v)
- }
-
- if dataLocation != LOC_BODY {
- request.URL.RawQuery = vals.Encode()
- request.URL.RawQuery = strings.Replace(
- request.URL.RawQuery, ";", "%3B", -1)
-
- } else {
- // TODO(mrjones): validate that we're not overrideing an exising body?
- request.Body = ioutil.NopCloser(strings.NewReader(vals.Encode()))
- request.ContentLength = int64(len(vals.Encode()))
- }
-
- for k, vs := range c.AdditionalHeaders {
- for _, v := range vs {
- request.Header.Set(k, v)
- }
- }
-
- if dataLocation == LOC_BODY {
- request.Header.Set("Content-Type", "application/x-www-form-urlencoded")
- }
-
- if dataLocation == LOC_JSON {
- request.Header.Set("Content-Type", "application/json")
- }
-
- if dataLocation == LOC_XML {
- request.Header.Set("Content-Type", "application/xml")
- }
-
- if dataLocation == LOC_MULTIPART {
- pipeReader, pipeWriter := io.Pipe()
- writer := multipart.NewWriter(pipeWriter)
- if request.URL.Host == "www.mrjon.es" &&
- request.URL.Path == "/unittest" {
- writer.SetBoundary("UNITTESTBOUNDARY")
- }
- go func(body io.Reader) {
- part, err := writer.CreateFormFile(multipartName, "/no/matter")
- if err != nil {
- writer.Close()
- pipeWriter.CloseWithError(err)
- return
- }
- _, err = io.Copy(part, body)
- if err != nil {
- writer.Close()
- pipeWriter.CloseWithError(err)
- return
- }
- writer.Close()
- pipeWriter.Close()
- }(body)
- request.Body = pipeReader
- request.Header.Set("Content-Type", writer.FormDataContentType())
- }
-
- rt := RoundTripper{consumer: c, token: token}
-
- resp, err = rt.RoundTrip(request)
- if err != nil {
- return resp, err
- }
-
- if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
- defer resp.Body.Close()
- bytes, _ := ioutil.ReadAll(resp.Body)
-
- return resp, HTTPExecuteError{
- RequestHeaders: "",
- ResponseBodyBytes: bytes,
- Status: resp.Status,
- StatusCode: resp.StatusCode,
- }
- }
-
- return resp, nil
- }
-
- // cloneReq clones the src http.Request, making deep copies of the Header and
- // the URL but shallow copies of everything else
- func cloneReq(src *http.Request) *http.Request {
- dst := &http.Request{}
- *dst = *src
-
- dst.Header = make(http.Header, len(src.Header))
- for k, s := range src.Header {
- dst.Header[k] = append([]string(nil), s...)
- }
-
- if src.URL != nil {
- dst.URL = cloneURL(src.URL)
- }
-
- return dst
- }
-
- // cloneURL shallow clones the src *url.URL
- func cloneURL(src *url.URL) *url.URL {
- dst := &url.URL{}
- *dst = *src
-
- return dst
- }
-
- func canonicalizeUrl(u *url.URL) string {
- var buf bytes.Buffer
- buf.WriteString(u.Scheme)
- buf.WriteString("://")
- buf.WriteString(u.Host)
- buf.WriteString(u.Path)
-
- return buf.String()
- }
-
- func parseBody(request *http.Request) (map[string]string, error) {
- userParams := map[string]string{}
-
- // TODO(mrjones): factor parameter extraction into a separate method
- if request.Header.Get("Content-Type") !=
- "application/x-www-form-urlencoded" {
- // Most of the time we get parameters from the query string:
- for k, vs := range request.URL.Query() {
- if len(vs) != 1 {
- return nil, fmt.Errorf("Must have exactly one value per param")
- }
-
- userParams[k] = vs[0]
- }
- } else {
- // x-www-form-urlencoded parameters come from the body instead:
- defer request.Body.Close()
- originalBody, err := ioutil.ReadAll(request.Body)
- if err != nil {
- return nil, err
- }
-
- // If there was a body, we have to re-install it
- // (because we've ruined it by reading it).
- request.Body = ioutil.NopCloser(bytes.NewReader(originalBody))
-
- params, err := url.ParseQuery(string(originalBody))
- if err != nil {
- return nil, err
- }
-
- for k, vs := range params {
- if len(vs) != 1 {
- return nil, fmt.Errorf("Must have exactly one value per param")
- }
-
- userParams[k] = vs[0]
- }
- }
-
- return userParams, nil
- }
-
- func paramsToSortedPairs(params map[string]string) pairs {
- // Sort parameters alphabetically
- paramPairs := make(pairs, len(params))
- i := 0
- for key, value := range params {
- paramPairs[i] = pair{key: key, value: value}
- i++
- }
- sort.Sort(paramPairs)
-
- return paramPairs
- }
-
- func calculateBodyHash(request *http.Request, s signer) (string, error) {
- if request.Header.Get("Content-Type") ==
- "application/x-www-form-urlencoded" {
- return "", nil
- }
-
- var originalBody []byte
-
- if request.Body != nil {
- var err error
-
- defer request.Body.Close()
- originalBody, err = ioutil.ReadAll(request.Body)
- if err != nil {
- return "", err
- }
-
- // If there was a body, we have to re-install it
- // (because we've ruined it by reading it).
- request.Body = ioutil.NopCloser(bytes.NewReader(originalBody))
- }
-
- h := s.HashFunc().New()
- h.Write(originalBody)
- rawSignature := h.Sum(nil)
-
- return base64.StdEncoding.EncodeToString(rawSignature), nil
- }
-
- func (rt *RoundTripper) RoundTrip(userRequest *http.Request) (*http.Response, error) {
- serverRequest := cloneReq(userRequest)
-
- allParams := rt.consumer.baseParams(
- rt.consumer.consumerKey, rt.consumer.AdditionalParams)
-
- // Do not add the "oauth_token" parameter, if the access token has not been
- // specified. By omitting this parameter when it is not specified, allows
- // two-legged OAuth calls.
- if len(rt.token.Token) > 0 {
- allParams.Add(TOKEN_PARAM, rt.token.Token)
- }
-
- if rt.consumer.serviceProvider.BodyHash {
- bodyHash, err := calculateBodyHash(serverRequest, rt.consumer.signer)
- if err != nil {
- return nil, err
- }
-
- if bodyHash != "" {
- allParams.Add(BODY_HASH_PARAM, bodyHash)
- }
- }
-
- authParams := allParams.Clone()
-
- // TODO(mrjones): put these directly into the paramPairs below?
- userParams, err := parseBody(serverRequest)
- if err != nil {
- return nil, err
- }
- paramPairs := paramsToSortedPairs(userParams)
-
- for i := range paramPairs {
- allParams.Add(paramPairs[i].key, paramPairs[i].value)
- }
-
- signingURL := cloneURL(serverRequest.URL)
- if host := serverRequest.Host; host != "" {
- signingURL.Host = host
- }
- baseString := rt.consumer.requestString(serverRequest.Method, canonicalizeUrl(signingURL), allParams)
-
- signature, err := rt.consumer.signer.Sign(baseString, rt.token.Secret)
- if err != nil {
- return nil, err
- }
-
- authParams.Add(SIGNATURE_PARAM, signature)
-
- // Set auth header.
- oauthHdr := OAUTH_HEADER
- for pos, key := range authParams.Keys() {
- for innerPos, value := range authParams.Get(key) {
- if pos+innerPos > 0 {
- oauthHdr += ","
- }
- oauthHdr += key + "=\"" + value + "\""
- }
- }
- serverRequest.Header.Add(HTTP_AUTH_HEADER, oauthHdr)
-
- if rt.consumer.debug {
- fmt.Printf("Request: %v\n", serverRequest)
- }
-
- resp, err := rt.consumer.HttpClient.Do(serverRequest)
-
- if err != nil {
- return resp, err
- }
-
- return resp, nil
- }
-
- func (c *Consumer) makeAuthorizedRequest(method string, url string, dataLocation DataLocation, body string, userParams map[string]string, token *AccessToken) (resp *http.Response, err error) {
- return c.makeAuthorizedRequestReader(method, url, dataLocation, len(body), "", ioutil.NopCloser(strings.NewReader(body)), userParams, token)
- }
-
- type request struct {
- method string
- url string
- oauthParams *OrderedParams
- userParams map[string]string
- }
-
- type HttpClient interface {
- Do(req *http.Request) (resp *http.Response, err error)
- }
-
- type clock interface {
- Seconds() int64
- Nanos() int64
- }
-
- type nonceGenerator interface {
- Int63() int64
- }
-
- type key interface {
- String() string
- }
-
- type signer interface {
- Sign(message string, tokenSecret string) (string, error)
- Verify(message string, signature string) error
- SignatureMethod() string
- HashFunc() crypto.Hash
- Debug(enabled bool)
- }
-
- type defaultClock struct{}
-
- func (*defaultClock) Seconds() int64 {
- return time.Now().Unix()
- }
-
- func (*defaultClock) Nanos() int64 {
- return time.Now().UnixNano()
- }
-
- func (c *Consumer) signRequest(req *request, tokenSecret string) (*request, error) {
- baseString := c.requestString(req.method, req.url, req.oauthParams)
-
- signature, err := c.signer.Sign(baseString, tokenSecret)
- if err != nil {
- return nil, err
- }
-
- req.oauthParams.Add(SIGNATURE_PARAM, signature)
- return req, nil
- }
-
- // Obtains an AccessToken from the response of a service provider.
- // - data:
- // The response body.
- //
- // This method returns:
- // - atoken:
- // The AccessToken generated from the response body.
- //
- // - err:
- // Set if an AccessToken could not be parsed from the given input.
- func parseAccessToken(data string) (atoken *AccessToken, err error) {
- parts, err := url.ParseQuery(data)
- if err != nil {
- return nil, err
- }
-
- tokenParam := parts[TOKEN_PARAM]
- parts.Del(TOKEN_PARAM)
- if len(tokenParam) < 1 {
- return nil, errors.New("Missing " + TOKEN_PARAM + " in response. " +
- "Full response body: '" + data + "'")
- }
- tokenSecretParam := parts[TOKEN_SECRET_PARAM]
- parts.Del(TOKEN_SECRET_PARAM)
- if len(tokenSecretParam) < 1 {
- return nil, errors.New("Missing " + TOKEN_SECRET_PARAM + " in response." +
- "Full response body: '" + data + "'")
- }
-
- additionalData := parseAdditionalData(parts)
-
- return &AccessToken{tokenParam[0], tokenSecretParam[0], additionalData}, nil
- }
-
- func parseRequestToken(data string) (*RequestToken, error) {
- parts, err := url.ParseQuery(data)
- if err != nil {
- return nil, err
- }
-
- tokenParam := parts[TOKEN_PARAM]
- if len(tokenParam) < 1 {
- return nil, errors.New("Missing " + TOKEN_PARAM + " in response. " +
- "Full response body: '" + data + "'")
- }
- tokenSecretParam := parts[TOKEN_SECRET_PARAM]
- if len(tokenSecretParam) < 1 {
- return nil, errors.New("Missing " + TOKEN_SECRET_PARAM + " in response." +
- "Full response body: '" + data + "'")
- }
- return &RequestToken{tokenParam[0], tokenSecretParam[0]}, nil
- }
-
- func (c *Consumer) baseParams(consumerKey string, additionalParams map[string]string) *OrderedParams {
- params := NewOrderedParams()
- params.Add(VERSION_PARAM, OAUTH_VERSION)
- params.Add(SIGNATURE_METHOD_PARAM, c.signer.SignatureMethod())
- params.Add(TIMESTAMP_PARAM, strconv.FormatInt(c.clock.Seconds(), 10))
- params.Add(NONCE_PARAM, strconv.FormatInt(c.nonceGenerator.Int63(), 10))
- params.Add(CONSUMER_KEY_PARAM, consumerKey)
- for key, value := range additionalParams {
- params.Add(key, value)
- }
- return params
- }
-
- func parseAdditionalData(parts url.Values) map[string]string {
- params := make(map[string]string)
- for key, value := range parts {
- if len(value) > 0 {
- params[key] = value[0]
- }
- }
- return params
- }
-
- type HMACSigner struct {
- consumerSecret string
- hashFunc crypto.Hash
- debug bool
- }
-
- func (s *HMACSigner) Debug(enabled bool) {
- s.debug = enabled
- }
-
- func (s *HMACSigner) Sign(message string, tokenSecret string) (string, error) {
- key := escape(s.consumerSecret) + "&" + escape(tokenSecret)
- if s.debug {
- fmt.Println("Signing:", message)
- fmt.Println("Key:", key)
- }
-
- h := hmac.New(s.HashFunc().New, []byte(key))
- h.Write([]byte(message))
- rawSignature := h.Sum(nil)
-
- base64signature := base64.StdEncoding.EncodeToString(rawSignature)
- if s.debug {
- fmt.Println("Base64 signature:", base64signature)
- }
- return base64signature, nil
- }
-
- func (s *HMACSigner) Verify(message string, signature string) error {
- if s.debug {
- fmt.Println("Verifying Base64 signature:", signature)
- }
- validSignature, err := s.Sign(message, "")
- if err != nil {
- return err
- }
-
- if validSignature != signature {
- decodedSigniture, _ := url.QueryUnescape(signature)
- if validSignature != decodedSigniture {
- return fmt.Errorf("signature did not match")
- }
- }
-
- return nil
- }
-
- func (s *HMACSigner) SignatureMethod() string {
- return SIGNATURE_METHOD_HMAC + HASH_METHOD_MAP[s.HashFunc()]
- }
-
- func (s *HMACSigner) HashFunc() crypto.Hash {
- return s.hashFunc
- }
-
- type RSASigner struct {
- debug bool
- rand io.Reader
- privateKey *rsa.PrivateKey
- hashFunc crypto.Hash
- }
-
- func (s *RSASigner) Debug(enabled bool) {
- s.debug = enabled
- }
-
- func (s *RSASigner) Sign(message string, tokenSecret string) (string, error) {
- if s.debug {
- fmt.Println("Signing:", message)
- }
-
- h := s.HashFunc().New()
- h.Write([]byte(message))
- digest := h.Sum(nil)
-
- signature, err := rsa.SignPKCS1v15(s.rand, s.privateKey, s.HashFunc(), digest)
- if err != nil {
- return "", nil
- }
-
- base64signature := base64.StdEncoding.EncodeToString(signature)
- if s.debug {
- fmt.Println("Base64 signature:", base64signature)
- }
-
- return base64signature, nil
- }
-
- func (s *RSASigner) Verify(message string, base64signature string) error {
- if s.debug {
- fmt.Println("Verifying:", message)
- fmt.Println("Verifying Base64 signature:", base64signature)
- }
-
- h := s.HashFunc().New()
- h.Write([]byte(message))
- digest := h.Sum(nil)
-
- signature, err := base64.StdEncoding.DecodeString(base64signature)
- if err != nil {
- return err
- }
-
- return rsa.VerifyPKCS1v15(&s.privateKey.PublicKey, s.HashFunc(), digest, signature)
- }
-
- func (s *RSASigner) SignatureMethod() string {
- return SIGNATURE_METHOD_RSA + HASH_METHOD_MAP[s.HashFunc()]
- }
-
- func (s *RSASigner) HashFunc() crypto.Hash {
- return s.hashFunc
- }
-
- func escape(s string) string {
- t := make([]byte, 0, 3*len(s))
- for i := 0; i < len(s); i++ {
- c := s[i]
- if isEscapable(c) {
- t = append(t, '%')
- t = append(t, "0123456789ABCDEF"[c>>4])
- t = append(t, "0123456789ABCDEF"[c&15])
- } else {
- t = append(t, s[i])
- }
- }
- return string(t)
- }
-
- func isEscapable(b byte) bool {
- return !('A' <= b && b <= 'Z' || 'a' <= b && b <= 'z' || '0' <= b && b <= '9' || b == '-' || b == '.' || b == '_' || b == '~')
-
- }
-
- func (c *Consumer) requestString(method string, url string, params *OrderedParams) string {
- result := method + "&" + escape(url)
- for pos, key := range params.Keys() {
- for innerPos, value := range params.Get(key) {
- if pos+innerPos == 0 {
- result += "&"
- } else {
- result += escape("&")
- }
- result += escape(fmt.Sprintf("%s=%s", key, value))
- }
- }
- return result
- }
-
- func (c *Consumer) getBody(method, url string, oauthParams *OrderedParams) (*string, error) {
- resp, err := c.httpExecute(method, url, "", 0, nil, oauthParams)
- if err != nil {
- return nil, errors.New("httpExecute: " + err.Error())
- }
- bodyBytes, err := ioutil.ReadAll(resp.Body)
- resp.Body.Close()
- if err != nil {
- return nil, errors.New("ReadAll: " + err.Error())
- }
- bodyStr := string(bodyBytes)
- if c.debug {
- fmt.Printf("STATUS: %d %s\n", resp.StatusCode, resp.Status)
- fmt.Println("BODY RESPONSE: " + bodyStr)
- }
- return &bodyStr, nil
- }
-
- // HTTPExecuteError signals that a call to httpExecute failed.
- type HTTPExecuteError struct {
- // RequestHeaders provides a stringified listing of request headers.
- RequestHeaders string
- // ResponseBodyBytes is the response read into a byte slice.
- ResponseBodyBytes []byte
- // Status is the status code string response.
- Status string
- // StatusCode is the parsed status code.
- StatusCode int
- }
-
- // Error provides a printable string description of an HTTPExecuteError.
- func (e HTTPExecuteError) Error() string {
- return "HTTP response is not 200/OK as expected. Actual response: \n" +
- "\tResponse Status: '" + e.Status + "'\n" +
- "\tResponse Code: " + strconv.Itoa(e.StatusCode) + "\n" +
- "\tResponse Body: " + string(e.ResponseBodyBytes) + "\n" +
- "\tRequest Headers: " + e.RequestHeaders
- }
-
- func (c *Consumer) httpExecute(
- method string, urlStr string, contentType string, contentLength int, body io.Reader, oauthParams *OrderedParams) (*http.Response, error) {
- // Create base request.
- req, err := http.NewRequest(method, urlStr, body)
- if err != nil {
- return nil, errors.New("NewRequest failed: " + err.Error())
- }
-
- // Set auth header.
- req.Header = http.Header{}
- oauthHdr := "OAuth "
- for pos, key := range oauthParams.Keys() {
- for innerPos, value := range oauthParams.Get(key) {
- if pos+innerPos > 0 {
- oauthHdr += ","
- }
- oauthHdr += key + "=\"" + value + "\""
- }
- }
- req.Header.Add("Authorization", oauthHdr)
-
- // Add additional custom headers
- for key, vals := range c.AdditionalHeaders {
- for _, val := range vals {
- req.Header.Add(key, val)
- }
- }
-
- // Set contentType if passed.
- if contentType != "" {
- req.Header.Set("Content-Type", contentType)
- }
-
- // Set contentLength if passed.
- if contentLength > 0 {
- req.Header.Set("Content-Length", strconv.Itoa(contentLength))
- }
-
- if c.debug {
- fmt.Printf("Request: %v\n", req)
- }
- resp, err := c.HttpClient.Do(req)
- if err != nil {
- return nil, errors.New("Do: " + err.Error())
- }
-
- debugHeader := ""
- for k, vals := range req.Header {
- for _, val := range vals {
- debugHeader += "[key: " + k + ", val: " + val + "]"
- }
- }
-
- // StatusMultipleChoices is 300, any 2xx response should be treated as success
- if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
- defer resp.Body.Close()
- bytes, _ := ioutil.ReadAll(resp.Body)
-
- return resp, HTTPExecuteError{
- RequestHeaders: debugHeader,
- ResponseBodyBytes: bytes,
- Status: resp.Status,
- StatusCode: resp.StatusCode,
- }
- }
- return resp, err
- }
-
- //
- // String Sorting helpers
- //
-
- type ByValue []string
-
- func (a ByValue) Len() int {
- return len(a)
- }
-
- func (a ByValue) Swap(i, j int) {
- a[i], a[j] = a[j], a[i]
- }
-
- func (a ByValue) Less(i, j int) bool {
- return a[i] < a[j]
- }
-
- //
- // ORDERED PARAMS
- //
-
- type OrderedParams struct {
- allParams map[string][]string
- keyOrdering []string
- }
-
- func NewOrderedParams() *OrderedParams {
- return &OrderedParams{
- allParams: make(map[string][]string),
- keyOrdering: make([]string, 0),
- }
- }
-
- func (o *OrderedParams) Get(key string) []string {
- sort.Sort(ByValue(o.allParams[key]))
- return o.allParams[key]
- }
-
- func (o *OrderedParams) Keys() []string {
- sort.Sort(o)
- return o.keyOrdering
- }
-
- func (o *OrderedParams) Add(key, value string) {
- o.AddUnescaped(key, escape(value))
- }
-
- func (o *OrderedParams) AddUnescaped(key, value string) {
- if _, exists := o.allParams[key]; !exists {
- o.keyOrdering = append(o.keyOrdering, key)
- o.allParams[key] = make([]string, 1)
- o.allParams[key][0] = value
- } else {
- o.allParams[key] = append(o.allParams[key], value)
- }
- }
-
- func (o *OrderedParams) Len() int {
- return len(o.keyOrdering)
- }
-
- func (o *OrderedParams) Less(i int, j int) bool {
- return o.keyOrdering[i] < o.keyOrdering[j]
- }
-
- func (o *OrderedParams) Swap(i int, j int) {
- o.keyOrdering[i], o.keyOrdering[j] = o.keyOrdering[j], o.keyOrdering[i]
- }
-
- func (o *OrderedParams) Clone() *OrderedParams {
- clone := NewOrderedParams()
- for _, key := range o.Keys() {
- for _, value := range o.Get(key) {
- clone.AddUnescaped(key, value)
- }
- }
- return clone
- }
|