Skip to content

Commit

Permalink
Allow noncer to be configured (#45)
Browse files Browse the repository at this point in the history
* Add a Noncer interface to allow custom noncers
* Default to Base64Noncer (no change, recommended)
* Add HexNoncer for providers that don't accept non-alphanumeric nonces
  • Loading branch information
andrewmostello authored Oct 11, 2020
1 parent 14b83d0 commit e32d262
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 31 deletions.
23 changes: 8 additions & 15 deletions auther.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ package oauth1

import (
"bytes"
"crypto/rand"
"encoding/base64"
"fmt"
"io/ioutil"
"net/http"
Expand Down Expand Up @@ -38,19 +36,19 @@ type clock interface {
Now() time.Time
}

// A noncer provides random nonce strings.
type noncer interface {
Nonce() string
}

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

func newAuther(config *Config) *auther {
if config == nil {
config = &Config{}
}
if config.Noncer == nil {
config.Noncer = Base64Noncer{}
}
return &auther{
config: config,
}
Expand Down Expand Up @@ -135,14 +133,9 @@ func (a *auther) commonOAuthParams() map[string]string {
return params
}

// Returns a base64 encoded random 32 byte string.
// Returns a nonce using the configured Noncer.
func (a *auther) nonce() string {
if a.noncer != nil {
return a.noncer.Nonce()
}
b := make([]byte, 32)
rand.Read(b)
return base64.StdEncoding.EncodeToString(b)
return a.config.Noncer.Nonce()
}

// Returns the Unix epoch seconds.
Expand Down
17 changes: 11 additions & 6 deletions auther_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@ func TestCommonOAuthParams(t *testing.T) {
}{
{
&auther{
&Config{ConsumerKey: "some_consumer_key"},
&Config{
ConsumerKey: "some_consumer_key",
Noncer: &fixedNoncer{"some_nonce"},
},
&fixedClock{time.Unix(50037133, 0)},
&fixedNoncer{"some_nonce"},
},
map[string]string{
"oauth_consumer_key": "some_consumer_key",
Expand All @@ -31,9 +33,12 @@ func TestCommonOAuthParams(t *testing.T) {
},
{
&auther{
&Config{ConsumerKey: "some_consumer_key", Realm: "photos"},
&Config{
ConsumerKey: "some_consumer_key",
Realm: "photos",
Noncer: &fixedNoncer{"some_nonce"},
},
&fixedClock{time.Unix(50037133, 0)},
&fixedNoncer{"some_nonce"},
},
map[string]string{
"oauth_consumer_key": "some_consumer_key",
Expand All @@ -52,7 +57,7 @@ func TestCommonOAuthParams(t *testing.T) {
}

func TestNonce(t *testing.T) {
auther := &auther{}
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
Expand All @@ -62,7 +67,7 @@ func TestNonce(t *testing.T) {
}

func TestEpoch(t *testing.T) {
a := &auther{}
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
Expand Down
2 changes: 2 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ type Config struct {
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.
Expand Down
34 changes: 34 additions & 0 deletions noncer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package oauth1

import (
"crypto/rand"
"encoding/base64"
"encoding/hex"
)

// Noncer provides random nonce strings.
type Noncer interface {
Nonce() string
}

// Base64Noncer reads 32 bytes from crypto/rand and
// returns those bytes as a base64 encoded string.
type Base64Noncer struct{}

// Nonce provides a random nonce string.
func (n Base64Noncer) Nonce() string {
b := make([]byte, 32)
rand.Read(b)
return base64.StdEncoding.EncodeToString(b)
}

// HexNoncer reads 32 bytes from crypto/rand and
// returns those bytes as a base64 encoded string.
type HexNoncer struct{}

// Nonce provides a random nonce string.
func (n HexNoncer) Nonce() string {
b := make([]byte, 32)
rand.Read(b)
return hex.EncodeToString(b)
}
13 changes: 8 additions & 5 deletions reference_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,10 @@ func TestTwitterRequestTokenAuthHeader(t *testing.T) {
AuthorizeURL: "https://api.twitter.com/oauth/authorize",
AccessTokenURL: "https://api.twitter.com/oauth/access_token",
},
Noncer: &fixedNoncer{expectedNonce},
}

auther := &auther{config, &fixedClock{time.Unix(unixTimestamp, 0)}, &fixedNoncer{expectedNonce}}
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)
Expand Down Expand Up @@ -70,9 +71,10 @@ func TestTwitterAccessTokenAuthHeader(t *testing.T) {
AuthorizeURL: "https://api.twitter.com/oauth/authorize",
AccessTokenURL: "https://api.twitter.com/oauth/access_token",
},
Noncer: &fixedNoncer{expectedNonce},
}

auther := &auther{config, &fixedClock{time.Unix(unixTimestamp, 0)}, &fixedNoncer{expectedNonce}}
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)
Expand Down Expand Up @@ -105,10 +107,11 @@ var twitterConfig = &Config{
AuthorizeURL: "https://api.twitter.com/oauth/authorize",
AccessTokenURL: "https://api.twitter.com/oauth/access_token",
},
Noncer: &fixedNoncer{expectedNonce},
}

func TestTwitterParameterString(t *testing.T) {
auther := &auther{twitterConfig, &fixedClock{time.Unix(unixTimestampOfRequest, 0)}, &fixedNoncer{expectedNonce}}
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 @@ -125,7 +128,7 @@ func TestTwitterParameterString(t *testing.T) {
}

func TestTwitterSignatureBase(t *testing.T) {
auther := &auther{twitterConfig, &fixedClock{time.Unix(unixTimestampOfRequest, 0)}, &fixedNoncer{expectedNonce}}
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 @@ -148,7 +151,7 @@ func TestTwitterRequestAuthHeader(t *testing.T) {
expectedSignature := PercentEncode("tnnArxj06cWHq44gCs1OSKk/jLY=")
expectedTimestamp := "1318622958"

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

Expand Down
8 changes: 3 additions & 5 deletions transport_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ func TestTransport(t *testing.T) {
config := &Config{
ConsumerKey: expectedConsumerKey,
ConsumerSecret: "consumer_secret",
Noncer: &fixedNoncer{expectedNonce},
}
auther := &auther{
config: config,
clock: &fixedClock{time.Unix(123456789, 0)},
noncer: &fixedNoncer{expectedNonce},
}
tr := &Transport{
source: StaticTokenSource(NewToken(expectedToken, "some_secret")),
Expand Down Expand Up @@ -69,9 +69,8 @@ func TestTransport_nilSource(t *testing.T) {
tr := &Transport{
source: nil,
auther: &auther{
config: &Config{},
config: &Config{Noncer: &fixedNoncer{"any_nonce"}},
clock: &fixedClock{time.Unix(123456789, 0)},
noncer: &fixedNoncer{"any_nonce"},
},
}
client := &http.Client{Transport: tr}
Expand All @@ -86,9 +85,8 @@ func TestTransport_emptySource(t *testing.T) {
tr := &Transport{
source: StaticTokenSource(nil),
auther: &auther{
config: &Config{},
config: &Config{Noncer: &fixedNoncer{"any_nonce"}},
clock: &fixedClock{time.Unix(123456789, 0)},
noncer: &fixedNoncer{"any_nonce"},
},
}
client := &http.Client{Transport: tr}
Expand Down

0 comments on commit e32d262

Please sign in to comment.