Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Export Auther #76

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 33 additions & 33 deletions auther.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package oauth1
import (
"bytes"
"fmt"
"io/ioutil"
"io"
"net/http"
"net/url"
"sort"
Expand Down Expand Up @@ -32,33 +32,33 @@ const (

// clock provides a interface for current time providers. A Clock can be used
// in place of calling time.Now() directly.
type clock interface {
type Clock interface {
Now() time.Time
}

// auther adds an "OAuth" Authorization header field to requests.
type auther struct {
config *Config
clock clock
type Auther struct {
Config *Config
Clock Clock
}

func newAuther(config *Config) *auther {
func NewAuther(config *Config) *Auther {
if config == nil {
config = &Config{}
}
if config.Noncer == nil {
config.Noncer = Base64Noncer{}
}
return &auther{
config: config,
return &Auther{
Config: config,
}
}

// setRequestTokenAuthHeader adds the OAuth1 header for the request token
// SetRequestTokenAuthHeader adds the OAuth1 header for the request token
// request (temporary credential) according to RFC 5849 2.1.
func (a *auther) setRequestTokenAuthHeader(req *http.Request) error {
func (a *Auther) SetRequestTokenAuthHeader(req *http.Request) error {
oauthParams := a.commonOAuthParams()
oauthParams[oauthCallbackParam] = a.config.CallbackURL
oauthParams[oauthCallbackParam] = a.Config.CallbackURL
params, err := collectParameters(req, oauthParams)
if err != nil {
return err
Expand All @@ -69,16 +69,16 @@ func (a *auther) setRequestTokenAuthHeader(req *http.Request) error {
return err
}
oauthParams[oauthSignatureParam] = signature
if a.config.Realm != "" {
oauthParams[realmParam] = a.config.Realm
if a.Config.Realm != "" {
oauthParams[realmParam] = a.Config.Realm
}
req.Header.Set(authorizationHeaderParam, authHeaderValue(oauthParams))
return nil
}

// setAccessTokenAuthHeader sets the OAuth1 header for the access token request
// SetAccessTokenAuthHeader sets the OAuth1 header for the access token request
// (token credential) according to RFC 5849 2.3.
func (a *auther) setAccessTokenAuthHeader(req *http.Request, requestToken, requestSecret, verifier string) error {
func (a *Auther) SetAccessTokenAuthHeader(req *http.Request, requestToken, requestSecret, verifier string) error {
oauthParams := a.commonOAuthParams()
oauthParams[oauthTokenParam] = requestToken
oauthParams[oauthVerifierParam] = verifier
Expand All @@ -96,9 +96,9 @@ func (a *auther) setAccessTokenAuthHeader(req *http.Request, requestToken, reque
return nil
}

// setRequestAuthHeader sets the OAuth1 header for making authenticated
// SetRequestAuthHeader sets the OAuth1 header for making authenticated
// requests with an AccessToken (token credential) according to RFC 5849 3.1.
func (a *auther) setRequestAuthHeader(req *http.Request, accessToken *Token) error {
func (a *Auther) SetRequestAuthHeader(req *http.Request, accessToken *Token) error {
oauthParams := a.commonOAuthParams()
oauthParams[oauthTokenParam] = accessToken.Token
params, err := collectParameters(req, oauthParams)
Expand All @@ -115,43 +115,43 @@ func (a *auther) setRequestAuthHeader(req *http.Request, accessToken *Token) err
return nil
}

// commonOAuthParams returns a map of the common OAuth1 protocol parameters,
// CommonOAuthParams returns a map of the common OAuth1 protocol parameters,
// excluding the oauth_signature parameter. This includes the realm parameter
// if it was set in the config. The realm parameter will not be included in
// the signature base string as specified in RFC 5849 3.4.1.3.1.
func (a *auther) commonOAuthParams() map[string]string {
func (a *Auther) commonOAuthParams() map[string]string {
params := map[string]string{
oauthConsumerKeyParam: a.config.ConsumerKey,
oauthConsumerKeyParam: a.Config.ConsumerKey,
oauthSignatureMethodParam: a.signer().Name(),
oauthTimestampParam: strconv.FormatInt(a.epoch(), 10),
oauthNonceParam: a.nonce(),
oauthVersionParam: defaultOauthVersion,
}
if a.config.Realm != "" {
params[realmParam] = a.config.Realm
if a.Config.Realm != "" {
params[realmParam] = a.Config.Realm
}
return params
}

// Returns a nonce using the configured Noncer.
func (a *auther) nonce() string {
return a.config.Noncer.Nonce()
func (a *Auther) nonce() string {
return a.Config.Noncer.Nonce()
}

// Returns the Unix epoch seconds.
func (a *auther) epoch() int64 {
if a.clock != nil {
return a.clock.Now().Unix()
func (a *Auther) epoch() int64 {
if a.Clock != nil {
return a.Clock.Now().Unix()
}
return time.Now().Unix()
}

// Returns the Config's Signer or the default Signer.
func (a *auther) signer() Signer {
if a.config.Signer != nil {
return a.config.Signer
func (a *Auther) signer() Signer {
if a.Config.Signer != nil {
return a.Config.Signer
}
return &HMACSigner{ConsumerSecret: a.config.ConsumerSecret}
return &HMACSigner{ConsumerSecret: a.Config.ConsumerSecret}
}

// authHeaderValue formats OAuth parameters according to RFC 5849 3.5.1. OAuth
Expand Down Expand Up @@ -207,7 +207,7 @@ func collectParameters(req *http.Request, oauthParams map[string]string) (map[st
}
if req.Body != nil && req.Header.Get(contentType) == formContentType {
// reads data to a []byte, draining req.Body
b, err := ioutil.ReadAll(req.Body)
b, err := io.ReadAll(req.Body)
if err != nil {
return nil, err
}
Expand All @@ -220,7 +220,7 @@ func collectParameters(req *http.Request, oauthParams map[string]string) (map[st
params[key] = value[0]
}
// reinitialize Body with ReadCloser over the []byte
req.Body = ioutil.NopCloser(bytes.NewReader(b))
req.Body = io.NopCloser(bytes.NewReader(b))
}
for key, value := range oauthParams {
// according to 3.4.1.3.1. the realm parameter is excluded
Expand Down
23 changes: 12 additions & 11 deletions auther_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ import (

func TestCommonOAuthParams(t *testing.T) {
cases := []struct {
auther *auther
Auther *Auther
expectedParams map[string]string
}{
{
&auther{
&Auther{
&Config{
ConsumerKey: "some_consumer_key",
Noncer: &fixedNoncer{"some_nonce"},
Expand All @@ -32,7 +32,7 @@ func TestCommonOAuthParams(t *testing.T) {
},
},
{
&auther{
&Auther{
&Config{
ConsumerKey: "some_consumer_key",
Realm: "photos",
Expand All @@ -52,13 +52,13 @@ func TestCommonOAuthParams(t *testing.T) {
}

for _, c := range cases {
assert.Equal(t, c.expectedParams, c.auther.commonOAuthParams())
assert.Equal(t, c.expectedParams, c.Auther.commonOAuthParams())
}
}

func TestNonce(t *testing.T) {
auther := newAuther(nil)
nonce := auther.nonce()
Auther := NewAuther(nil)
nonce := Auther.nonce()
// assert that 32 bytes (256 bites) become 44 bytes since a base64 byte
// zeros the 2 high bits. 3 bytes convert to 4 base64 bytes, 40 base64 bytes
// represent the first 30 of 32 bytes, = padding adds another 4 byte group.
Expand All @@ -67,17 +67,17 @@ func TestNonce(t *testing.T) {
}

func TestEpoch(t *testing.T) {
a := newAuther(nil)
a := NewAuther(nil)
// assert that a real time is used by default
assert.InEpsilon(t, time.Now().Unix(), a.epoch(), 1)
// assert that the fixed clock can be used for testing
a = &auther{clock: &fixedClock{time.Unix(50037133, 0)}}
a = &Auther{Clock: &fixedClock{time.Unix(50037133, 0)}}
assert.Equal(t, int64(50037133), a.epoch())
}

func TestSigner_Default(t *testing.T) {
config := &Config{ConsumerSecret: "consumer_secret"}
a := newAuther(config)
a := NewAuther(config)
// echo -n "hello world" | openssl dgst -sha1 -hmac "consumer_secret&token_secret" -binary | base64
expectedSignature := "BE0uILOruKfSXd4UzYlLJDfOq08="
// assert that the default signer produces the expected HMAC-SHA1 digest
Expand All @@ -92,7 +92,7 @@ func TestSigner_SHA256(t *testing.T) {
config := &Config{
Signer: &HMAC256Signer{ConsumerSecret: "consumer_secret"},
}
a := newAuther(config)
a := NewAuther(config)
// echo -n "hello world" | openssl dgst -sha256 -hmac "consumer_secret&token_secret" -binary | base64
expectedSignature := "pW9drXUyErU8DASWbsP2I3XZbju37AW+VzcGdYSeMo8="
// assert that the signer produces the expected HMAC-SHA256 digest
Expand All @@ -118,7 +118,7 @@ func TestSigner_Custom(t *testing.T) {
ConsumerSecret: "consumer_secret",
Signer: &identitySigner{},
}
a := newAuther(config)
a := NewAuther(config)
// assert that the custom signer is used
method := a.signer().Name()
digest, err := a.signer().Sign("secret", "hello world")
Expand Down Expand Up @@ -249,6 +249,7 @@ func TestBaseURI(t *testing.T) {
reqB, err := http.NewRequest("POST", "https://www.example.net:8080/?q=1", nil)
assert.Nil(t, err)
reqC, err := http.NewRequest("POST", "https://example.com:443", nil)
assert.Nil(t, err)
cases := []struct {
req *http.Request
baseURI string
Expand Down
6 changes: 3 additions & 3 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func NewClient(ctx context.Context, config *Config, token *Token) *http.Client {
transport := &Transport{
Base: contextTransport(ctx),
source: StaticTokenSource(token),
auther: newAuther(config),
auther: NewAuther(config),
}
return &http.Client{Transport: transport}
}
Expand All @@ -70,7 +70,7 @@ func (c *Config) RequestToken() (requestToken, requestSecret string, err error)
if err != nil {
return "", "", err
}
err = newAuther(c).setRequestTokenAuthHeader(req)
err = NewAuther(c).SetRequestTokenAuthHeader(req)
if err != nil {
return "", "", err
}
Expand Down Expand Up @@ -149,7 +149,7 @@ func (c *Config) AccessToken(requestToken, requestSecret, verifier string) (acce
if err != nil {
return "", "", err
}
err = newAuther(c).setAccessTokenAuthHeader(req, requestToken, requestSecret, verifier)
err = NewAuther(c).SetAccessTokenAuthHeader(req, requestToken, requestSecret, verifier)
if err != nil {
return "", "", err
}
Expand Down
16 changes: 8 additions & 8 deletions reference_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@ func TestTwitterRequestTokenAuthHeader(t *testing.T) {
Noncer: &fixedNoncer{expectedNonce},
}

auther := &auther{config, &fixedClock{time.Unix(unixTimestamp, 0)}}
auther := &Auther{config, &fixedClock{time.Unix(unixTimestamp, 0)}}
req, err := http.NewRequest("POST", config.Endpoint.RequestTokenURL, nil)
assert.Nil(t, err)
err = auther.setRequestTokenAuthHeader(req)
err = auther.SetRequestTokenAuthHeader(req)
// assert the request for a request token is signed and has an oauth_callback
assert.Nil(t, err)
params := parseOAuthParamsOrFail(t, req.Header.Get(authorizationHeaderParam))
Expand Down Expand Up @@ -74,10 +74,10 @@ func TestTwitterAccessTokenAuthHeader(t *testing.T) {
Noncer: &fixedNoncer{expectedNonce},
}

auther := &auther{config, &fixedClock{time.Unix(unixTimestamp, 0)}}
auther := &Auther{config, &fixedClock{time.Unix(unixTimestamp, 0)}}
req, err := http.NewRequest("POST", config.Endpoint.AccessTokenURL, nil)
assert.Nil(t, err)
err = auther.setAccessTokenAuthHeader(req, expectedRequestToken, requestTokenSecret, expectedVerifier)
err = auther.SetAccessTokenAuthHeader(req, expectedRequestToken, requestTokenSecret, expectedVerifier)
// assert the request for an access token is signed and has an oauth_token and verifier
assert.Nil(t, err)
params := parseOAuthParamsOrFail(t, req.Header.Get(authorizationHeaderParam))
Expand Down Expand Up @@ -111,7 +111,7 @@ var twitterConfig = &Config{
}

func TestTwitterParameterString(t *testing.T) {
auther := &auther{twitterConfig, &fixedClock{time.Unix(unixTimestampOfRequest, 0)}}
auther := &Auther{twitterConfig, &fixedClock{time.Unix(unixTimestampOfRequest, 0)}}
values := url.Values{}
values.Add("status", "Hello Ladies + Gentlemen, a signed OAuth request!")
// note: the reference example is old and uses api v1 in the URL
Expand All @@ -128,7 +128,7 @@ func TestTwitterParameterString(t *testing.T) {
}

func TestTwitterSignatureBase(t *testing.T) {
auther := &auther{twitterConfig, &fixedClock{time.Unix(unixTimestampOfRequest, 0)}}
auther := &Auther{twitterConfig, &fixedClock{time.Unix(unixTimestampOfRequest, 0)}}
values := url.Values{}
values.Add("status", "Hello Ladies + Gentlemen, a signed OAuth request!")
// note: the reference example is old and uses api v1 in the URL
Expand All @@ -151,15 +151,15 @@ func TestTwitterRequestAuthHeader(t *testing.T) {
expectedSignature := PercentEncode("tnnArxj06cWHq44gCs1OSKk/jLY=")
expectedTimestamp := "1318622958"

auther := &auther{twitterConfig, &fixedClock{time.Unix(unixTimestampOfRequest, 0)}}
auther := &Auther{twitterConfig, &fixedClock{time.Unix(unixTimestampOfRequest, 0)}}
values := url.Values{}
values.Add("status", "Hello Ladies + Gentlemen, a signed OAuth request!")

accessToken := &Token{expectedTwitterOAuthToken, oauthTokenSecret}
req, err := http.NewRequest("POST", "https://api.twitter.com/1/statuses/update.json?include_entities=true", strings.NewReader(values.Encode()))
assert.Nil(t, err)
req.Header.Set(contentType, formContentType)
err = auther.setRequestAuthHeader(req, accessToken)
err = auther.SetRequestAuthHeader(req, accessToken)
// assert that request is signed and has an access token token
assert.Nil(t, err)
params := parseOAuthParamsOrFail(t, req.Header.Get(authorizationHeaderParam))
Expand Down
4 changes: 2 additions & 2 deletions transport.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ type Transport struct {
// source supplies the token to use when signing a request
source TokenSource
// auther adds OAuth1 Authorization headers to requests
auther *auther
auther *Auther
}

// RoundTrip authorizes the request with a signed OAuth1 Authorization header
Expand All @@ -36,7 +36,7 @@ func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
}
// RoundTripper should not modify the given request, clone it
req2 := cloneRequest(req)
err = t.auther.setRequestAuthHeader(req2, accessToken)
err = t.auther.SetRequestAuthHeader(req2, accessToken)
if err != nil {
return nil, err
}
Expand Down
18 changes: 9 additions & 9 deletions transport_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ func TestTransport(t *testing.T) {
ConsumerSecret: "consumer_secret",
Noncer: &fixedNoncer{expectedNonce},
}
auther := &auther{
config: config,
clock: &fixedClock{time.Unix(123456789, 0)},
auther := &Auther{
Config: config,
Clock: &fixedClock{time.Unix(123456789, 0)},
}
tr := &Transport{
source: StaticTokenSource(NewToken(expectedToken, "some_secret")),
Expand Down Expand Up @@ -68,9 +68,9 @@ func TestTransport_customBaseTransport(t *testing.T) {
func TestTransport_nilSource(t *testing.T) {
tr := &Transport{
source: nil,
auther: &auther{
config: &Config{Noncer: &fixedNoncer{"any_nonce"}},
clock: &fixedClock{time.Unix(123456789, 0)},
auther: &Auther{
Config: &Config{Noncer: &fixedNoncer{"any_nonce"}},
Clock: &fixedClock{time.Unix(123456789, 0)},
},
}
client := &http.Client{Transport: tr}
Expand All @@ -84,9 +84,9 @@ func TestTransport_nilSource(t *testing.T) {
func TestTransport_emptySource(t *testing.T) {
tr := &Transport{
source: StaticTokenSource(nil),
auther: &auther{
config: &Config{Noncer: &fixedNoncer{"any_nonce"}},
clock: &fixedClock{time.Unix(123456789, 0)},
auther: &Auther{
Config: &Config{Noncer: &fixedNoncer{"any_nonce"}},
Clock: &fixedClock{time.Unix(123456789, 0)},
},
}
client := &http.Client{Transport: tr}
Expand Down