176 lines
5.9 KiB
Go
176 lines
5.9 KiB
Go
package oauth1
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"net/url"
|
|
)
|
|
|
|
const (
|
|
oauthTokenSecretParam = "oauth_token_secret"
|
|
oauthCallbackConfirmedParam = "oauth_callback_confirmed"
|
|
)
|
|
|
|
// Config represents an OAuth1 consumer's (client's) key and secret, the
|
|
// callback URL, and the provider Endpoint to which the consumer corresponds.
|
|
type Config struct {
|
|
// Consumer Key (Client Identifier)
|
|
ConsumerKey string
|
|
// Consumer Secret (Client Shared-Secret)
|
|
ConsumerSecret string
|
|
// Callback URL
|
|
CallbackURL string
|
|
// Provider Endpoint specifying OAuth1 endpoint URLs
|
|
Endpoint Endpoint
|
|
// Realm of authorization
|
|
Realm string
|
|
// OAuth1 Signer (defaults to HMAC-SHA1)
|
|
Signer Signer
|
|
// Noncer creates request nonces (defaults to DefaultNoncer)
|
|
Noncer Noncer
|
|
}
|
|
|
|
// NewConfig returns a new Config with the given consumer key and secret.
|
|
func NewConfig(consumerKey, consumerSecret string) *Config {
|
|
return &Config{
|
|
ConsumerKey: consumerKey,
|
|
ConsumerSecret: consumerSecret,
|
|
}
|
|
}
|
|
|
|
// Client returns an HTTP client which uses the provided ctx and access Token.
|
|
func (c *Config) Client(ctx context.Context, t *Token) *http.Client {
|
|
return NewClient(ctx, c, t)
|
|
}
|
|
|
|
// NewClient returns a new http Client which signs requests via OAuth1.
|
|
func NewClient(ctx context.Context, config *Config, token *Token) *http.Client {
|
|
transport := &Transport{
|
|
Base: contextTransport(ctx),
|
|
source: StaticTokenSource(token),
|
|
auther: newAuther(config),
|
|
}
|
|
return &http.Client{Transport: transport}
|
|
}
|
|
|
|
// RequestToken obtains a Request token and secret (temporary credential) by
|
|
// POSTing a request (with oauth_callback in the auth header) to the Endpoint
|
|
// RequestTokenURL. The response body form is validated to ensure
|
|
// oauth_callback_confirmed is true. Returns the request token and secret
|
|
// (temporary credentials).
|
|
// See RFC 5849 2.1 Temporary Credentials.
|
|
func (c *Config) RequestToken() (requestToken, requestSecret string, err error) {
|
|
req, err := http.NewRequest("POST", c.Endpoint.RequestTokenURL, nil)
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
err = newAuther(c).setRequestTokenAuthHeader(req)
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
// when err is nil, resp contains a non-nil resp.Body which must be closed
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
|
|
return "", "", fmt.Errorf("oauth1: Server returned status %d", resp.StatusCode)
|
|
}
|
|
body, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
// ParseQuery to decode URL-encoded application/x-www-form-urlencoded body
|
|
values, err := url.ParseQuery(string(body))
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
requestToken = values.Get(oauthTokenParam)
|
|
requestSecret = values.Get(oauthTokenSecretParam)
|
|
if requestToken == "" || requestSecret == "" {
|
|
return "", "", errors.New("oauth1: Response missing oauth_token or oauth_token_secret")
|
|
}
|
|
if values.Get(oauthCallbackConfirmedParam) != "true" {
|
|
return "", "", errors.New("oauth1: oauth_callback_confirmed was not true")
|
|
}
|
|
return requestToken, requestSecret, nil
|
|
}
|
|
|
|
// AuthorizationURL accepts a request token and returns the *url.URL to the
|
|
// Endpoint's authorization page that asks the user (resource owner) for to
|
|
// authorize the consumer to act on his/her/its behalf.
|
|
// See RFC 5849 2.2 Resource Owner Authorization.
|
|
func (c *Config) AuthorizationURL(requestToken string) (*url.URL, error) {
|
|
authorizationURL, err := url.Parse(c.Endpoint.AuthorizeURL)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
values := authorizationURL.Query()
|
|
values.Add(oauthTokenParam, requestToken)
|
|
authorizationURL.RawQuery = values.Encode()
|
|
return authorizationURL, nil
|
|
}
|
|
|
|
// ParseAuthorizationCallback parses an OAuth1 authorization callback request
|
|
// from a provider server. The oauth_token and oauth_verifier parameters are
|
|
// parsed to return the request token from earlier in the flow and the
|
|
// verifier string.
|
|
// See RFC 5849 2.2 Resource Owner Authorization.
|
|
func ParseAuthorizationCallback(req *http.Request) (requestToken, verifier string, err error) {
|
|
// parse the raw query from the URL into req.Form
|
|
err = req.ParseForm()
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
requestToken = req.Form.Get(oauthTokenParam)
|
|
verifier = req.Form.Get(oauthVerifierParam)
|
|
if requestToken == "" || verifier == "" {
|
|
return "", "", errors.New("oauth1: Request missing oauth_token or oauth_verifier")
|
|
}
|
|
return requestToken, verifier, nil
|
|
}
|
|
|
|
// AccessToken obtains an access token (token credential) by POSTing a
|
|
// request (with oauth_token and oauth_verifier in the auth header) to the
|
|
// Endpoint AccessTokenURL. Returns the access token and secret (token
|
|
// credentials).
|
|
// See RFC 5849 2.3 Token Credentials.
|
|
func (c *Config) AccessToken(requestToken, requestSecret, verifier string) (accessToken, accessSecret string, err error) {
|
|
req, err := http.NewRequest("POST", c.Endpoint.AccessTokenURL, nil)
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
err = newAuther(c).setAccessTokenAuthHeader(req, requestToken, requestSecret, verifier)
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
// when err is nil, resp contains a non-nil resp.Body which must be closed
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
|
|
return "", "", fmt.Errorf("oauth1: Server returned status %d", resp.StatusCode)
|
|
}
|
|
body, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
// ParseQuery to decode URL-encoded application/x-www-form-urlencoded body
|
|
values, err := url.ParseQuery(string(body))
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
accessToken = values.Get(oauthTokenParam)
|
|
accessSecret = values.Get(oauthTokenSecretParam)
|
|
if accessToken == "" || accessSecret == "" {
|
|
return "", "", errors.New("oauth1: Response missing oauth_token or oauth_token_secret")
|
|
}
|
|
return accessToken, accessSecret, nil
|
|
}
|