Skip to content

Commit

Permalink
Add tests for signatureBase, nonce, and signature
Browse files Browse the repository at this point in the history
  • Loading branch information
dghubble committed Aug 24, 2015
1 parent daa689a commit faa3805
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 38 deletions.
13 changes: 7 additions & 6 deletions reference_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const (
expectedSignatureMethod = "HMAC-SHA1"
)

func TestSetRequestTokenAuthHeader(t *testing.T) {
func TestTwitterRequestTokenAuthHeader(t *testing.T) {
// example from https://dev.twitter.com/web/sign-in/implementing
var unixTimestamp int64 = 1318467427
expectedConsumerKey := "cChZNFj6T5R0TigYB9yd1w"
Expand Down Expand Up @@ -52,7 +52,7 @@ func TestSetRequestTokenAuthHeader(t *testing.T) {
assert.Equal(t, expectedSignatureMethod, params[oauthSignatureMethodParam])
}

func TestSetAccessTokenAuthHeader(t *testing.T) {
func TestTwitterAccessTokenAuthHeader(t *testing.T) {
// example from https://dev.twitter.com/web/sign-in/implementing
var unixTimestamp int64 = 1318467427
expectedConsumerKey := "cChZNFj6T5R0TigYB9yd1w"
Expand Down Expand Up @@ -108,7 +108,7 @@ var twitterConfig = &Config{
},
}

func TestParameterString(t *testing.T) {
func TestTwitterParameterString(t *testing.T) {
signer := &Signer{twitterConfig, &fixedClock{time.Unix(unixTimestampOfRequest, 0)}, &fixedNoncer{expectedNonce}}
values := url.Values{}
values.Add("status", "Hello Ladies + Gentlemen, a signed OAuth request!")
Expand All @@ -125,7 +125,7 @@ func TestParameterString(t *testing.T) {
assert.Equal(t, expectedParameterString, normalizedParameterString(params))
}

func TestSignatureBase(t *testing.T) {
func TestTwitterSignatureBase(t *testing.T) {
signer := &Signer{twitterConfig, &fixedClock{time.Unix(unixTimestampOfRequest, 0)}, &fixedNoncer{expectedNonce}}
values := url.Values{}
values.Add("status", "Hello Ladies + Gentlemen, a signed OAuth request!")
Expand All @@ -135,15 +135,16 @@ func TestSignatureBase(t *testing.T) {
req.Header.Set(contentType, formContentType)
oauthParams := signer.commonOAuthParams()
oauthParams[oauthTokenParam] = expectedTwitterOAuthToken
signatureBase, err := signatureBase(req, oauthParams)
params, err := collectParameters(req, oauthParams)
signatureBase, err := signatureBase(req, params)
// assert that the signature base string matches the reference
// checks that method is uppercased, url is encoded, parameter string is added, all joined by &
expectedSignatureBase := "POST&https%3A%2F%2Fapi.twitter.com%2F1%2Fstatuses%2Fupdate.json&include_entities%3Dtrue%26oauth_consumer_key%3Dxvz1evFS4wEEPTGEFPHBog%26oauth_nonce%3DkYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1318622958%26oauth_token%3D370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb%26oauth_version%3D1.0%26status%3DHello%2520Ladies%2520%252B%2520Gentlemen%252C%2520a%2520signed%2520OAuth%2520request%2521"
assert.Nil(t, err)
assert.Equal(t, expectedSignatureBase, signatureBase)
}

func TestRequestAuthHeader(t *testing.T) {
func TestTwitterRequestAuthHeader(t *testing.T) {
oauthTokenSecret := "LswwdoUaIvS8ltyTt5jkRh4J50vUPVVHtR2YPi5kE"
expectedSignature := PercentEncode("tnnArxj06cWHq44gCs1OSKk/jLY=")
expectedTimestamp := "1318622958"
Expand Down
64 changes: 35 additions & 29 deletions sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,17 @@ type Signer struct {
func (s *Signer) SetRequestTokenAuthHeader(req *http.Request) error {
oauthParams := s.commonOAuthParams()
oauthParams[oauthCallbackParam] = s.config.CallbackURL
signatureBase, err := signatureBase(req, oauthParams)
params, err := collectParameters(req, oauthParams)
if err != nil {
return err
}
signatureBase, err := signatureBase(req, params)
if err != nil {
return err
}
signature := signature(s.config.ConsumerSecret, "", signatureBase)
oauthParams[oauthSignatureParam] = signature
setAuthorizationHeader(req, oauthParams)
setAuthorizationHeader(req, authHeaderValue(oauthParams))
return nil
}

Expand All @@ -61,13 +65,17 @@ func (s *Signer) SetAccessTokenAuthHeader(req *http.Request, requestToken *Reque
oauthParams := s.commonOAuthParams()
oauthParams[oauthTokenParam] = requestToken.Token
oauthParams[oauthVerifierParam] = verifier
signatureBase, err := signatureBase(req, oauthParams)
params, err := collectParameters(req, oauthParams)
if err != nil {
return err
}
signatureBase, err := signatureBase(req, params)
if err != nil {
return err
}
signature := signature(s.config.ConsumerSecret, requestToken.TokenSecret, signatureBase)
oauthParams[oauthSignatureParam] = signature
setAuthorizationHeader(req, oauthParams)
setAuthorizationHeader(req, authHeaderValue(oauthParams))
return nil
}

Expand All @@ -76,13 +84,17 @@ func (s *Signer) SetAccessTokenAuthHeader(req *http.Request, requestToken *Reque
func (s *Signer) SetRequestAuthHeader(req *http.Request, accessToken *Token) error {
oauthParams := s.commonOAuthParams()
oauthParams[oauthTokenParam] = accessToken.Token
signatureBase, err := signatureBase(req, oauthParams)
params, err := collectParameters(req, oauthParams)
if err != nil {
return err
}
signatureBase, err := signatureBase(req, params)
if err != nil {
return err
}
signature := signature(s.config.ConsumerSecret, accessToken.TokenSecret, signatureBase)
oauthParams[oauthSignatureParam] = signature
setAuthorizationHeader(req, oauthParams)
setAuthorizationHeader(req, authHeaderValue(oauthParams))
return nil
}

Expand All @@ -98,7 +110,7 @@ func (s *Signer) commonOAuthParams() map[string]string {
}
}

// Returns a base64 encoded random 32 bytes.
// Returns a base64 encoded random 32 byte string.
func (s *Signer) nonce() string {
if s.noncer != nil {
return s.noncer.Nonce()
Expand All @@ -113,11 +125,10 @@ func (s *Signer) epoch() int64 {
return s.clock.Now().Unix()
}

// setAuthorizationHeader formats the OAuth1 protocol parameters into a header
// and sets the header on the Request.
func setAuthorizationHeader(req *http.Request, oauthParams map[string]string) {
authHeader := authHeaderValue(oauthParams)
req.Header.Set(authorizationHeaderParam, authHeader)
// setAuthorizationHeader sets the given headerValue as the request's
// Authorization header.
func setAuthorizationHeader(req *http.Request, headerValue string) {
req.Header.Set(authorizationHeaderParam, headerValue)
}

// authHeaderValue formats OAuth parameters according to RFC 5849 3.5.1. OAuth
Expand Down Expand Up @@ -159,23 +170,6 @@ func sortParameters(params map[string]string) []string {
return pairs
}

// signatureBase combines the uppercase request method, percent encoded base
// string URI, and parameter string. Returns the OAuth1 signature base string
// according to RFC5849 3.4.1.
func signatureBase(req *http.Request, oauthParams map[string]string) (string, error) {
method := strings.ToUpper(req.Method)
baseURL := strings.Split(req.URL.String(), "?")[0]
// add oauth, query, and body parameters into params
params, err := collectParameters(req, oauthParams)
if err != nil {
return "", err
}
parameterString := normalizedParameterString(params)
// signature base string constructed accoding to 3.4.1.1
baseParts := []string{method, PercentEncode(baseURL), PercentEncode(parameterString)}
return strings.Join(baseParts, "&"), nil
}

// collectParameters collects request parameters from the request query, OAuth
// parameters (which should exclude oauth_signature), and the request body
// provided the body is single part, form encoded, and the form content type
Expand Down Expand Up @@ -211,6 +205,18 @@ func collectParameters(req *http.Request, oauthParams map[string]string) (map[st
return params, nil
}

// signatureBase combines the uppercase request method, percent encoded base
// string URI, and normalizes the request parameters int a parameter string.
// Returns the OAuth1 signature base string according to RFC5849 3.4.1.
func signatureBase(req *http.Request, params map[string]string) (string, error) {
method := strings.ToUpper(req.Method)
baseURL := strings.Split(req.URL.String(), "?")[0]
parameterString := normalizedParameterString(params)
// signature base string constructed accoding to 3.4.1.1
baseParts := []string{method, PercentEncode(baseURL), PercentEncode(parameterString)}
return strings.Join(baseParts, "&"), nil
}

// parameterString normalizes collected OAuth parameters (which should exclude
// oauth_signature) into a parameter string as defined in RFC 5894 3.4.1.3.2.
// The parameters are encoded, sorted by key, keys and values joined with "&",
Expand Down
53 changes: 50 additions & 3 deletions signer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,24 @@ func TestCommonOAuthParams(t *testing.T) {
assert.Equal(t, expectedParams, signer.commonOAuthParams())
}

func TestNonce(t *testing.T) {
signer := &Signer{}
nonce := signer.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.
// base64 bytes = 4 * floor(bytes/3) + 4
assert.Equal(t, 44, len([]byte(nonce)))
}

func TestSetAuthorizationHeader(t *testing.T) {
expectedHeaderValue := "test_authorization_string"
req, err := http.NewRequest("GET", "/", nil)
assert.Nil(t, err)
setAuthorizationHeader(req, expectedHeaderValue)
assert.Equal(t, req.Header.Get("Authorization"), expectedHeaderValue)
}

func TestAuthHeaderValue(t *testing.T) {
cases := []struct {
params map[string]string
Expand Down Expand Up @@ -117,6 +135,26 @@ func TestCollectParameters(t *testing.T) {
// http://golang.org/src/net/http/request.go#L837
}

func TestSignatureBase(t *testing.T) {
//reqA, err := http.NewRequest("get", "HTTPS://HELLO.IO?q=test", nil)
//assert.Nil(t, err)
reqB, err := http.NewRequest("POST", "http://hello.io:8080", nil)
assert.Nil(t, err)
cases := []struct {
req *http.Request
params map[string]string
signatureBase string
}{
//{reqA, map[string]string{"a": "b", "c": "d"}, ""},
{reqB, map[string]string{"a": "b"}, "POST&http%3A%2F%2Fhello.io%3A8080&a%3Db"},
}
for _, c := range cases {
base, err := signatureBase(c.req, c.params)
assert.Nil(t, err)
assert.Equal(t, c.signatureBase, base)
}
}

func TestNormalizedParameterString(t *testing.T) {
simple := map[string]string{
"a": "b & c",
Expand All @@ -136,13 +174,22 @@ func TestNormalizedParameterString(t *testing.T) {
"plus": "2 q",
}
cases := []struct {
params map[string]string
authHeader string
params map[string]string
parameterStr string
}{
{simple, "%E2%98%83=snowman&a=b%20%26%20c"},
{rfcExample, "a2=r%20b&a3=a&b5=%3D%253D&c%40=&c2=&oauth_consumer_key=9djdj82h48djs9d2&oauth_nonce=7d8f3e4a&oauth_signature_method=HMAC-SHA1&oauth_timestamp=137131201&oauth_token=kkk9d7dh3k39sjv7&plus=2%20q"},
}
for _, c := range cases {
assert.Equal(t, c.authHeader, normalizedParameterString(c.params))
assert.Equal(t, c.parameterStr, normalizedParameterString(c.params))
}
}

func TestSignature(t *testing.T) {
consumerSecret := "consumer_secret"
tokenSecret := "token_secret"
message := "hello world"
// echo -n "hello world" | openssl dgst -sha1 -hmac "consumer_secret&token_secret" -binary | base64
expectedSignature := "BE0uILOruKfSXd4UzYlLJDfOq08="
assert.Equal(t, expectedSignature, signature(consumerSecret, tokenSecret, message))
}

0 comments on commit faa3805

Please sign in to comment.